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

posted in DruinkJournal
Published December 29, 2010
Advertisement
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.
Previous Entry Untitled
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement