diff --git a/appdomain.go b/appdomain.go index 15c770b..fd10908 100644 --- a/appdomain.go +++ b/appdomain.go @@ -1,9 +1,12 @@ -package main +// +build windows + +package clr import ( - "github.com/Microsoft/go-winio/pkg/guid" "syscall" "unsafe" + + "github.com/Microsoft/go-winio/pkg/guid" ) type AppDomain struct { @@ -83,7 +86,7 @@ type AppDomainVtbl struct { get_DynamicDirectory uintptr } -func newAppDomain(ppv uintptr) *AppDomain { +func NewAppDomain(ppv uintptr) *AppDomain { return (*AppDomain)(unsafe.Pointer(ppv)) } diff --git a/assembly.go b/assembly.go index cff97e9..666925e 100644 --- a/assembly.go +++ b/assembly.go @@ -1,9 +1,12 @@ -package main +// +build windows + +package clr import ( - "github.com/Microsoft/go-winio/pkg/guid" "syscall" "unsafe" + + "github.com/Microsoft/go-winio/pkg/guid" ) // from mscorlib.tlh @@ -16,6 +19,10 @@ type AssemblyVtbl struct { QueryInterface uintptr AddRef uintptr Release uintptr + GetTypeInfoCount uintptr + GetTypeInfo uintptr + GetIDsOfNames uintptr + Invoke uintptr get_ToString uintptr Equals uintptr GetHashCode uintptr @@ -62,7 +69,7 @@ type AssemblyVtbl struct { get_GlobalAssemblyCache uintptr } -func newAssembly(ppv uintptr) *Assembly { +func NewAssembly(ppv uintptr) *Assembly { return (*Assembly)(unsafe.Pointer(ppv)) } diff --git a/examples/DLLfromDisk/DLLfromDisk.go b/examples/DLLfromDisk/DLLfromDisk.go new file mode 100644 index 0000000..f4adf5f --- /dev/null +++ b/examples/DLLfromDisk/DLLfromDisk.go @@ -0,0 +1,73 @@ +// +build windows + +package main + +import ( + "fmt" + "log" + "syscall" + + clr "github.com/ropnop/go-clr" +) + +func must(err error) { + if err != nil { + log.Fatal(err) + } +} + +func checkOK(hr uintptr, caller string) { + if hr != 0x0 { + log.Fatalf("%s returned 0x%08x", hr) + } +} + +func main() { + metahost, err := clr.GetICLRMetaHost() + must(err) + fmt.Println("[+] Got metahost") + + installedRuntimes, err := clr.GetInstalledRuntimes(metahost) + must(err) + fmt.Printf("[+] Found installed runtimes: %s\n", installedRuntimes) + versionString := "v4.0.30319" + pwzVersion, _ := syscall.UTF16PtrFromString(versionString) + var pRuntimeInfo uintptr + + hr := metahost.GetRuntime(pwzVersion, &clr.IID_ICLRRuntimeInfo, &pRuntimeInfo) + checkOK(hr, "metaHost.GetRuntime") + runtimeInfo := clr.NewICLRRuntimeInfo(pRuntimeInfo) + fmt.Printf("[+] Using runtime: %s\n", versionString) + + var isLoadable bool + hr = runtimeInfo.IsLoadable(&isLoadable) + checkOK(hr, "runtimeInfo.IsLoadable") + if !isLoadable { + log.Fatal("[!] IsLoadable returned false. Bailing...") + } + + var pRuntimeHost uintptr + hr = runtimeInfo.GetInterface(&clr.CLSID_CLRRuntimeHost, &clr.IID_ICLRRuntimeHost, &pRuntimeHost) + checkOK(hr, "runtimeInfo.GetInterface") + runtimeHost := clr.NewICLRRuntimeHost(pRuntimeHost) + + hr = runtimeHost.Start() + checkOK(hr, "runtimeHost.Start") + fmt.Println("[+] Loaded CLR into this process") + + fmt.Println("[+] Executing assembly...") + pDLLPath, _ := syscall.UTF16PtrFromString("TestDLL.dll") + pTypeName, _ := syscall.UTF16PtrFromString("TestDLL.HelloWorld") + pMethodName, _ := syscall.UTF16PtrFromString("SayHello") + pArgument, _ := syscall.UTF16PtrFromString("foobar") + var pReturnVal *uint16 + hr = runtimeHost.ExecuteInDefaultAppDomain( + pDLLPath, + pTypeName, + pMethodName, + pArgument, + pReturnVal) + + checkOK(hr, "runtimeHost.ExecuteInDefaultAppDomain") + fmt.Printf("[+] Assembly returned: 0x%x\n", pReturnVal) +} diff --git a/examples/EXEfromMemory/EXEfromMemory.go b/examples/EXEfromMemory/EXEfromMemory.go new file mode 100644 index 0000000..21cfa6f --- /dev/null +++ b/examples/EXEfromMemory/EXEfromMemory.go @@ -0,0 +1,109 @@ +// +build windows + +package main + +import ( + "fmt" + "io/ioutil" + "log" + "runtime" + "syscall" + "unsafe" + + clr "github.com/ropnop/go-clr" +) + +func must(err error) { + if err != nil { + log.Fatal(err) + } +} + +func checkOK(hr uintptr, caller string) { + if hr != 0x0 { + log.Fatalf("%s returned 0x%08x", caller, hr) + } +} + +func main() { + metaHost, err := clr.GetMetaHost() + must(err) + versionString := "v4.0.30319" + pwzVersion, _ := syscall.UTF16PtrFromString(versionString) + var pRuntimeInfo uintptr + hr := metaHost.GetRuntime(pwzVersion, &clr.IID_ICLRRuntimeInfo, &pRuntimeInfo) + checkOK(hr, "metahost.GetRuntime") + runtimeInfo := clr.NewICLRRuntimeInfo(pRuntimeInfo) + + var isLoadable bool + hr = runtimeInfo.IsLoadable(&isLoadable) + checkOK(hr, "runtimeInfo.IsLoadable") + if !isLoadable { + log.Fatal("[!] IsLoadable returned false. Bailing...") + } + + hr = runtimeInfo.BindAsLegacyV2Runtime() + checkOK(hr, "runtimeInfo.BindAsLegacyV2Runtime") + + var pRuntimeHost uintptr + hr = runtimeInfo.GetInterface(&clr.CLSID_CorRuntimeHost, &clr.IID_ICorRuntimeHost, &pRuntimeHost) + runtimeHost := clr.NewICORRuntimeHost(pRuntimeHost) + hr = runtimeHost.Start() + checkOK(hr, "runtimeHost.Start") + fmt.Println("[+] Loaded CLR into this process") + + var pAppDomain uintptr + var pIUnknown uintptr + hr = runtimeHost.GetDefaultDomain(&pIUnknown) + checkOK(hr, "runtimeHost.GetDefaultDomain") + iu := clr.NewIUnknown(pIUnknown) + hr = iu.QueryInterface(&clr.IID_AppDomain, &pAppDomain) + checkOK(hr, "iu.QueryInterface") + appDomain := clr.NewAppDomain(pAppDomain) + fmt.Println("[+] Got default AppDomain") + + testEXEBytes, err := ioutil.ReadFile("./TestEXE.exe") + must(err) + runtime.KeepAlive(testEXEBytes) + + fmt.Printf("[+] Loaded %d bytes into memory from TestExe.exe\n", len(testEXEBytes)) + + safeArray, err := clr.CreateSafeArray(testEXEBytes) + must(err) + runtime.KeepAlive(safeArray) + fmt.Println("[+] Crated SafeArray from byte array") + + var pAssembly uintptr + hr = appDomain.Load_3(uintptr(unsafe.Pointer(&safeArray)), &pAssembly) + checkOK(hr, "appDomain.Load_3") + assembly := clr.NewAssembly(pAssembly) + fmt.Printf("[+] Executable loaded into memory at 0x%08x\n", pAssembly) + + var pEntryPointInfo uintptr + hr = assembly.GetEntryPoint(&pEntryPointInfo) + checkOK(hr, "assembly.GetEntryPoint") + fmt.Printf("[+] Executable entrypoint found at 0x%08x. Calling...\n", pEntryPointInfo) + fmt.Println("-------") + methodInfo := clr.NewMethodInfo(pEntryPointInfo) + + var pRetCode uintptr + nullVariant := clr.Variant{ + VT: 1, + Val: uintptr(0), + } + hr = methodInfo.Invoke_3( + nullVariant, + uintptr(0), + &pRetCode) + + fmt.Println("-------") + + checkOK(hr, "methodInfo.Invoke_3") + fmt.Printf("[+] Executable returned code %d\n", pRetCode) + + appDomain.Release() + runtimeHost.Release() + runtimeInfo.Release() + metaHost.Release() + +} diff --git a/go-clr.go b/go-clr.go new file mode 100644 index 0000000..9a45457 --- /dev/null +++ b/go-clr.go @@ -0,0 +1,152 @@ +// +build windows + +package clr + +import ( + "fmt" + "strings" + "syscall" +) + +var ( + Metahost *ICLRMetaHost + RuntimeInfo *ICLRRuntimeInfo + IsLoadable bool + LegacyV2Runtime bool + CorRuntimeHost *ICORRuntimeHost + ClrRuntimeHost *ICLRRuntimeHost + Iu *IUnknown + LoadedAppDomain *AppDomain + LoadedAssembly *Assembly + EntryPoint *MethodInfo +) + +func GetMetaHost() (*ICLRMetaHost, error) { + var pMetaHost uintptr + hr := CLRCreateInstance(&CLSID_CLRMetaHost, &IID_ICLRMetaHost, &pMetaHost) + err := checkOK(hr, "CLRCreateInstance") + if err != nil { + return nil, err + } + return NewICLRMetaHost(pMetaHost), nil +} + +func GetInstalledRuntimes(metahost *ICLRMetaHost) ([]string, error) { + var runtimes []string + var pInstalledRuntimes uintptr + hr := metahost.EnumerateInstalledRuntimes(&pInstalledRuntimes) + err := checkOK(hr, "EnumerateInstalledRuntimes") + if err != nil { + return runtimes, err + } + installedRuntimes := NewIEnumUnknown(pInstalledRuntimes) + var pRuntimeInfo uintptr + var fetched = uint32(0) + var versionString string + versionStringBytes := make([]uint16, 20) + versionStringSize := uint32(len(versionStringBytes)) + var runtimeInfo *ICLRRuntimeInfo + for { + hr = installedRuntimes.Next(1, &pRuntimeInfo, &fetched) + if hr != S_OK { + break + } + runtimeInfo = NewICLRRuntimeInfo(pRuntimeInfo) + if ret := runtimeInfo.GetVersionString(&versionStringBytes[0], &versionStringSize); ret != S_OK { + return runtimes, fmt.Errorf("GetVersionString returned 0x%08x", ret) + } + versionString = syscall.UTF16ToString(versionStringBytes) + runtimes = append(runtimes, versionString) + } + if len(runtimes) == 0 { + return runtimes, fmt.Errorf("Could not find any installed runtimes") + } + runtimeInfo.Release() + return runtimes, err +} + +func GetRuntimeInfo(metahost *ICLRMetaHost, version string) (*ICLRRuntimeInfo, error) { + pwzVersion, err := syscall.UTF16PtrFromString(version) + if err != nil { + return nil, err + } + var pRuntimeInfo uintptr + hr := metahost.GetRuntime(pwzVersion, &IID_ICLRRuntimeInfo, &pRuntimeInfo) + err = checkOK(hr, "metahost.GetRuntime") + if err != nil { + return nil, err + } + return NewICLRRuntimeInfo(pRuntimeInfo), nil +} + +func GetICLRRuntimeHost(runtimeInfo *ICLRRuntimeInfo) (*ICLRRuntimeHost, error) { + var pRuntimeHost uintptr + hr := runtimeInfo.GetInterface(&CLSID_CorRuntimeHost, &IID_ICLRRuntimeHost, &pRuntimeHost) + err := checkOK(hr, "runtimeInfo.GetInterface") + if err != nil { + return nil, err + } + return NewICLRRuntimeHost(pRuntimeHost), nil +} + +// ExecuteDLL is a wrapper function that will automatically load the latest installed CLR into the current process +// and execute a DLL on disk in the default app domain +func ExecuteDLL(dllpath, typeName, methodName, argument string) (retCode int, err error) { + retCode = -1 + metahost, err := GetMetaHost() + if err != nil { + return + } + defer metahost.Release() + + runtimes, err := GetInstalledRuntimes(metahost) + if err != nil { + return + } + var latestRuntime string + for _, r := range runtimes { + if strings.Contains(r, "v4") { + latestRuntime = r + break + } else { + latestRuntime = r + } + } + runtimeInfo, err := GetRuntimeInfo(metahost, latestRuntime) + if err != nil { + return + } + defer runtimeInfo.Release() + var isLoadable bool + hr := runtimeInfo.IsLoadable(&isLoadable) + err = checkOK(hr, "runtimeInfo.IsLoadable") + if err != nil { + return + } + if !isLoadable { + return -1, fmt.Errorf("%s is not loadable for some reason", latestRuntime) + } + runtimeHost, err := GetICLRRuntimeHost(runtimeInfo) + if err != nil { + return + } + defer runtimeHost.Release() + hr = runtimeHost.Start() + err = checkOK(hr, "runtimeHost.Start") + if err != nil { + return + } + + pDLLPath, _ := syscall.UTF16PtrFromString(dllpath) + pTypeName, _ := syscall.UTF16PtrFromString(typeName) + pMethodName, _ := syscall.UTF16PtrFromString(methodName) + pArgument, _ := syscall.UTF16PtrFromString(argument) + var pReturnVal *uint16 + hr = runtimeHost.ExecuteInDefaultAppDomain(pDLLPath, pTypeName, pMethodName, pArgument, pReturnVal) + err = checkOK(hr, "runtimeHost.ExecuteInDefaultAppDomain") + if err != nil { + return int(*pReturnVal), err + } + return int(*pReturnVal), nil + +} diff --git a/go.mod b/go.mod index 10a1a22..13e69de 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,5 @@ go 1.13 require ( github.com/Microsoft/go-winio v0.4.14 - github.com/go-ole/go-ole v1.2.4 - github.com/pkg/errors v0.9.1 - golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae + golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 ) diff --git a/go.sum b/go.sum index 02f6060..e422f6e 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,17 @@ github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= -github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/guids.go b/guids.go new file mode 100644 index 0000000..39a3487 --- /dev/null +++ b/guids.go @@ -0,0 +1,18 @@ +package clr + +import ( + "golang.org/x/sys/windows" +) + +var ( + CLSID_CLRMetaHost = windows.GUID{0x9280188d, 0xe8e, 0x4867, [8]byte{0xb3, 0xc, 0x7f, 0xa8, 0x38, 0x84, 0xe8, 0xde}} + IID_ICLRMetaHost = windows.GUID{0xD332DB9E, 0xB9B3, 0x4125, [8]byte{0x82, 0x07, 0xA1, 0x48, 0x84, 0xF5, 0x32, 0x16}} + IID_ICLRRuntimeInfo = windows.GUID{0xBD39D1D2, 0xBA2F, 0x486a, [8]byte{0x89, 0xB0, 0xB4, 0xB0, 0xCB, 0x46, 0x68, 0x91}} + + CLSID_CLRRuntimeHost = windows.GUID{0x90F1A06E, 0x7712, 0x4762, [8]byte{0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02}} + IID_ICLRRuntimeHost = windows.GUID{0x90F1A06C, 0x7712, 0x4762, [8]byte{0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02}} + IID_ICorRuntimeHost = windows.GUID{0xcb2f6722, 0xab3a, 0x11d2, [8]byte{0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e}} + CLSID_CorRuntimeHost = windows.GUID{0xcb2f6723, 0xab3a, 0x11d2, [8]byte{0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e}} + + IID_AppDomain = windows.GUID{0x5f696dc, 0x2b29, 0x3663, [8]uint8{0xad, 0x8b, 0xc4, 0x38, 0x9c, 0xf2, 0xa7, 0x13}} +) diff --git a/iclrmetahost.go b/iclrmetahost.go index edcd4d2..25c46c6 100644 --- a/iclrmetahost.go +++ b/iclrmetahost.go @@ -1,19 +1,20 @@ -//+build windows +// +build windows -package main +package clr import ( - "github.com/Microsoft/go-winio/pkg/guid" "syscall" "unsafe" + + "golang.org/x/sys/windows" ) var ( - modMSCoree = syscall.MustLoadDLL("mscoree.dll") - procCLRCreateInstance = modMSCoree.MustFindProc("CLRCreateInstance") + modMSCoree = syscall.NewLazyDLL("mscoree.dll") + procCLRCreateInstance = modMSCoree.NewProc("CLRCreateInstance") ) -func CLRCreateInstance(clsid, riid *guid.GUID, ppInterface *uintptr) uintptr { +func CLRCreateInstance(clsid, riid *windows.GUID, ppInterface *uintptr) uintptr { ret, _, _ := procCLRCreateInstance.Call( uintptr(unsafe.Pointer(clsid)), uintptr(unsafe.Pointer(riid)), @@ -21,14 +22,16 @@ func CLRCreateInstance(clsid, riid *guid.GUID, ppInterface *uintptr) uintptr { return ret } -// GetICLRMetaHost -//func GetICLRMetaHost() (*ICLRMetaHost, error) { -// var ppInterface uintptr -// if hr := getICLRMetaHostPtr(&ppInterface); hr != S_OK { -// return nil, fmt.Errorf("Could not get pointer to ICLRMetaHost. HRESULT: 0x%x", hr) -// } -// return newICLRMetaHost(ppInterface), nil -//} +func GetICLRMetaHost() (metahost *ICLRMetaHost, err error) { + var pMetaHost uintptr + hr := CLRCreateInstance(&CLSID_CLRMetaHost, &IID_ICLRMetaHost, &pMetaHost) + err = checkOK(hr, "CLRCreateInstance") + if err != nil { + return + } + metahost = NewICLRMetaHost(pMetaHost) + return +} //ICLRMetaHost Interface from metahost.h // https://stackoverflow.com/questions/37781676/how-to-use-com-component-object-model-in-golang @@ -49,7 +52,7 @@ type ICLRMetaHostVtbl struct { } // newICLRMetaHost takes a uintptr to ICLRMetahost, which must come from the syscall CLRCreateInstance -func newICLRMetaHost(ppv uintptr) *ICLRMetaHost { +func NewICLRMetaHost(ppv uintptr) *ICLRMetaHost { return (*ICLRMetaHost)(unsafe.Pointer(ppv)) } @@ -83,16 +86,16 @@ func (obj *ICLRMetaHost) EnumerateInstalledRuntimes(pInstalledRuntimes *uintptr) return ret } -func (obj *ICLRMetaHost) GetRuntime(pwzVersion *uint16, riid *guid.GUID, pRuntimeHost *uintptr) uintptr { - v4Ptr, err := syscall.UTF16PtrFromString("v4.0.30319") - if err != nil { - panic(err) - } +func (obj *ICLRMetaHost) GetRuntime(pwzVersion *uint16, riid *windows.GUID, pRuntimeHost *uintptr) uintptr { + // v4Ptr, err := syscall.UTF16PtrFromString("v4.0.30319") + // if err != nil { + // panic(err) + // } ret, _, _ := syscall.Syscall6( obj.vtbl.GetRuntime, 4, uintptr(unsafe.Pointer(obj)), - uintptr(unsafe.Pointer(v4Ptr)), + uintptr(unsafe.Pointer(pwzVersion)), uintptr(unsafe.Pointer(&IID_ICLRRuntimeInfo)), uintptr(unsafe.Pointer(pRuntimeHost)), 0, diff --git a/iclrruntimehost.go b/iclrruntimehost.go index d21140e..7be3d0c 100644 --- a/iclrruntimehost.go +++ b/iclrruntimehost.go @@ -1,4 +1,6 @@ -package main +// +build windows + +package clr import ( "syscall" @@ -24,7 +26,7 @@ type ICLRRuntimeHostVtbl struct { ExecuteInDefaultAppDomain uintptr } -func newICLRRuntimeHost(ppv uintptr) *ICLRRuntimeHost { +func NewICLRRuntimeHost(ppv uintptr) *ICLRRuntimeHost { return (*ICLRRuntimeHost)(unsafe.Pointer(ppv)) } @@ -74,7 +76,7 @@ func (obj *ICLRRuntimeHost) ExecuteInDefaultAppDomain(pwzAssemblyPath, pwzTypeNa return ret } -func (obj *ICLRRuntimeHost) GetCurrentappDomainID(pdwAppDomainId *uint16) uintptr { +func (obj *ICLRRuntimeHost) GetCurrentAppDomainID(pdwAppDomainId *uint16) uintptr { ret, _, _ := syscall.Syscall( obj.vtbl.GetCurrentAppDomainId, 2, diff --git a/iclrruntimeinfo.go b/iclrruntimeinfo.go index d85a477..d4d9372 100644 --- a/iclrruntimeinfo.go +++ b/iclrruntimeinfo.go @@ -1,7 +1,9 @@ -package main +// +build windows + +package clr import ( - "github.com/Microsoft/go-winio/pkg/guid" + "golang.org/x/sys/windows" "syscall" "unsafe" ) @@ -28,7 +30,7 @@ type ICLRRuntimeInfoVtbl struct { IsStarted uintptr } -func newICLRRuntimeInfo(ppv uintptr) *ICLRRuntimeInfo { +func NewICLRRuntimeInfo(ppv uintptr) *ICLRRuntimeInfo { return (*ICLRRuntimeInfo)(unsafe.Pointer(ppv)) } @@ -62,7 +64,7 @@ func (obj *ICLRRuntimeInfo) GetVersionString(pcchBuffer *uint16, pVersionstringS return ret } -func (obj *ICLRRuntimeInfo) GetInterface(rclsid *guid.GUID, riid *guid.GUID, ppUnk *uintptr) uintptr { +func (obj *ICLRRuntimeInfo) GetInterface(rclsid *windows.GUID, riid *windows.GUID, ppUnk *uintptr) uintptr { ret, _, _ := syscall.Syscall6( obj.vtbl.GetInterface, 4, diff --git a/icorruntimehost.go b/icorruntimehost.go index fa7c390..e72d0af 100644 --- a/icorruntimehost.go +++ b/icorruntimehost.go @@ -1,4 +1,6 @@ -package main +// +build windows + +package clr import ( "syscall" @@ -34,7 +36,7 @@ type ICORRuntimeHostVtbl struct { CurrentDomain uintptr } -func newICORRuntimeHost(ppv uintptr) *ICORRuntimeHost { +func NewICORRuntimeHost(ppv uintptr) *ICORRuntimeHost { return (*ICORRuntimeHost)(unsafe.Pointer(ppv)) } diff --git a/ienumunknown.go b/ienumunknown.go index ffa77cd..1e24228 100644 --- a/ienumunknown.go +++ b/ienumunknown.go @@ -1,4 +1,6 @@ -package main +// +build windows + +package clr import ( "syscall" @@ -19,7 +21,7 @@ type IEnumUnknownVtbl struct { Clone uintptr } -func newIEnumUnknown(ppv uintptr) *IEnumUnknown { +func NewIEnumUnknown(ppv uintptr) *IEnumUnknown { return (*IEnumUnknown)(unsafe.Pointer(ppv)) } diff --git a/iunknown.go b/iunknown.go index a3f0155..6016797 100644 --- a/iunknown.go +++ b/iunknown.go @@ -1,9 +1,12 @@ -package main +// +build windows + +package clr import ( - "github.com/Microsoft/go-winio/pkg/guid" "syscall" "unsafe" + + "golang.org/x/sys/windows" ) type IUnknown struct { @@ -16,11 +19,11 @@ type IUnknownVtbl struct { Release uintptr } -func newIUnknown(ppv uintptr) *IUnknown { +func NewIUnknown(ppv uintptr) *IUnknown { return (*IUnknown)(unsafe.Pointer(ppv)) } -func (obj *IUnknown) QueryInterface(riid *guid.GUID, ppvObject *uintptr) uintptr { +func (obj *IUnknown) QueryInterface(riid *windows.GUID, ppvObject *uintptr) uintptr { ret, _, _ := syscall.Syscall( obj.vtbl.QueryInterface, 3, diff --git a/main.go b/main.go index a951dc7..85aa966 100644 --- a/main.go +++ b/main.go @@ -1,273 +1 @@ -//+build windows -package main - -import ( - "fmt" - "github.com/Microsoft/go-winio/pkg/guid" - "io/ioutil" - "log" - "syscall" - "unsafe" - //ole "github.com/go-ole/go-ole" - //"github.com/go-ole/go-ole/oleutil" -) - -const S_OK = 0 -const E_POINTER = 0x80004003 - -var ( - CLSID_CLRMetaHost = guid.GUID{0x9280188d, 0xe8e, 0x4867, [8]byte{0xb3, 0xc, 0x7f, 0xa8, 0x38, 0x84, 0xe8, 0xde}} - IID_ICLRMetaHost = guid.GUID{0xD332DB9E, 0xB9B3, 0x4125, [8]byte{0x82, 0x07, 0xA1, 0x48, 0x84, 0xF5, 0x32, 0x16}} - IID_ICLRRuntimeInfo = guid.GUID{0xBD39D1D2, 0xBA2F, 0x486a, [8]byte{0x89, 0xB0, 0xB4, 0xB0, 0xCB, 0x46, 0x68, 0x91}} - - //EXTERN_GUID(CLSID_CLRRuntimeHost, 0x90F1A06E, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02); - CLSID_CLRRuntimeHost = guid.GUID{0x90F1A06E, 0x7712, 0x4762, [8]byte{0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02}} - - //EXTERN_GUID(IID_ICLRRuntimeHost, 0x90F1A06C, 0x7712, 0x4762, 0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02); - IID_ICLRRuntimeHost = guid.GUID{0x90F1A06C, 0x7712, 0x4762, [8]byte{0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02}} - - //EXTERN_GUID(IID_ICorRuntimeHost, 0xcb2f6722, 0xab3a, 0x11d2, 0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e); - IID_ICorRuntimeHost = guid.GUID{0xcb2f6722, 0xab3a, 0x11d2, [8]byte{0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e}} - - //EXTERN_GUID(CLSID_CorRuntimeHost, 0xcb2f6723, 0xab3a, 0x11d2, 0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e); - CLSID_CorRuntimeHost = guid.GUID{0xcb2f6723, 0xab3a, 0x11d2, [8]byte{0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e}} - - //https://docs.microsoft.com/en-us/dotnet/api/system._appdomain?view=netframework-4.8 - // _AppDomain Interface GUID - IID_AppDomain = guid.GUID{0x5f696dc, 0x2b29, 0x3663, [8]uint8{0xad, 0x8b, 0xc4, 0x38, 0x9c, 0xf2, 0xa7, 0x13}} -) - -func must(err error) { - if err != nil { - log.Fatal(err) - } -} - -func checkOK(hr uintptr) { - if hr == S_OK { - return - } else if hr == E_POINTER { - log.Fatalf("Error getting pointer: 0x%x\n", hr) - } else { - log.Fatalf("Unknown return result: 0x%x\n", hr) - } -} - -func main() { - var pMetaHost uintptr - var metaHost *ICLRMetaHost - hr := CLRCreateInstance(&CLSID_CLRMetaHost, &IID_ICLRMetaHost, &pMetaHost) - checkOK(hr) - metaHost = newICLRMetaHost(pMetaHost) - - //metaHost, err := GetICLRMetaHost() - //must(err) - //var pInstalledRuntimes uintptr - // - //hr = metaHost.EnumerateInstalledRuntimes(&pInstalledRuntimes) - // - //checkOK(hr) - //installedRuntimes := newIEnumUnknown(pInstalledRuntimes) - // - //var pRuntimeInfo uintptr - //var fetched = uint32(0) - //var versionString string - //versionStringBytes := make([]uint16, 20) - //versionStringSize := uint32(20) - //var runtimeInfo *ICLRRuntimeInfo - //fmt.Println("[+] Enumerating installed runtimes...") - //for { - // hr = installedRuntimes.Next(1, &pRuntimeInfo, &fetched ) - // if hr != S_OK {break} - // runtimeInfo = newICLRRuntimeInfo(pRuntimeInfo) - // if ret := runtimeInfo.GetVersionString(&versionStringBytes[0], &versionStringSize); ret != S_OK { - // log.Fatalf("[+] Error getting version string: 0x%x", ret) - // } - // versionString = syscall.UTF16ToString(versionStringBytes) - // fmt.Printf("\t[+] Found: %s\n", versionString) - //} - //fmt.Printf("[+] Using latest supported framework: %s\n", versionString) - - //So I'll use the older API for now - var pRuntimeInfo uintptr - var runtimeInfo *ICLRRuntimeInfo - versionString := "v4.0.30319" - pwzVersion, _ := syscall.UTF16PtrFromString(versionString) - hr = metaHost.GetRuntime(pwzVersion, &IID_ICLRRuntimeInfo, &pRuntimeInfo) - checkOK(hr) - runtimeInfo = newICLRRuntimeInfo(pRuntimeInfo) - fmt.Println("[+] Got RuntimeHost") - var isLoadable bool - hr = runtimeInfo.IsLoadable(&isLoadable) - checkOK(hr) - fmt.Printf("[+] isLoadable: %t\n", isLoadable) - - if !isLoadable { - log.Fatal("[!] IsLoadable returned false. Won't load CLR") - } - - fmt.Println("[+] BindAsLegacyV2Runtime...") - hr = runtimeInfo.BindAsLegacyV2Runtime() - checkOK(hr) - - var pRuntimeHost uintptr - // This is the "new" API, but I can't figure out how to load assemblies from memory in unmanaged code :/ - //hr = runtimeInfo.GetInterface(&CLSID_CLRRuntimeHost, &IID_ICLRRuntimeHost, &pRuntimeHost) - - // So I'll use the older API for now - //pwzVersion, _ := syscall.UTF16PtrFromString(versionString) - //hr = metaHost.GetRuntime(pwzVersion, &IID_ICLRRuntimeInfo, &pRuntimeInfo) - //checkOK(hr) - //runtimeInfo = newICLRRuntimeInfo(pRuntimeInfo) - fmt.Println("[+] Getting interface...") - hr = runtimeInfo.GetInterface(&CLSID_CorRuntimeHost, &IID_ICorRuntimeHost, &pRuntimeHost) - checkOK(hr) - runTimeHost := newICORRuntimeHost(pRuntimeHost) - fmt.Println("[+] Got interface. Loading CLR...") - hr = runTimeHost.Start() - checkOK(hr) - fmt.Println("[+] Loaded CLR into this process") - - fmt.Println("[+] Getting Default AppDomain") - - //ole.CoInitialize(0) - //var pIUnknown uintptr - //https://docs.microsoft.com/en-us/dotnet/api/system._appdomain?view=netframework-4.8 - //unknown, err := oleutil.CreateObject("{05F696DC-2B29-3663-AD8B-C4389CF2A713}") - //if err != nil { - // fmt.Printf("createobject failed: %s", err) - //} - //must(err) - //iid, _ := oleutil.ClassIDFrom("{05F696DC-2B29-3663-AD8B-C4389CF2A713}") - //oleappdomain, _ := unknown.QueryInterface(ole.IID_IDispatch) - - var pAppDomain uintptr - var pIUnknown uintptr - hr = runTimeHost.GetDefaultDomain(&pIUnknown) - checkOK(hr) - iu := newIUnknown(pIUnknown) - //appDomainGuid, err := guid.FromString("05F696DC-2B29-3663-AD8B-C4389CF2A713") - //must(err) - hr = iu.QueryInterface(&IID_AppDomain, &pAppDomain) - appDomain := newAppDomain(pAppDomain) - fmt.Println("[+] Got default appdomain") - - //box := packr.New("EXEs", "./static") - //testEXEBytes, err := box.Find("TestEXE.exe") - //must(err) - testEXEBytes, err := ioutil.ReadFile("./static/TestEXE.exe") - must(err) - //testEXEBytes := []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41} - fmt.Printf("[+] Loaded %d bytes from TestExe.exe\n", len(testEXEBytes)) - - safeArray, err := createSafeArray(testEXEBytes) - must(err) - - var pAssembly uintptr - hr = appDomain.Load_3(uintptr(unsafe.Pointer(&safeArray)), &pAssembly) - checkOK(hr) - - fmt.Printf("[+] Assembly loaded into memory at 0x%08x\n", pAssembly) - assembly := newAssembly(pAssembly) - var pEntryPointInfo uintptr - fmt.Printf("entrypoint: 0x%08x\n", pEntryPointInfo) - hr = assembly.GetEntryPoint(&pEntryPointInfo) - checkOK(hr) - fmt.Printf("entrypoint: 0x%08x\n", pEntryPointInfo) - fmt.Println("[+] Found assembly entrypoint") - - var pRetCode uintptr - nullVariant := Variant{ - VT: 1, - Val: 0, - } - - - //printRawData(uintptr(unsafe.Pointer(&nullVariant)), unsafe.Sizeof(nullVariant)) - methodInfo := newMethodInfo(pEntryPointInfo) - //methodGUID, err := guid.FromString("ffcc1b5d-ecb8-38dd-9b01-3dc8abc2aa5f") - //must(err) - //iu = newIUnknown(pEntryPointInfo) - //hr = iu.QueryInterface(&methodGUID, &pRetCode) - //checkOK(hr) - //fmt.Printf("[+] MethodInfo at 0x%08x\n", pRetCode) - //// - ////methodInfo := newMethodInfo(pRetCode) - //fmt.Println("testing gettype") - //hr = methodInfo.GetType(&pRetCode) - //checkOK(hr) - // - - fmt.Println("[+] Calling default entry point with no args") - hr = methodInfo.Invoke_3( - uintptr(unsafe.Pointer(&nullVariant)), - uintptr(0), - &pRetCode) - checkOK(hr) - - //var appDomainID uint16 - //runTimeHost.GetCurrentappDomainID(&appDomainID) - //checkOK(hr) - //fmt.Printf("[+] Current AppDomain ID: %d\n", appDomainID) - // - //fmt.Println("[+] Executing assembly...") - //pDLLPath, _ := syscall.UTF16PtrFromString("TestDLL.dll") - //pTypeName, _ := syscall.UTF16PtrFromString("TestDLL.HelloWorld") - //pMethodName, _ := syscall.UTF16PtrFromString("SayHello") - //pArgument, _ := syscall.UTF16PtrFromString("foobar") - //var pReturnVal *uint16 - //hr = runTimeHost.ExecuteInDefaultAppDomain( - // pDLLPath, - // pTypeName, - // pMethodName, - // pArgument, - // pReturnVal) - // - //checkOK(hr) - //fmt.Printf("[+] Assembly returned: 0x%x\n", pReturnVal) - - //if hr == S_OK { - // log.Println("[+] Enumerating runtimes") - //} else if hr == E_POINTER { - // log.Fatal("[!] Couldn't get pointer to enumerate runtimes") - //} else { - // log.Fatalf("[!] Unknown error: %x\n", hr) - //} - // - // - // - //var pRuntimeInfo uintptr - // - //hr = metaHost.GetRuntime("v4.0.30319", pRuntimeInfo) - //if hr == S_OK { - // log.Println("[+] Got Runtime!") - //} else if hr == E_POINTER { - // log.Fatalf("[!] Error getting runtime pointer: %x\n", hr) - //} else { - // log.Fatalf("[!] Unknown error: %x\n", hr) - //} - -} - -func printRawData(ptr uintptr, size uintptr) { - fmt.Printf("Printing ptr %016x size %d\n", ptr, size) - i := ptr - var offset uintptr - for i < ptr+size { - if offset%16 == 0 { - fmt.Printf("%016x : ", i) - } - - fmt.Printf("%02x", *(*byte)(unsafe.Pointer(i))) - - i++ - offset++ - if offset%16 == 0 || offset == size { - fmt.Print("\n") - } else if offset%8 == 0 { - fmt.Print(" ") - } else { - fmt.Print(" ") - } - } -} +package clr \ No newline at end of file diff --git a/methodinfo.go b/methodinfo.go index f827f3f..527f48b 100644 --- a/methodinfo.go +++ b/methodinfo.go @@ -1,9 +1,12 @@ -package main +// +build windows + +package clr import ( - "github.com/Microsoft/go-winio/pkg/guid" "syscall" "unsafe" + + "github.com/Microsoft/go-winio/pkg/guid" ) // from mscorlib.tlh @@ -56,7 +59,7 @@ type MethodInfoVtbl struct { GetBaseDefinition uintptr } -func newMethodInfo(ppv uintptr) *MethodInfo { +func NewMethodInfo(ppv uintptr) *MethodInfo { return (*MethodInfo)(unsafe.Pointer(ppv)) } @@ -100,16 +103,16 @@ func (obj *MethodInfo) GetType(pRetVal *uintptr) uintptr { return ret } -func (obj *MethodInfo) Invoke_3(variantObj, parameters uintptr, pRetVal *uintptr) uintptr { +func (obj *MethodInfo) Invoke_3(variantObj Variant, parameters uintptr, pRetVal *uintptr) uintptr { ret, _, _ := syscall.Syscall6( obj.vtbl.Invoke_3, 4, uintptr(unsafe.Pointer(obj)), - uintptr(unsafe.Pointer(variantObj)), + uintptr(unsafe.Pointer(&variantObj)), 0, uintptr(unsafe.Pointer(pRetVal)), 0, 0, - ) + ) return ret } diff --git a/safearray.go b/safearray.go index ee67f7b..ab6c97b 100644 --- a/safearray.go +++ b/safearray.go @@ -1,7 +1,10 @@ -package main +// +build windows + +package clr import ( "fmt" + "runtime" "syscall" "unsafe" ) @@ -15,8 +18,6 @@ import ( // from OAld.h - - type SafeArray struct { cDims uint16 fFeatures uint16 @@ -31,7 +32,7 @@ type SafeArrayBound struct { lLbound int32 } -func createSafeArray(rawBytes []byte) (SafeArray, error) { +func CreateSafeArray(rawBytes []byte) (SafeArray, error) { modOleAuto, err := syscall.LoadDLL("OleAut32.dll") if err != nil { return SafeArray{}, err @@ -57,6 +58,7 @@ func createSafeArray(rawBytes []byte) (SafeArray, error) { } sa := (*SafeArray)(unsafe.Pointer(ret)) + runtime.KeepAlive(sa) // now we need to use RtlCopyMemory to copy our bytes to the SafeArray modNtDll, err := syscall.LoadDLL("ntdll.dll") if err != nil { @@ -75,9 +77,6 @@ func createSafeArray(rawBytes []byte) (SafeArray, error) { return SafeArray{}, err } - //printRawData(uintptr(unsafe.Pointer(&sa)), uintptr(32)) - return *sa, nil - //return uintptr(unsafe.Pointer(&sa)), nil } diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..3139c44 --- /dev/null +++ b/utils.go @@ -0,0 +1,24 @@ +// +build windows + +package clr + +import ( + "fmt" + "log" +) + +const S_OK = 0x0 + +func checkOK(hr uintptr, caller string) error { + if hr != S_OK { + return fmt.Errorf("%s returned 0x%08x", caller, hr) + } else { + return nil + } +} + +func must(err error) { + if err != nil { + log.Fatal(err) + } +} diff --git a/variant.go b/variant.go index 365a9be..1ff666b 100644 --- a/variant.go +++ b/variant.go @@ -1,4 +1,6 @@ -package main +// +build windows + +package clr import "unsafe" @@ -9,10 +11,10 @@ type Variant struct { wReserved1 uint16 wReserved2 uint16 wReserved3 uint16 - Val int64 + Val uintptr _ [8]byte } -func newVariant(ppv uintptr) *Variant { +func variantFromPtr(ppv uintptr) *Variant { return (*Variant)(unsafe.Pointer(ppv)) }