Archive

Posts Tagged ‘module’

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

LdrpLoadedDllHandleCache

May 6th, 2009

LdrpLoadedDllHandleCache is the name of the cached module handle that NTDLL holds which was covered briefly in my previous post.

Just as a confirmation that it does infact exist and is still present, here is the entire body of LdrpUnloadDll from Vista x86 SP1 Untouched. (Generated by IDA v5.4 and HexRays)

int __stdcall LdrpUnloadDll(int a1, int a2)
{
int v2; // [email protected]
int v4; // [email protected]
int v5; // [email protected]
int v6; // [email protected]
int v7; // [email protected]
int v8; // [email protected]
int v9; // [email protected]
int v10; // [email protected]
int v11; // [email protected]
int v12; // [email protected]
int v13; // [email protected]
int v14; // [email protected]
int v15; // [email protected]
int v16; // [email protected]
int v17; // [email protected]
int v18; // [email protected]
int v19; // [email protected]
int v20; // [email protected]
int v21; // [email protected]
int v22; // [email protected]
int v23; // [email protected]
int v24; // [email protected]
char v25; // [email protected]
int v26; // [email protected]
int v27; // [email protected]
int v28; // [email protected]
char v29; // [email protected]
int (__stdcall *v30)(_DWORD); // [email protected]
int v31; // [email protected]
int v32; // [email protected]
int v33; // [email protected]
int v34; // [email protected]
int v35; // [email protected]
int v36; // [email protected]
int v37; // [email protected]
int v38; // [email protected]
int v39; // [email protected]
int v40; // [email protected]
int v41; // [email protected]
int v42; // [sp+70h] [bp-28h]@1
int v43; // [sp+68h] [bp-30h]@1
int v44; // [sp+6Ch] [bp-2Ch]@1
int v45; // [sp+94h] [bp-4h]@1
int v46; // [sp+7Ch] [bp-1Ch]@2
int v47; // [sp+74h] [bp-24h]@7
int *v48; // [sp+78h] [bp-20h]@7
int *v49; // [sp+5Ch] [bp-3Ch]@10
int *v50; // [sp+58h] [bp-40h]@10
int v51; // [sp+64h] [bp-34h]@10
int v52; // [sp+10h] [bp-88h]@13
int v53; // [sp+14h] [bp-84h]@13
char v54; // [sp+18h] [bp-80h]@13
char v55; // [sp+60h] [bp-38h]@19
int v56; // [sp+34h] [bp-64h]@28
int v57; // [sp+38h] [bp-60h]@28
char v58; // [sp+3Ch] [bp-5Ch]@28

v42 = *(_DWORD *)a2;
v16 = *(_DWORD *)(*MK_FP(__FS__, 24) + 48);
v43 = *(_DWORD *)(*MK_FP(__FS__, 24) + 48);
v2 = 0;
v44 = 0;
v45 = 0;
++LdrpActiveUnloadCount;
if ( !*(_BYTE *)(*(_DWORD *)(v16 + 12) + 40) )
{
if ( (unsigned __int8)LdrpCheckForLoadedDllHandle(a1, &v46) )
{
if ( *(_WORD *)(v46 + 56) != -1 )
{
-*(_WORD *)(v46 + 56);
LdrpUpdateServiceTagCount(v46, 2);
LdrpCheckForwardedEntries(v46);
if ( *(_BYTE *)(v46 + 52) & 4 )
{
v56 = 36;
v57 = 1;
memset(&v58, 0, 0×1Cu);
RtlActivateActivationContextUnsafeFast();
v45 = 1;
LdrpUpdateLoadCount2(v46, 2);
v45 = 0;
sub_77F0982B();
}
if ( LdrpActiveUnloadCount == 1 )
{
dword_77F991A4 = (int)&LdrpUnloadHead;
LdrpUnloadHead = (int)&LdrpUnloadHead;
}
v13 = dword_77F94CE0;
v48 = (int *)dword_77F94CE0;
while ( v13 != (_DWORD)&dword_77F94CDC )
{
v31 = v13 - 16;
v46 = v13 - 16;
v13 = *(_DWORD *)(v13 + 4);
v48 = (int *)v13;
*(_DWORD *)(v31 + 52) &= 0xFFFFDFFFu;
if ( !*(_WORD *)(v46 + 56) )
{
LdrpModuleCacheUnloadNotification(v46);
v14 = v46;
if ( !*(_WORD *)(v46 + 56) )
{
v47 = v46;
if ( g_ShimsEnabled )
{
v30 = (int (__stdcall *)(_DWORD))RtlDecodeSystemPointer(g_pfnSE_DllUnloaded);
v30(v14);
}
v32 = *(_DWORD *)(v14 + 16);
v33 = *(_DWORD *)(v14 + 20);
*(_DWORD *)v33 = v32;
*(_DWORD *)(v32 + 4) = v33;
v34 = *(_DWORD *)(v14 + 8);
v35 = *(_DWORD *)(v14 + 12);
*(_DWORD *)v35 = v34;
*(_DWORD *)(v34 + 4) = v35;
v15 = v14 + 60;
v36 = *(_DWORD *)(v14 + 60);
v37 = *(_DWORD *)(v14 + 64);
*(_DWORD *)v37 = v36;
*(_DWORD *)(v36 + 4) = v37;
if ( (unsigned __int8)LdrpActiveUnloadCount > 1u )
{
LdrpLoadedDllHandleCache = 0;
*(_DWORD *)(v14 + = 0;
}
v38 = dword_77F991A4;
*(_DWORD *)v15 = &LdrpUnloadHead;
*(_DWORD *)(v14 + 64) = v38;
*(_DWORD *)v38 = v15;
dword_77F991A4 = v14 + 60;
}
}
}
if ( (unsigned __int8)LdrpActiveUnloadCount <= 1u )
{
v49 = (int *)&v50;
v50 = (int *)&v50;
v5 = 0;
v47 = 0;
v4 = LdrpUnloadHead;
v48 = (int *)LdrpUnloadHead;
while ( v4 != (_DWORD)&LdrpUnloadHead )
{
if ( v5 == v2 )
{
v6 = (int)v48;
}
else
{
RtlRemoveInvertedFunctionTable(&LdrpInvertedFunctionTable, *(_DWORD *)(v5 + 24));
v39 = *(_DWORD *)v5;
v40 = *(_DWORD *)(v5 + 4);
*(_DWORD *)v40 = v39;
*(_DWORD *)(v39 + 4) = v40;
v47 = v2;
v6 = LdrpUnloadHead;
v48 = (int *)LdrpUnloadHead;
if ( LdrpUnloadHead == (_DWORD)&LdrpUnloadHead )
break;
}
v19 = v6 - 60;
v46 = v19;
LdrpRecordUnloadEvent(v19);
v8 = v46;
v47 = v46;
LdrpLoadedDllHandleCache = v2;
*(_DWORD *)(v46 + = v2;
v20 = v46 + 60;
v21 = *(_DWORD *)(v46 + 60);
v22 = *(_DWORD *)(v46 + 64);
*(_DWORD *)v22 = v21;
*(_DWORD *)(v21 + 4) = v22;
v23 = (int)v49;
*(_DWORD *)v20 = &v50;
*(_DWORD *)(v20 + 4) = v23;
*(_DWORD *)v23 = v20;
v49 = (int *)v20;
v7 = *(_DWORD *)(v46 + 28);
v51 = *(_DWORD *)(v46 + 28);
if ( !v7 || (v24 = *(_DWORD *)(v46 + 52), v24 &= 0×80000u, v25 = v24 == 0, v25) )
{
RtlRemoveInvertedFunctionTable(&LdrpInvertedFunctionTable, *(_DWORD *)(v8 + 24));
v17 = *(_DWORD *)v8;
v18 = *(_DWORD *)(v8 + 4);
*(_DWORD *)v18 = v17;
*(_DWORD *)(v17 + 4) = v18;
v5 = 0;
v47 = 0;
v4 = LdrpUnloadHead;
v48 = (int *)LdrpUnloadHead;
}
else
{
v45 = 2;
JUMPOUT((dword_77F143C8 | 1) & ShowSnaps, *(unsigned int *)sub_77F41A51);
if ( ShowSnaps & dword_77F143CC )
DbgBreakPoint();
v52 = 36;
v53 = 1;
memset(&v54, 0, 0×1Cu);
RtlActivateActivationContextUnsafeFast();
v45 = 3;
v9 = v46;
if ( *(_WORD *)(v46 + 58) )
{
LdrpCallTlsInitializers(0, v46);
v9 = v46;
}
LdrpCallInitRoutine(v7, *(_DWORD *)(v9 + 24), 0, 0);
v45 = 2;
sub_77F0904D();
RtlRemoveInvertedFunctionTable(&LdrpInvertedFunctionTable, *(_DWORD *)(v8 + 24));
v26 = *(_DWORD *)v8;
v27 = *(_DWORD *)(v8 + 4);
*(_DWORD *)v27 = v26;
*(_DWORD *)(v26 + 4) = v27;
v5 = 0;
v47 = 0;
v4 = LdrpUnloadHead;
v48 = (int *)LdrpUnloadHead;
v45 = 0;
}
v2 = 0;
}
v11 = (int)v50;
v48 = v50;
while ( v11 != (_DWORD)&v50 )
{
v10 = v11 - 60;
v46 = v11 - 60;
v11 = *(_DWORD *)v11;
v48 = (int *)v11;
v12 = v10;
v47 = v10;
if ( *(_WORD *)(v43 + 104) & 0×100 )
{
AVrfDllUnloadNotification(v10);
LOBYTE(v10) = v46;
}
if ( (dword_77F143C8 | 1) & ShowSnaps )
LdrpLogDbgPrint(
“d:\\rtm\\base\\ntdll\\ldrapi.c”,
1468,
“LdrpUnloadDll”,
2,
“Unmapping DLL \”%wZ\”\n”,
v10 + 36);
if ( ShowSnaps & dword_77F143CC )
DbgBreakPoint();
LdrUnloadAlternateResourceModule(*(_DWORD *)(v12 + 24));
if ( RtlImageDirectoryEntryToData(*(_DWORD *)(v12 + 24), 1, 14, &v55) != v2 )
LdrpCorUnloadImage(*(_DWORD *)(v12 + 24));
v28 = *(_DWORD *)(v12 + 52);
v28 &= 0×800000u;
v29 = v28 == 0;
if ( v29 )
v44 = NtUnmapViewOfSection(-1, *(_DWORD *)(v12 + 24));
LdrpSendDllNotifications(v12, 2, *(_BYTE *)(*(_DWORD *)(v43 + 12) + 40) != 0);
while ( *(_DWORD *)(v12 + 76) != v2 )
{
v41 = *(_DWORD *)(v12 + 76);
*(_DWORD *)(v12 + 76) = *(_DWORD *)(v41 + 12);
*(_DWORD *)(v41 + 12) = v42;
v42 = v41;
}
LdrpCheckForwardedEntries(v12);
LdrpFinalizeAndDeallocateDataTableEntry(v12);
if ( v12 == LdrpGetModuleHandleCache )
LdrpGetModuleHandleCache = v2;
}
LdrpModuleReferenceFreeCallback();
}
}
}
else
{
v44 = -1073741515;
}
}
v45 = -2;
sub_77F0E99C();
*(_DWORD *)a2 = v42;
return v44;
}

You can see referenced there LdrpLoadedDllHandleCache. Just wanted to point that out for anyone who had their doubts about its existence.