├── README.md ├── go.mod ├── go.sum ├── load └── load.go ├── main.go ├── preExit.cs └── testbin ├── 1.cs ├── hello.exe └── hello111.exe /README.md: -------------------------------------------------------------------------------- 1 | # Doge-CLRLoad 2 | load assembly executable file in memory 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/timwhitez/Doge-CLRLoad 2 | 3 | go 1.19 4 | 5 | require github.com/Ne0nd0g/go-clr v1.0.2 6 | 7 | require ( 8 | golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect 9 | golang.org/x/text v0.3.7 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Ne0nd0g/go-clr v1.0.2 h1:jVD7iJyXaS3KhPiKetUnUAuLxgDlNSvYmPfMFK8E3VY= 2 | github.com/Ne0nd0g/go-clr v1.0.2/go.mod h1:TKYSQ/5xT25EvBUttAlUrzpR8yHuI0qTRK495I5xG/I= 3 | golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY= 4 | golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 5 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 6 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 7 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 8 | -------------------------------------------------------------------------------- /load/load.go: -------------------------------------------------------------------------------- 1 | package load 2 | 3 | import ( 4 | "crypto/sha256" 5 | "errors" 6 | "fmt" 7 | clr "github.com/Ne0nd0g/go-clr" 8 | "log" 9 | "sync" 10 | ) 11 | 12 | var ( 13 | clrInstance *CLRInstance 14 | assemblies []*assembly 15 | ) 16 | 17 | type assembly struct { 18 | methodInfo *clr.MethodInfo 19 | hash [32]byte 20 | } 21 | 22 | type CLRInstance struct { 23 | runtimeHost *clr.ICORRuntimeHost 24 | sync.Mutex 25 | } 26 | 27 | func init() { 28 | clrInstance = &CLRInstance{} 29 | assemblies = make([]*assembly, 0) 30 | } 31 | 32 | func (c *CLRInstance) GetRuntimeHost(runtime string, debug bool) *clr.ICORRuntimeHost { 33 | c.Lock() 34 | defer c.Unlock() 35 | if c.runtimeHost == nil { 36 | if debug { 37 | log.Printf("Initializing CLR runtime host") 38 | } 39 | c.runtimeHost, _ = clr.LoadCLR(runtime) 40 | err := clr.RedirectStdoutStderr() 41 | if err != nil { 42 | if debug { 43 | log.Printf("could not redirect stdout/stderr: %v\n", err) 44 | } 45 | } 46 | } 47 | return c.runtimeHost 48 | } 49 | 50 | func CleanCLR(debug bool) { 51 | runtimeHost := clrInstance.runtimeHost 52 | appDomain, err := clr.GetAppDomain(runtimeHost) 53 | if err != nil { 54 | appDomain = nil 55 | } 56 | 57 | metaHost, err := clr.CLRCreateInstance(clr.CLSID_CLRMetaHost, clr.IID_ICLRMetaHost) 58 | if err != nil { 59 | metaHost = nil 60 | } 61 | 62 | if appDomain != nil { 63 | if debug { 64 | log.Printf("release appDomain\n") 65 | } 66 | appDomain.Release() 67 | } 68 | if runtimeHost != nil { 69 | if debug { 70 | log.Printf("release runtimeHost\n") 71 | } 72 | runtimeHost.Release() 73 | } 74 | 75 | if assemblies != nil { 76 | for i, _ := range assemblies { 77 | assemblies[i].methodInfo.Release() 78 | } 79 | } 80 | 81 | if metaHost != nil { 82 | if debug { 83 | log.Printf("release metaHost\n") 84 | } 85 | metaHost.Release() 86 | } 87 | 88 | if debug { 89 | log.Printf("release assemblies\n") 90 | } 91 | assemblies = make([]*assembly, 0) 92 | } 93 | 94 | func addAssembly(methodInfo *clr.MethodInfo, data []byte) { 95 | asmHash := sha256.Sum256(data) 96 | asm := &assembly{methodInfo: methodInfo, hash: asmHash} 97 | assemblies = append(assemblies, asm) 98 | } 99 | 100 | func getAssembly(data []byte) *assembly { 101 | asmHash := sha256.Sum256(data) 102 | for _, asm := range assemblies { 103 | if asm.hash == asmHash { 104 | return asm 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | func LoadBin(data []byte, assemblyArgs []string, runtime string, debug bool) (string, error) { 111 | var ( 112 | methodInfo *clr.MethodInfo 113 | err error 114 | ) 115 | 116 | rtHost := clrInstance.GetRuntimeHost(runtime, debug) 117 | if rtHost == nil { 118 | return "", errors.New("Could not load CLR runtime host") 119 | } 120 | 121 | if asm := getAssembly(data); asm != nil { 122 | methodInfo = asm.methodInfo 123 | } else { 124 | methodInfo, err = clr.LoadAssembly(rtHost, data) 125 | if err != nil { 126 | if debug { 127 | log.Printf("could not load assembly: %v\n", err) 128 | } 129 | return "", err 130 | } 131 | addAssembly(methodInfo, data) 132 | } 133 | if len(assemblyArgs) == 1 && assemblyArgs[0] == "" { 134 | // for methods like Main(String[] args), if we pass an empty string slice 135 | // the clr loader will not pass the argument and look for a method with 136 | // no arguments, which won't work 137 | assemblyArgs = []string{" "} 138 | } 139 | if debug { 140 | log.Printf("Assembly loaded, methodInfo: %+v\n", methodInfo) 141 | log.Printf("Calling assembly with args: %+v\n", assemblyArgs) 142 | } 143 | stdout, stderr := clr.InvokeAssembly(methodInfo, assemblyArgs) 144 | if debug { 145 | log.Printf("Got output: %s\n%s\n", stdout, stderr) 146 | } 147 | return fmt.Sprintf("%s\n%s", stdout, stderr), nil 148 | } 149 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/timwhitez/Doge-CLRLoad/load" 6 | "io/ioutil" 7 | "log" 8 | ) 9 | 10 | func main() { 11 | debug := true 12 | 13 | helloWorldPath := `testbin/hello.exe` 14 | helloWorld111Path := `testbin/hello111.exe` 15 | // Get hello 16 | helloBytes, err := ioutil.ReadFile(helloWorldPath) 17 | if err != nil { 18 | log.Fatal(fmt.Sprintf("there was an error reading in the HelloWorld file from %s:\n%s", helloWorldPath, err)) 19 | } 20 | if debug { 21 | fmt.Printf("[-] Ingested %d assembly bytes\n", len(helloBytes)) 22 | } 23 | 24 | // Get hello111 25 | hello11Bytes, err := ioutil.ReadFile(helloWorld111Path) 26 | if err != nil { 27 | log.Fatal(fmt.Sprintf("there was an error reading in the hello111 file from %s:\n%s", helloWorld111Path, err)) 28 | } 29 | 30 | if debug { 31 | fmt.Printf("[-] Ingested %d assembly bytes\n", len(hello11Bytes)) 32 | } 33 | 34 | // Load assembly into default AppDomain 35 | stdout, e := load.LoadBin(helloBytes, []string{"arg111"}, "v4", debug) 36 | if e != nil { 37 | fmt.Printf("[DEBUG] Returned STDOUT/STDERR: \n%s\n", stdout) 38 | } 39 | 40 | // Execute assembly from default AppDomain x2 41 | if debug { 42 | fmt.Println("[-] Executing the helloworld x2...") 43 | } 44 | 45 | stdout, e = load.LoadBin(helloBytes, []string{""}, "v4", debug) 46 | if e != nil { 47 | fmt.Printf("[DEBUG] Returned STDOUT/STDERR: \n%s\n", stdout) 48 | } 49 | 50 | // Load assembly into default AppDomain 51 | stdout, e = load.LoadBin(hello11Bytes, []string{"11111"}, "v4", debug) 52 | if e != nil { 53 | fmt.Printf("[DEBUG] Returned STDOUT/STDERR: \n%s\n", stdout) 54 | } 55 | 56 | load.CleanCLR(debug) 57 | } 58 | -------------------------------------------------------------------------------- /preExit.cs: -------------------------------------------------------------------------------- 1 | // see https://www.mdsec.co.uk/2020/08/massaging-your-clr-preventing-environment-exit-in-in-process-net-assemblies/ 2 | using System; 3 | using System.Reflection; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Runtime.InteropServices; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace test 10 | { 11 | public class Program 12 | { 13 | public struct MEMORY_BASIC_INFORMATION 14 | { 15 | public IntPtr BaseAddress; 16 | public IntPtr AllocationBase; 17 | public AllocationProtectEnum AllocationProtect; 18 | public IntPtr RegionSize; 19 | public StateEnum State; 20 | public AllocationProtectEnum Protect; 21 | public TypeEnum Type; 22 | } 23 | 24 | public enum AllocationProtectEnum : uint 25 | { 26 | PAGE_EXECUTE = 0x00000010, 27 | PAGE_EXECUTE_READ = 0x00000020, 28 | PAGE_EXECUTE_READWRITE = 0x00000040, 29 | PAGE_EXECUTE_WRITECOPY = 0x00000080, 30 | PAGE_NOACCESS = 0x00000001, 31 | PAGE_READONLY = 0x00000002, 32 | PAGE_READWRITE = 0x00000004, 33 | PAGE_WRITECOPY = 0x00000008, 34 | PAGE_GUARD = 0x00000100, 35 | PAGE_NOCACHE = 0x00000200, 36 | PAGE_WRITECOMBINE = 0x00000400 37 | } 38 | 39 | public enum StateEnum : uint 40 | { 41 | MEM_COMMIT = 0x1000, 42 | MEM_FREE = 0x10000, 43 | MEM_RESERVE = 0x2000 44 | } 45 | 46 | public enum TypeEnum : uint 47 | { 48 | MEM_IMAGE = 0x1000000, 49 | MEM_MAPPED = 0x40000, 50 | MEM_PRIVATE = 0x20000 51 | } 52 | 53 | [DllImport("kernel32.dll")] 54 | static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength); 55 | 56 | [DllImport("kernel32.dll")] 57 | static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); 58 | 59 | public static void Main(string[] args) 60 | { 61 | var methods = new List(typeof(Environment).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); 62 | var exitMethod = methods.Find((MethodInfo mi) => mi.Name == "Exit"); 63 | 64 | RuntimeHelpers.PrepareMethod(exitMethod.MethodHandle); 65 | var exitMethodPtr = exitMethod.MethodHandle.GetFunctionPointer(); 66 | 67 | unsafe 68 | { 69 | IntPtr target = exitMethod.MethodHandle.GetFunctionPointer(); 70 | 71 | MEMORY_BASIC_INFORMATION mbi; 72 | 73 | if (VirtualQueryEx((IntPtr)(-1), target, out mbi, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) != 0) 74 | { 75 | if (mbi.Protect == AllocationProtectEnum.PAGE_EXECUTE_READ) 76 | { 77 | // seems to be executable code 78 | uint flOldProtect; 79 | 80 | if (VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (IntPtr)1, (uint)AllocationProtectEnum.PAGE_EXECUTE_READWRITE, out flOldProtect)) 81 | { 82 | *(byte*)target = 0xc3; // ret 83 | 84 | VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (IntPtr)1, flOldProtect, out flOldProtect); 85 | } 86 | } 87 | } 88 | } 89 | // original code with Environment.Exit(0) here 90 | Console.WriteLine("About to call Environment.Exit"); 91 | //Environment.Exit(0); 92 | //Console.WriteLine("Survived exit"); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /testbin/1.cs: -------------------------------------------------------------------------------- 1 | // Hello World! program 2 | namespace HelloWorld 3 | { 4 | class Hello { 5 | static void Main(string[] args) 6 | { 7 | System.Console.WriteLine("Hello World!"); 8 | System.Console.WriteLine("Hello World111!"); 9 | foreach (var arg in args) 10 | { 11 | System.Console.WriteLine(arg); 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /testbin/hello.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timwhitez/Doge-CLRLoad/7f4787b00638bf45412cd2b03907af775d5847db/testbin/hello.exe -------------------------------------------------------------------------------- /testbin/hello111.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timwhitez/Doge-CLRLoad/7f4787b00638bf45412cd2b03907af775d5847db/testbin/hello111.exe --------------------------------------------------------------------------------