Overview
In the previous lesson and accompanying lab, we learned how to enumerate running processes and obtain a handle (hProcess
) to a target process with specific access rights. This handle is our key to interacting with the target process’s virtual address space, i.e. allowing us to inject our shellcode into its memory.
Now that we have a handle, the next steps in performing process injection involve manipulating the target process’s memory. Specifically, we need to:
- Allocate a region of memory within the target process to hold our shellcode.
- Inject our shellcode into that allocated remote memory region.
- (Often) Change the memory protection flags of that region to make it executable just before we trigger it.
The Need for Remote Operations
This process should seem very familiar of course since it’s the same conceptual steps we perform when injecting into our own memory. The major difference will be the specific functions we use to allow us to do these things to an external process. Also, just to be clear: external here does not mean “on another host”, it just means another process.
Fortunately, the standard Windows API provides all the functions we need to perform cross-process memory operations. They are often recognizable by an Ex
suffix (for “Extended” or “External”) compared to their counterparts that operate within the current process. For example instead of using VirtualAlloc
, we’ll use VirtualAllocEx
.
As you might expect, in almost each case we’ll need to pass our handle (hProcess
) as an argument to call an *Ex
function, which tells the Windows kernel which process’s memory map to modify.
Allocating Memory Remotely: VirtualAllocEx
This function is the cross-process equivalent of VirtualAlloc
. It reserves, commits, or changes the state of a region of memory within the virtual address space of a specified process.
LPVOID VirtualAllocEx(
HANDLE hProcess, // Handle to the target process
LPVOID lpAddress, // Desired starting address (or NULL)
SIZE_T dwSize, // Size of the region to allocate
DWORD flAllocationType, // Allocation type (e.g., MEM_COMMIT | MEM_RESERVE)
DWORD flProtect // Memory protection (e.g., PAGE_READWRITE)
);
Key Differences from VirtualAlloc
:
hProcess
: This is the crucial first parameter. We must provide the valid handle to the target process that you obtained usingOpenProcess
. This handle must have been opened with thePROCESS_VM_OPERATION
access right.lpAddress
: Specifies the desired starting address within the target process’s address space. PassingNULL
lets the system choose an address within the target process.dwSize
: The size of the memory to allocate for our payload.flAllocationType
: TypicallyMEM_COMMIT | MEM_RESERVE
.flProtect
: The initial memory protection. We should once again allocate this asPAGE_READWRITE
initially, intending to change it later.
Return Value: If successful, VirtualAllocEx
returns the base address of the allocated region within the target process’s address space. This address is only meaningful within the context of that target process. If it fails, it returns NULL
.
So just to recap: We started off by obtaining a handle to the target process, now hopefully using VirtualAllocEx
we can obtain the specific starting address inside the process memory where we intend to write our shellcode to.
Writing to Remote Memory: WriteProcessMemory
Once we’ve allocated memory in the target process (at the address returned by VirtualAllocEx
), we need to copy our payload (e.g., shellcode) from our loader process into that remote buffer. WriteProcessMemory
achieves this.
BOOL WriteProcessMemory(
HANDLE hProcess, // Handle to the target process
LPVOID lpBaseAddress, // Base address to write TO (in target process)
LPCVOID lpBuffer, // Pointer to the data to write FROM (in current process)
SIZE_T nSize, // Number of bytes to write
SIZE_T *lpNumberOfBytesWritten // Optional: Pointer to receive bytes actually written
);
Parameters:
hProcess
: The handle to the target process, which must havePROCESS_VM_WRITE
andPROCESS_VM_OPERATION
access rights.lpBaseAddress
: The starting address within the target process where writing should begin - this is the address returned by ourVirtualAllocEx
call.lpBuffer
: A pointer to the buffer in the current (loader) process that contains the data to be written (e.g. our shellcode byte array).nSize
: The number of bytes to write fromlpBuffer
.lpNumberOfBytesWritten
: A pointer to a variable that will receive the number of bytes successfully written. Can beNULL
if not needed, but checking it is good practice.
Return Value: Returns non-zero (TRUE
) if successful, zero (FALSE
) on failure.
Changing Remote Memory Protections: VirtualProtectEx
After writing the shellcode into the remote process’s PAGE_READWRITE
memory region, we need to change its protection to make it executable, following the RW -> RX pattern. VirtualProtectEx
is the function for modifying memory protections in another process.
BOOL VirtualProtectEx(
HANDLE hProcess, // Handle to the target process
LPVOID lpAddress, // Base address of the region to change (in target process)
SIZE_T dwSize, // Size of the region
DWORD flNewProtect, // The NEW desired memory protection (e.g., PAGE_EXECUTE_READ)
PDWORD lpflOldProtect // Pointer to receive the OLD protection flags
);
Key Differences from VirtualProtect
:
hProcess
: The handle to the target process, requiringPROCESS_VM_OPERATION
access right.lpAddress
: The starting address within the target process whose protection needs changing (again, typically the address fromVirtualAllocEx
).dwSize
: The size of the memory region.flNewProtect
: The new protection constant, usuallyPAGE_EXECUTE_READ
(RX) for executing shellcode.lpflOldProtect
: Pointer to aDWORD
that will receive the previous protection flags.
Return Value: Returns non-zero (TRUE
) if successful, zero (FALSE
) on failure.
Putting It Together (Conceptual Workflow)
The entire sequence, including obtaining our initial handle, now looks like this:
OpenProcess
-> GethProcess
with required rights (VM_OPERATION
,VM_WRITE
,CREATE_THREAD
, etc.).VirtualAllocEx(hProcess, NULL, payloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)
-> GetremoteBufferAddress
.WriteProcessMemory(hProcess, remoteBufferAddress, localPayloadBuffer, payloadSize, NULL)
-> Copy payload to target.VirtualProtectEx(hProcess, remoteBufferAddress, payloadSize, PAGE_EXECUTE_READ, &oldProtect)
-> Make remote buffer executable.So, we’re almost there, but not quite yet, in order to execute we’ll also still perform the following steps, which we’ll cover in Theory 10.3.
(Next Step) Use
remoteBufferAddress
as the start address forCreateRemoteThread
.(Cleanup) Eventually call
VirtualFreeEx
onremoteBufferAddress
andCloseHandle
onhProcess
.
Conclusion
The Windows API provides specific Ex
suffixed functions (VirtualAllocEx
, VirtualProtectEx
) and WriteProcessMemory
to allow a process with appropriate permissions (obtained via OpenProcess
) to allocate, write to, and change the protections of memory within another process’s address space.
In the next lab, we will use these functions in Go to perform these remote memory operations.