Home > Programming, Windows > Calling Exports in an Injected DLL

Calling Exports in an Injected DLL

June 29th, 2009

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 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 ExitCod
  1. Raindog
    July 1st, 2009 at 14:19 | #1

    Don’t be an RTARD. Simply write over the asm bytes to call your exported function when it’s injected, doing it the same way that you injected it via the call to getprocaddress and loadlibrary.

  2. July 1st, 2009 at 15:13 | #2

    @Raindog
    Sigh. I wanted a PORTABLE method. Dumping bytes right in is hardly portable. My entire framework is platform independent (with official support for IA32 and AMD64) except for a few unavoidable parts (for example in the VEH class I need to be able to modify the instruction pointer so I use an architecture ifdef/elseif/etc).

    If I want to support a new platform then it’s VERY little work for me to do so (probably about a dozen or so lines need to be updated). The same can’t be said when you do shit like dump platform-specific bytes into a remote process.

    If you think that portability is “retarded” I’d hate to have to port your code to a new architecture (e.g. AMD64).

  3. poire
    July 3rd, 2009 at 16:38 | #3

    I really like the project you’re working on. I am thinking of doing something similar too, hit me up if you’re interested.

  4. July 4th, 2009 at 13:49 | #4

    @poire
    Not interested sorry. I’m capable of doing everything I need without taking on a partner. It’s just an unnecessary hassle.

  1. No trackbacks yet.