Skip to content

Commit

Permalink
Merge pull request #1 from ropnop/master
Browse files Browse the repository at this point in the history
Update to source repo
  • Loading branch information
Ne0nd0g authored Feb 26, 2021
2 parents 30039fd + a9381fb commit b99b1aa
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 56 deletions.
3 changes: 2 additions & 1 deletion appdomain.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
package clr

import (
"golang.org/x/sys/windows"
"syscall"
"unsafe"

"golang.org/x/sys/windows"
)

type AppDomain struct {
Expand Down
9 changes: 5 additions & 4 deletions examples/CLRWrapper/CLRWrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
package main

import (
clr "github.com/ropnop/go-clr"
"log"
"fmt"
"io/ioutil"
"log"
"runtime"

clr "github.com/ropnop/go-clr"
)

func main() {
fmt.Println("[+] Loading DLL from Disk")
ret, err := clr.ExecuteDLLFromDisk(
"v4",
"TestDLL.dll",
"TestDLL.HelloWorld",
"SayHello",
Expand All @@ -22,15 +24,14 @@ func main() {
}
fmt.Printf("[+] DLL Return Code: %d\n", ret)


fmt.Println("[+] Executing EXE from memory")
exebytes, err := ioutil.ReadFile("helloworld.exe")
if err != nil {
log.Fatal(err)
}
runtime.KeepAlive(exebytes)

ret2, err := clr.ExecuteByteArray(exebytes)
ret2, err := clr.ExecuteByteArray("v2", exebytes, []string{"test", "test2"})
if err != nil {
log.Fatal(err)
}
Expand Down
75 changes: 61 additions & 14 deletions go-clr.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package clr

import (
"fmt"
"runtime"
"strings"
"syscall"
"unsafe"
Expand Down Expand Up @@ -48,10 +47,13 @@ func GetInstalledRuntimes(metahost *ICLRMetaHost) ([]string, error) {
}

// ExecuteDLLFromDisk 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. It takes in the DLLPath, TypeName, MethodName and Argument to use
// as strings. It returns the return code from the assembly
func ExecuteDLLFromDisk(dllpath, typeName, methodName, argument string) (retCode int16, err error) {
// and execute a DLL on disk in the default app domain. It takes in the target runtime, DLLPath, TypeName, MethodName
// and Argument to use as strings. It returns the return code from the assembly
func ExecuteDLLFromDisk(targetRuntime, dllpath, typeName, methodName, argument string) (retCode int16, err error) {
retCode = -1
if targetRuntime == "" {
targetRuntime = "v4"
}
metahost, err := GetICLRMetaHost()
if err != nil {
return
Expand All @@ -63,7 +65,7 @@ func ExecuteDLLFromDisk(dllpath, typeName, methodName, argument string) (retCode
}
var latestRuntime string
for _, r := range runtimes {
if strings.Contains(r, "v4") {
if strings.Contains(r, targetRuntime) {
latestRuntime = r
break
} else {
Expand Down Expand Up @@ -105,11 +107,15 @@ func ExecuteDLLFromDisk(dllpath, typeName, methodName, argument string) (retCode

}

// ExecuteByteArray is a wrapper function that will automatically load the latest supported framework into the current
// process using the legacy APIs, then load and execute an executable from memory. It takes in a byte array of the
// executable to load and run and returns the return code. It currently does not support any arguments on the entry point
func ExecuteByteArray(rawBytes []byte) (retCode int32, err error) {
// ExecuteByteArray is a wrapper function that will automatically loads the supplied target framework into the current
// process using the legacy APIs, then load and execute an executable from memory. If no targetRuntime is specified, it
// will default to latest. It takes in a byte array of the executable to load and run and returns the return code.
// You can supply an array of strings as command line arguments.
func ExecuteByteArray(targetRuntime string, rawBytes []byte, params []string) (retCode int32, err error) {
retCode = -1
if targetRuntime == "" {
targetRuntime = "v4"
}
metahost, err := GetICLRMetaHost()
if err != nil {
return
Expand All @@ -121,7 +127,7 @@ func ExecuteByteArray(rawBytes []byte) (retCode int32, err error) {
}
var latestRuntime string
for _, r := range runtimes {
if strings.Contains(r, "v4") {
if strings.Contains(r, targetRuntime) {
latestRuntime = r
break
} else {
Expand Down Expand Up @@ -149,13 +155,12 @@ func ExecuteByteArray(rawBytes []byte) (retCode int32, err error) {
if err != nil {
return
}
safeArray, err := CreateSafeArray(rawBytes)
safeArrayPtr, err := CreateSafeArray(rawBytes)
if err != nil {
return
}
runtime.KeepAlive(&safeArray)
var pAssembly uintptr
hr = appDomain.Load_3(uintptr(unsafe.Pointer(&safeArray)), &pAssembly)
hr = appDomain.Load_3(uintptr(safeArrayPtr), &pAssembly)
err = checkOK(hr, "appDomain.Load_3")
if err != nil {
return
Expand All @@ -168,14 +173,28 @@ func ExecuteByteArray(rawBytes []byte) (retCode int32, err error) {
return
}
methodInfo := NewMethodInfoFromPtr(pEntryPointInfo)

var methodSignaturePtr, paramPtr uintptr
err = methodInfo.GetString(&methodSignaturePtr)
if err != nil {
return
}
methodSignature := readUnicodeStr(unsafe.Pointer(methodSignaturePtr))

if expectsParams(methodSignature) {
if paramPtr, err = PrepareParameters(params); err != nil {
return
}
}

var pRetCode uintptr
nullVariant := Variant{
VT: 1,
Val: uintptr(0),
}
hr = methodInfo.Invoke_3(
nullVariant,
uintptr(0),
paramPtr,
&pRetCode)
err = checkOK(hr, "methodInfo.Invoke_3")
if err != nil {
Expand All @@ -188,3 +207,31 @@ func ExecuteByteArray(rawBytes []byte) (retCode int32, err error) {
return int32(pRetCode), nil

}

// PrepareParameters creates a safe array of strings (arguments) nested inside a Variant object, which is itself
// appended to the final safe array
func PrepareParameters(params []string) (uintptr, error) {
listStrSafeArrayPtr, err := CreateEmptySafeArray(0x0008, len(params)) // VT_BSTR
if err != nil {
return 0, err
}
for i, p := range params {
bstr, _ := SysAllocString(p)
SafeArrayPutElement(listStrSafeArrayPtr, bstr, i)
}

paramVariant := Variant{
VT: 0x0008 | 0x2000, // VT_BSTR | VT_ARRAY
Val: uintptr(listStrSafeArrayPtr),
}

paramsSafeArrayPtr, err := CreateEmptySafeArray(0x000C, 1) // VT_VARIANT
if err != nil {
return 0, err
}
err = SafeArrayPutElement(paramsSafeArrayPtr, unsafe.Pointer(&paramVariant), 0)
if err != nil {
return 0, err
}
return uintptr(paramsSafeArrayPtr), nil
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/ropnop/go-clr

go 1.13

require golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
require (
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
golang.org/x/text v0.3.3
)
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
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=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
2 changes: 2 additions & 0 deletions guids.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// +build windows

package clr

import (
Expand Down
17 changes: 15 additions & 2 deletions methodinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
package clr

import (
"golang.org/x/sys/windows"
"syscall"
"unsafe"

"golang.org/x/sys/windows"
)

// from mscorlib.tlh
Expand Down Expand Up @@ -108,10 +109,22 @@ func (obj *MethodInfo) Invoke_3(variantObj Variant, parameters uintptr, pRetVal
4,
uintptr(unsafe.Pointer(obj)),
uintptr(unsafe.Pointer(&variantObj)),
0,
parameters,
uintptr(unsafe.Pointer(pRetVal)),
0,
0,
)
return ret
}

// GetString returns a string version of the method's signature
func (obj *MethodInfo) GetString(addr *uintptr) error {
ret, _, _ := syscall.Syscall(
obj.vtbl.get_ToString,
2,
uintptr(unsafe.Pointer(obj)),
uintptr(unsafe.Pointer(addr)),
0,
)
return checkOK(ret, "get_ToString")
}
91 changes: 57 additions & 34 deletions safearray.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
package clr

import (
"fmt"
"runtime"
"syscall"
"unsafe"
)
Expand Down Expand Up @@ -32,54 +30,79 @@ type SafeArrayBound struct {
lLbound int32
}

// CreateSafeArray is a wrapper function that takes in a Go byte array and creates a SafeArray by making two syscalls
// and copying raw memory into the correct spot.
func CreateSafeArray(rawBytes []byte) (SafeArray, error) {
modOleAuto, err := syscall.LoadDLL("OleAut32.dll")
// CreateSafeArray is a wrapper function that takes in a Go byte array and creates a SafeArray containing unsigned bytes
// by making two syscalls and copying raw memory into the correct spot.
func CreateSafeArray(rawBytes []byte) (unsafe.Pointer, error) {

saPtr, err := CreateEmptySafeArray(0x11, len(rawBytes)) // VT_UI1
if err != nil {
return SafeArray{}, err
return nil, err
}
procSafeArrayCreate, err := modOleAuto.FindProc("SafeArrayCreate")
if err != nil {
return SafeArray{}, err
// now we need to use RtlCopyMemory to copy our bytes to the SafeArray
modNtDll := syscall.MustLoadDLL("ntdll.dll")
procRtlCopyMemory := modNtDll.MustFindProc("RtlCopyMemory")
sa := (*SafeArray)(saPtr)
_, _, err = procRtlCopyMemory.Call(
sa.pvData,
uintptr(unsafe.Pointer(&rawBytes[0])),
uintptr(len(rawBytes)))
if err != syscall.Errno(0) {
return nil, err
}
return saPtr, nil

}

// CreateEmptySafeArray is a wrapper function that takes an array type and a size and creates a safe array with corresponding
// properties. It returns a pointer to that empty array.
func CreateEmptySafeArray(arrayType int, size int) (unsafe.Pointer, error) {
modOleAuto := syscall.MustLoadDLL("OleAut32.dll")
procSafeArrayCreate := modOleAuto.MustFindProc("SafeArrayCreate")

size := len(rawBytes)
sab := SafeArrayBound{
cElements: uint32(size),
lLbound: 0,
}
runtime.KeepAlive(sab)
vt := uint16(0x11) // VT_UI1
ret, _, _ := procSafeArrayCreate.Call(
vt := uint16(arrayType)
ret, _, err := procSafeArrayCreate.Call(
uintptr(vt),
uintptr(1),
uintptr(unsafe.Pointer(&sab)))

if ret == 0 {
return SafeArray{}, fmt.Errorf("Error creating SafeArray")
if err != syscall.Errno(0) {
return nil, err
}

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 {
return SafeArray{}, err
}
procRtlCopyMemory, err := modNtDll.FindProc("RtlCopyMemory")
if err != nil {
return SafeArray{}, err
}
return unsafe.Pointer(ret), nil

ret, _, err = procRtlCopyMemory.Call(
sa.pvData,
uintptr(unsafe.Pointer(&rawBytes[0])),
uintptr(size))
}

// SysAllocString converts a Go string to a BTSR string, that is a unicode string prefixed with its length.
// It returns a pointer to the string's content.
func SysAllocString(str string) (unsafe.Pointer, error) {
modOleAuto := syscall.MustLoadDLL("OleAut32.dll")
sysAllocString := modOleAuto.MustFindProc("SysAllocString")
input := utf16Le(str)
ret, _, err := sysAllocString.Call(
uintptr(unsafe.Pointer(&input[0])),
)
if err != syscall.Errno(0) {
return SafeArray{}, err
return nil, err
}
return unsafe.Pointer(ret), nil
}

return *sa, nil

// SafeArrayPutElement pushes an element to the safe array at a given index
func SafeArrayPutElement(array, btsr unsafe.Pointer, index int) (err error) {
modOleAuto := syscall.MustLoadDLL("OleAut32.dll")
safeArrayPutElement := modOleAuto.MustFindProc("SafeArrayPutElement")
_, _, err = safeArrayPutElement.Call(
uintptr(array),
uintptr(unsafe.Pointer(&index)),
uintptr(btsr),
)
if err != syscall.Errno(0) {
return err
}
return nil
}
Loading

0 comments on commit b99b1aa

Please sign in to comment.