A fairly common technique when it comes code injection is dll injection. All major c2 frameworks are using this technique when a command such as migrate is used. When a c2 migrates from one process to another, although it's seamless to the operator, it usually injects the implant dll into the remote process and kills the current implant session.
In this post we will explore how we can inject a dll to a remote process.
The following steps will be performed in the following code:
CreateRemoteThread
Let's dive into the code:
Download dll from a remote host
It is fairly easy to issue a get request and download the remote file into a byte slice in golang. The following function was used.
dwDesiredAccess: This is defined by the rest of the APIs that we will use.
VirtualAllocEX -> PROCESS_VM_OPERATION
WriteProcessMemory -> PROCESS_VM_WRITE and PROCESS_VM_OPERATION
CreateRemoteThread -> PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ
Alternatively we can use PROCESS_ALL_ACCESS for convenience
bInheritHandle: Will be set to false
dwProcessId: Will be the ID of the process to get a handle on
Luckily the OpenProcess API is part of the windows package
pid := uint32(12240)
PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF
fmt.Printf("[+] Getting a handle on process with pid: %d\n", pid)
pHandle, err := windows.OpenProcess(uint32(PROCESS_ALL_ACCESS), false, pid)
if err != nil {
log.Fatalf("[FATAL] Unable to get a handle on process with id: %d : %v ", pid, err)
}
fmt.Printf("[+] Obtained a handle 0x%x on process with ID: %d\n", pHandle, pid)
Allocating memory on remote process
VirtuallAllocEx will be used for allocating memory for the dll path in the remote memory.
hProcess: Process Handle returned by the OpenProcess 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 rwx permissions. It is however unusual for legitimate programs to allocate memory with rwx permissions and it is usually flagged my AV engines. Another option is to assign rx or rw and then change permissions as needed for writing and executing with VirtualProtectEx or WriteProcessMemory.
hProcess: Process Handle returned by the OpenProcess 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
// Write to remote memory
var numberOfBytesWritten uintptr
err = windows.WriteProcessMemory(pHandle,
addr,
&fnameBytes[0],
uintptr(len(fnameBytes)),
&numberOfBytesWritten)
if err != nil {
log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
}
fmt.Printf("[+] Wrote %d/%d bytes to destination address\n", numberOfBytesWritten, len(fnameBytes))
line 5: Pass the dll path byte slice
Get the address LoadLibraryA
// Get address of loadLibraryA
procLoadLibraryA := modKernel32.NewProc("LoadLibraryA")
Create Remote Thread
CreateRemoteThread API will be used to create a thread and run the shellcode.