Calling Exports in an Injected DLL
There’s a long list of things you’re not supposed to do in DllMain, typically you’re supposed to work around this by exporting a function to do all your actual initialization and calling that after your module is fully loaded. The problem is, this is hard when using an injected DLL, because you need to create another remote thread at a location that can only be determined at runtime. When faced with this problem my original solution was to get the module base address in the remote process and manually walk the EAT using ReadProcessMemory. Half way through writing that though I thought of an easier way, a way I could ‘cheat’. Rather than walk the EAT in the remote process I’d simply load the module without calling DllMain or performing any static linking then walk the EAT locally. I did this because I already had local EAT walking code and it’s much simpler to write.
I figured others would have had the same problem so I’m posting some sample code to get you started. I only finished it 10 minutes ago so it’s ANSI only, very lightly tested, relies on a bunch of internal classes, etc. You should easily be able to pull the logic from this though and port it to your needs with minimal changes.
Copyright (c) 2009 Cypher. Licensed under the Creative Commons 2.5 Attribution license.
// Call an exported function in a module in a remote process
DWORD Injector::CallExport(DWORD ProcID, const string& ModuleName,
const string& ExportName)
{
// Grab a new snapshot of the process
EnsureCloseHandle Snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
ProcID));
if (Snapshot == INVALID_HANDLE_VALUE)
throw runtime_error(”Injector::CallExport: Could not get module ”
“snapshot for remote process.”);
// Get the HMODULE of the desired library
MODULEENTRY32W ModEntry = { sizeof(ModEntry) };
bool Found = false;
BOOL bMoreMods = Module32FirstW(Snapshot, &ModEntry);
for (; bMoreMods; bMoreMods = Module32NextW(Snapshot, &ModEntry))
{
wstring ExePath(ModEntry.szExePath);
wstring ModuleTmp(ModuleName.begin(), ModuleName.end());
Found = (ExePath == ModuleTmp);
if (Found) break;
}
if (!Found)
throw runtime_error(”Injector::CallExport: Could not find module in ”
“remote process.”);
// Get module base address
PBYTE ModuleBase = ModEntry.modBaseAddr;
// Get a handle for the target process.
EnsureCloseHandle Process(OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION |
PROCESS_VM_READ,
FALSE, ProcID));
if (!Process)
throw runtime_error(”Injector::CallExport: Could not get handle to ”
“process.”);
// Load module as data so we can read the EAT locally
EnsureFreeLibrary MyModule(LoadLibraryExA(ModuleName.c_str(), NULL,
DONT_RESOLVE_DLL_REFERENCES));
// Get module pointer
PVOID Module = static_cast<PVOID>(MyModule);
// Get pointer to DOS header
PIMAGE_DOS_HEADER pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(
static_cast<HMODULE>(Module));
if (!pDosHeader || pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
throw runtime_error(”Injector::CustomGetProcAddress: DOS PE header ”
“is invalid.”);
// Get pointer to NT header
PIMAGE_NT_HEADERS pNtHeader = reinterpret_cast<PIMAGE_NT_HEADERS>
(reinterpret_cast<PCHAR>(Module) + pDosHeader->e_lfanew);
if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
throw runtime_error(”Injector::CustomGetProcAddress: NT PE header ”
“is invalid.”);
// Get pointer to image export directory
PVOID pExportDirTemp = reinterpret_cast<PBYTE>(Module) +
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress;
PIMAGE_EXPORT_DIRECTORY pExportDir =
reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(pExportDirTemp);
// Symbol names could be missing entirely
if (pExportDir->AddressOfNames == NULL)
throw runtime_error(”Injector::CustomGetProcAddress: Symbol names ”
“missing entirely.”);
// Get pointer to export names table
PDWORD pNamesRvas = reinterpret_cast<PDWORD>(
reinterpret_cast<PBYTE>(Module) + pExportDir->AddressOfNames);
// Get pointer to export ordinal table
PWORD pNameOrdinals = reinterpret_cast<PWORD>(
reinterpret_cast<PBYTE>(Module) + pExportDir->AddressOfNameOrdinals);
// Get pointer to export address table
PDWORD pFunctionAddresses = reinterpret_cast<PDWORD>(
reinterpret_cast<PBYTE>(Module) + pExportDir->AddressOfFunctions);
// Variable to hold the export address
FARPROC pExportAddr = 0;
// Walk the array of this module’s function names
for (DWORD n = 0; n < pExportDir->NumberOfNames; n++)
{
// Get the function name
PSTR CurrentName = reinterpret_cast<PSTR>(
reinterpret_cast<PBYTE>(Module) + pNamesRvas[n]);
// If not the specified function, try the next function
if (ExportName != CurrentName) continue;
// We found the specified function
// Get this function’s Ordinal value
WORD Ordinal = pNameOrdinals[n];
// Get the address of this function’s address
pExportAddr = reinterpret_cast<FARPROC>(reinterpret_cast<PBYTE>(Module)
+ pFunctionAddresses[Ordinal]);
// We got the func. Break out.
break;
}
// Nothing found, throw exception
if (!pExportAddr)
throw runtime_error(”Injector::CustomGetProcAddress: Could not find ”
+ ExportName + “.”);
// Convert local address to remote address
// TODO: Clean the casts. Currently working but could be simplified.
PTHREAD_START_ROUTINE pfnThreadRtn =
reinterpret_cast<PTHREAD_START_ROUTINE>((reinterpret_cast<DWORD_PTR>(
pExportAddr) - reinterpret_cast<DWORD_PTR>(Module)) +
reinterpret_cast<DWORD_PTR>(ModuleBase));
// Create a remote thread that calls FreeLibrary()
EnsureCloseHandle Thread(CreateRemoteThread(Process, NULL, 0,
pfnThreadRtn, ModEntry.modBaseAddr, 0, NULL));
if (!Thread)
throw runtime_error(”Injector::CallExport: Could not create thread in ”
“remote process.”);
// Wait for the remote thread to terminate
WaitForSingleObject(Thread, INFINITE);
// Get thread exit code
DWORD ExitCode = 0;
if (!GetExitCodeThread(Thread,&ExitCode))
throw runtime_error(”Injector::CallExport: Could not get thread exit ”
“code.”);
// Return thread exit code
return ExitCode;
}
DWORD Injector::CallExport(DWORD ProcID, const string& ModuleName,const string& ExportName){// Grab a new snapshot of the processEnsureCloseHandle Snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,ProcID));if (Snapshot == INVALID_HANDLE_VALUE)throw runtime_error(”Injector::CallExport: Could not get module ““snapshot for remote process.”);// Get the HMODULE of the desired libraryMODULEENTRY32W ModEntry = { sizeof(ModEntry) };bool Found = false;BOOL bMoreMods = Module32FirstW(Snapshot, &ModEntry);for (; bMoreMods; bMoreMods = Module32NextW(Snapshot, &ModEntry)){wstring ExePath(ModEntry.szExePath);wstring ModuleTmp(ModuleName.begin(), ModuleName.end());Found = (ExePath == ModuleTmp);if (Found) break;}if (!Found)throw runtime_error(”Injector::CallExport: Could not find module in ““remote process.”);// Get module base addressPBYTE ModuleBase = ModEntry.modBaseAddr;// Get a handle for the target process.EnsureCloseHandle Process(OpenProcess(PROCESS_QUERY_INFORMATION |PROCESS_CREATE_THREAD |PROCESS_VM_OPERATION |PROCESS_VM_READ,FALSE, ProcID));if (!Process)throw runtime_error(”Injector::CallExport: Could not get handle to ““process.”);// Load module as data so we can read the EAT locallyEnsureFreeLibrary MyModule(LoadLibraryExA(ModuleName.c_str(), NULL,DONT_RESOLVE_DLL_REFERENCES));// Get module pointerPVOID Module = static_cast<PVOID>(MyModule);// Get pointer to DOS headerPIMAGE_DOS_HEADER pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(static_cast<HMODULE>(Module));if (!pDosHeader || pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)throw runtime_error(”Injector::CustomGetProcAddress: DOS PE header ““is invalid.”);// Get pointer to NT headerPIMAGE_NT_HEADERS pNtHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<PCHAR>(Module) + pDosHeader->e_lfanew);if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)throw runtime_error(”Injector::CustomGetProcAddress: NT PE header ““is invalid.”);// Get pointer to image export directoryPVOID pExportDirTemp = reinterpret_cast<PBYTE>(Module) +pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;PIMAGE_EXPORT_DIRECTORY pExportDir =reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(pExportDirTemp);// Symbol names could be missing entirelyif (pExportDir->AddressOfNames == NULL)throw runtime_error(”Injector::CustomGetProcAddress: Symbol names ““missing entirely.”);// Get pointer to export names tablePDWORD pNamesRvas = reinterpret_cast<PDWORD>(reinterpret_cast<PBYTE>(Module) + pExportDir->AddressOfNames);// Get pointer to export ordinal tablePWORD pNameOrdinals = reinterpret_cast<PWORD>(reinterpret_cast<PBYTE>(Module) + pExportDir->AddressOfNameOrdinals);// Get pointer to export address tablePDWORD pFunctionAddresses = reinterpret_cast<PDWORD>(reinterpret_cast<PBYTE>(Module) + pExportDir->AddressOfFunctions);// Variable to hold the export addressFARPROC pExportAddr = 0;// Walk the array of this module’s function namesfor (DWORD n = 0; n < pExportDir->NumberOfNames; n++){// Get the function namePSTR CurrentName = reinterpret_cast<PSTR>(reinterpret_cast<PBYTE>(Module) + pNamesRvas[n]);// If not the specified function, try the next functionif (ExportName != CurrentName) continue;// We found the specified function// Get this function’s Ordinal valueWORD Ordinal = pNameOrdinals[n];// Get the address of this function’s addresspExportAddr = reinterpret_cast<FARPROC>(reinterpret_cast<PBYTE>(Module)+ pFunctionAddresses[Ordinal]);// We got the func. Break out.break;}// Nothing found, throw exceptionif (!pExportAddr)throw runtime_error(”Injector::CustomGetProcAddress: Could not find “+ ExportName + “.”);// Convert local address to remote address// TODO: Clean the casts. Currently working but could be simplified.PTHREAD_START_ROUTINE pfnThreadRtn =reinterpret_cast<PTHREAD_START_ROUTINE>((reinterpret_cast<DWORD_PTR>(pExportAddr) - reinterpret_cast<DWORD_PTR>(Module)) +reinterpret_cast<DWORD_PTR>(ModuleBase));// Create a remote thread that calls FreeLibrary()EnsureCloseHandle Thread(CreateRemoteThread(Process, NULL, 0,pfnThreadRtn, ModEntry.modBaseAddr, 0, NULL));if (!Thread)throw runtime_error(”Injector::CallExport: Could not create thread in ““remote process.”);// Wait for the remote thread to terminateWaitForSingleObject(Thread, INFINITE);// Get thread exit codeDWORD ExitCode = 0;if (!GetExitCodeThread(Thread,&ExitCode))throw runtime_error(”Injector::CallExport: Could not get thread exit ““code.”);// Return thread exit codereturn ExitCod