• Advertisement
Sign in to follow this  
  • entries
    557
  • comments
    1237
  • views
    422154

If you've got monkeys on the knees, just say Thynn

Sign in to follow this  

469 views

I'm not dead! Again.

I've not been doing an awful lot of my own stuff recently, I've been particularly busy with work stuff - as usual.

We got one of our projects (On two platforms) canned unfortunately, especially since we'd been working on it for over a year. I can't say much more about it for obvious reasons.

I'm currently dicking around doing some stuff like Microsoft's Detours library, just because I like doing fun low-level stuff like this. I've gone back to my APIHijack app that I wrote a while ago (Might be linked in a previous entry, PM me if it's not and you especially want the source), that lets me redirect calls to any call made from an EXE (or DLL) to another DLL to my own function.
That works by injecting a DLL into a remote process, via the usual CreateRemoteThread method, but the problem is that you can only do any real "work" in DllMain, or when a patched API call is made. Doing stuff in DllMain has several well known problems (The loader lock), and doing stuff in a patched API call might not be practical for various reasons.

So, the obvious solution is to use an initialisation function, which is called after the DLL has been loaded. The problem is calling that function, since it needs to be called from within the target process. That means that you need to do a GetProcAddress() in the remote process to get the address of the initialisation function, which isn't directly possible (It rather involved being on the other side of this airtight hatchway).

So, I've now written a GetRemoteProcAddress function, which uses ReadProcessMemory to parse the PE header and read the exports section (Which is what GetProcAddress does internally anyway).

So, for anyone who cares:

FARPROC GetRemoteProcAddress(HANDLE hProcess, HMODULE hDll, const char* szFunc)
{
// Read and check the DOS and NT headers
BYTE* pBaseAddress = (BYTE*)hDll;
SIZE_T nBytesToRead = sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS) + 4096;
BYTE* pBuffer = new BYTE[nBytesToRead];
SIZE_T nBytesRead = 0;
if(!ReadProcessMemory(hProcess, pBaseAddress, pBuffer, nBytesToRead, &nBytesRead) ||
nBytesRead != nBytesToRead)
{
delete[] pBuffer;
return NULL;
}
IMAGE_DOS_HEADER* pDOSHeader = (IMAGE_DOS_HEADER*)pBuffer;
if(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE || pDOSHeader->e_lfanew < 0 ||
(DWORD)pDOSHeader->e_lfanew > nBytesToRead-sizeof(IMAGE_NT_HEADERS))
{
delete[] pBuffer;
return NULL;
}
IMAGE_NT_HEADERS* pNTHeader = (IMAGE_NT_HEADERS*)(pBuffer + pDOSHeader->e_lfanew);
if(pNTHeader->Signature != IMAGE_NT_SIGNATURE)
{
delete[] pBuffer;
return NULL;
}

// Get export data directory
IMAGE_OPTIONAL_HEADER& optionalHeader = pNTHeader->OptionalHeader;
DWORD dwExportRVA = optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if(optionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT || dwExportRVA == 0)
{
delete[] pBuffer;
return NULL;
}

// Read export directory header
delete[] pBuffer;
IMAGE_EXPORT_DIRECTORY exportDirectory;
if(!ReadProcessMemory(hProcess, pBaseAddress + dwExportRVA, &exportDirectory,
sizeof(exportDirectory), &nBytesRead) || nBytesRead != sizeof(exportDirectory))
{
return NULL;
}

// Find this functions ordinal
WORD wFunctionIndex = 0xffff;
if((DWORD)szFunc < 0xffff)
{
// Function name passed as an ordinal
wFunctionIndex = (WORD)szFunc - (WORD)exportDirectory.Base;
}
else
{
DWORD dwFunctionOrdinalIndex = exportDirectory.NumberOfFunctions;

// Read export names table
DWORD* pNameRVAs = new DWORD[exportDirectory.NumberOfNames];
if(!ReadProcessMemory(hProcess, pBaseAddress + exportDirectory.AddressOfNames, pNameRVAs,
exportDirectory.NumberOfNames*sizeof(DWORD), &nBytesRead) ||
nBytesRead != exportDirectory.NumberOfNames*sizeof(DWORD))
{
delete[] pNameRVAs;
return NULL;
}

// Search the name table for this function
for(DWORD i=0; i {
// Read this export name
char szBuff[128];
if(!ReadProcessMemory(hProcess, pBaseAddress + pNameRVAs, szBuff, sizeof(szBuff),
&nBytesRead))
{
delete[] pNameRVAs;
return NULL;
}
if(nBytesRead < sizeof(szBuff))
szBuff[nBytesRead] = 0;

// Is this the one we want?
if(strcmp(szBuff, szFunc) == 0)
{
dwFunctionOrdinalIndex = i;
break;
}
}
delete[] pNameRVAs;

// Lookup this function in the ordinal table
if(dwFunctionOrdinalIndex >= exportDirectory.NumberOfFunctions)
return NULL;
if(!ReadProcessMemory(hProcess,
pBaseAddress + exportDirectory.AddressOfNameOrdinals + dwFunctionOrdinalIndex*sizeof(WORD),
&wFunctionIndex, sizeof(wFunctionIndex), &nBytesRead) ||
nBytesRead != sizeof(wFunctionIndex))
{
return NULL;
}
}
if(wFunctionIndex >= exportDirectory.NumberOfFunctions)
return NULL;

// Read the export RVA in the export table
DWORD dwFunctionRVA;
if(!ReadProcessMemory(hProcess,
pBaseAddress + exportDirectory.AddressOfFunctions + wFunctionIndex*sizeof(DWORD),
&dwFunctionRVA, sizeof(dwFunctionRVA), &nBytesRead) ||
nBytesRead != sizeof(dwFunctionRVA))
{
return NULL;
}

// Convert RVA to FARPROC
return (FARPROC)(pBaseAddress + dwFunctionRVA);
}

Not very well tested, and it could be a bit more optimal, but it does the job - especially if there's only a handful of exports.

Anyway, that's enough of my rambling for now. I'm returning to bed - I have the plague. Or flu. But I'm pretty sure it's the plague.
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement