🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Untitled

posted in DruinkJournal
Published July 20, 2007
Advertisement
Well, I might as well spew out a large chunk of code. I've been writing an API hooking library, and I'm about halfway through. It works perfectly in x86, but I'd like it to work in x64 too.

Anyway, here we go. I may as well just dump it all here.

Header file (Just structs just now:
//============================================================================// ProcessLib.h - Functions for doing various "things" to processes//============================================================================#ifndef __PROCESSLIB_H__#define __PROCESSLIB_H__#include #include //============================================================================// Structures//============================================================================struct ImportedFunction{	std::string strName;	// Function name	size_t dwOrdinal;		// Function ordinal; only valid if name is empty	size_t dwThunkAddress;	// Address of the thunk instruction	size_t dwTargetAddress;	// Address of the actual function (In the target DLL)};struct DLLImport{	std::string strName;						// DLL name	std::vector vFunctions;	// Functions imported};struct HookedFunction{	size_t dwOriginalThunkAddr;	// Original import table thunk address in process	size_t dwOriginalThunk;		// Original import table thunk value	void* pLoaderThunk;			// Address of new loader code (From VirtualAllocEx)};#endif // __PROCESSLIB_H__


Source file:
//============================================================================// ProcessLib.cpp - Main exports//============================================================================#define _WIN32_WINNT 0x0500#include "ProcessLib.h"#include #include #pragma warning (disable:4312) // 'type cast' : conversion from 'DWORD' to 'void *' of greater size#pragma warning (disable:4311) // 'type cast' : 'type cast' : pointer truncation//============================================================================// DLL loader function, copied into target process address space// Loads szDLLName, gets the address of szFuncName, and jumps to it// szDLLName and szFuncName are resolved at function call time, and the values// are inserted into the loader code.//============================================================================#define LOADLIB_TOKEN 0x01010101			// LoadLibrary() address#define GETPROCADDRESS_TOKEN 0x02020202		// GetProcAddress() address#define FREELIB_TOKEN 0x03030303			// FreeLibrary() address#define DLLNAME_TOKEN 0x04040404			// Address of DLL name string#define FUNCNAME_TOKEN 0x05050505			// Address of function name string#define LOADER_EOF_TOKEN 0x0a0a0a0a			// End of loader code#define DECLARE_VARIABLE(name) name: 	__asm _emit 0x00 	__asm _emit 0x00 	__asm _emit 0x00 	__asm _emit 0x00static __declspec(naked) void LoaderStubCode(){	__asm	{		// Store old EBP (part 1)		push ebp		mov ebp, esp		// Get function base offset to access "local" data		call lblStartlblStart:		pop ebp		sub ebp, offset lblStart		// Store old EBP (part 2)		pop eax		mov [ebp+varOldEBP], eax		// Store function return address		pop ebx		mov [ebp+varFuncRetAddr], ebx		// Call LoadLibrary() to load target DLL		push DLLNAME_TOKEN		mov eax, LOADLIB_TOKEN		call eax		mov [ebp+varTargetHMod], eax // Store target DLL HMODULE		// Call GetProcAddress()		push FUNCNAME_TOKEN		push eax		mov eax, GETPROCADDRESS_TOKEN		call eax		// Call function		call eax		// Restore stack frame and return address		mov ebx, [ebp+varFuncRetAddr]		push ebx;		push eax // Store function's return value on the stack		// Call FreeLibrary to free the target DLL		push [ebp+varTargetHMod]		mov eax, FREELIB_TOKEN		call eax		mov ebp, [ebp+varOldEBP]		// Restore function return value and stack frame, then return		pop eax		retDECLARE_VARIABLE(varTargetHMod)DECLARE_VARIABLE(varFuncRetAddr)DECLARE_VARIABLE(varOldEBP)		// EOF for loader code		_emit LOADER_EOF_TOKEN&0xff		_emit (LOADER_EOF_TOKEN>>8)&0xff		_emit (LOADER_EOF_TOKEN>>16)&0xff		_emit (LOADER_EOF_TOKEN>>24)&0xff	}}inline void FixupToken(BYTE* pStart, DWORD dwToken, DWORD dwValue){	while(*(DWORD*)pStart != LOADER_EOF_TOKEN)	{		while(*(DWORD*)pStart != dwToken && *(DWORD*)pStart != LOADER_EOF_TOKEN)			++pStart;		if(*(DWORD*)pStart != LOADER_EOF_TOKEN)		{			*(DWORD*)pStart = dwValue;			pStart += 4;		}	}}//============================================================================// Read a string from a process memory space//============================================================================bool ReadSting(HANDLE hProcess, void* pStartAddress, std::string& str){	BYTE* pAddress = (BYTE*)pStartAddress;	str = "";	for(;;)	{		// Read a chunk of data		char szBuff[65];		DWORD dwBytes;		if(!ReadProcessMemory(hProcess, pAddress, szBuff, sizeof(szBuff)-1, &dwBytes))			return false;		szBuff[63] = 0;		// Add it to the buffer		str += szBuff;		// Exit if there's a null terminator in it		bool bBreak = false;		for(size_t i=0; i<sizeof(szBuff)-1; ++i)		{			if(szBuff == 0)			{				bBreak = true;				break;			}		}		if(bBreak)			break;		pAddress += sizeof(szBuff)-1;	}	return true;}//============================================================================// Read all DLL imports from a process//============================================================================bool ReadImports(HANDLE hThread, HANDLE hProcess, std::vector& vImports,		std::string* pstrError){	// Get thread context	CONTEXT theContext;	theContext.ContextFlags = CONTEXT_FULL;	if(!GetThreadContext(hThread, &theContext))	{		if(pstrError)			*pstrError = "GetThreadContext() failed\n";		return false;	}	// Get address of the FS segment	LDT_ENTRY selEntry;	if(!GetThreadSelectorEntry(hThread, theContext.SegFs, &selEntry))	{		if(pstrError)			*pstrError = "GetThreadSelectorEntry() failed\n";		return false;	}	DWORD dwFSBase = (selEntry.HighWord.Bits.BaseHi << 24) |		(selEntry.HighWord.Bits.BaseMid << 16) |		selEntry.BaseLow;	// Read thread/process environment block to get image base address	DWORD dwBaseAddress=0;	DWORD dwBytes;	{		// Read entire TEB		TEB theTEB;		if(!ReadProcessMemory(hProcess, (void*)dwFSBase, &theTEB, sizeof(theTEB), &dwBytes) ||			dwBytes!=sizeof(theTEB))		{			if(pstrError)				*pstrError = "ReadProcessMemory() failed\n";			return false;		}		// PEB address is in TEB at byte offset 0x30		DWORD dwPEBAddress = *(DWORD*)(((BYTE*)&theTEB)+0x30);		// Base address is in the PEB at offset 0x08		if(!ReadProcessMemory(hProcess, (void*)(dwPEBAddress+8), &dwBaseAddress, sizeof(dwBaseAddress), &dwBytes) ||			dwBytes!=sizeof(dwBaseAddress))		{			if(pstrError)				*pstrError = "ReadProcessMemory() failed\n";			return false;		}	}	// Read the DOS header	IMAGE_DOS_HEADER dosHeader;	if(!ReadProcessMemory(hProcess, (void*)dwBaseAddress, &dosHeader, sizeof(dosHeader), &dwBytes) ||		dwBytes!=sizeof(dosHeader))	{		if(pstrError)			*pstrError = "ReadProcessMemory() failed\n";		return false;	}	// Validate it	if(dosHeader.e_magic != IMAGE_DOS_SIGNATURE)	{		if(pstrError)			*pstrError = "Processes DOS header magic number is invalid\n";		return false;	}	// Read NT header	IMAGE_NT_HEADERS ntHeader;	if(!ReadProcessMemory(hProcess, (void*)(dwBaseAddress+dosHeader.e_lfanew), &ntHeader, sizeof(ntHeader), &dwBytes) ||		dwBytes!=sizeof(ntHeader))	{		if(pstrError)			*pstrError = "ReadProcessMemory() failed\n";		return false;	}	// Validate it	if(ntHeader.Signature != IMAGE_NT_SIGNATURE)	{		if(pstrError)			*pstrError = "Processes NT header magic number is invalid\n";		return false;	}	if(ntHeader.OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_IMPORT)	{		if(pstrError)			*pstrError = "Process does not have an import table\n";		return false;	}	// Read the import descriptor table	vImports.clear();	for(size_t i=0; ; ++i)	{		// For each DLL...		IMAGE_IMPORT_DESCRIPTOR imports;		IMAGE_DATA_DIRECTORY& iat = ntHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];		if(!ReadProcessMemory(hProcess, (void*)(dwBaseAddress+iat.VirtualAddress+i*sizeof(imports)),			&imports, sizeof(imports), &dwBytes) || dwBytes!=sizeof(imports))		{			if(pstrError)				*pstrError = "ReadProcessMemory() failed\n";			return false;		}		// Is this the end of the list?		if(imports.OriginalFirstThunk == 0 && imports.FirstThunk == 0)			break;		// Grab DLL name		DLLImport theImport;		if(!ReadSting(hProcess, (void*)(dwBaseAddress+imports.Name), theImport.strName))		{			if(pstrError)				*pstrError = "ReadProcessMemory() failed\n";			return false;		}		// For each function in the DLL...		for(size_t j=0; ; ++j)		{			// Read the thunk			IMAGE_THUNK_DATA theThunk;			if(!ReadProcessMemory(hProcess, (void*)(dwBaseAddress+imports.OriginalFirstThunk+j*sizeof(theThunk)),				&theThunk, sizeof(theThunk), &dwBytes) || dwBytes!=sizeof(theThunk))			{				if(pstrError)					*pstrError = "ReadProcessMemory() failed\n";				return false;			}			// Is this the end of the list?			if(theThunk.u1.AddressOfData == 0)				break;			// Is this a thunk by ordinal?			ImportedFunction func;			if(theThunk.u1.Ordinal & 0x80000000)			{				func.dwOrdinal = theThunk.u1.Ordinal&0x7fffffff;			}			else			{				// Grab function name				if(!ReadSting(hProcess, (void*)(dwBaseAddress+theThunk.u1.ForwarderString+2), func.strName))				{					if(pstrError)						*pstrError = "ReadProcessMemory() failed\n";					return false;				}				func.dwOrdinal = 0;			}			// Read the actual import table entry			func.dwThunkAddress = dwBaseAddress+imports.FirstThunk+j*sizeof(theThunk);			if(!ReadProcessMemory(hProcess, (void*)func.dwThunkAddress, &theThunk, sizeof(theThunk), &dwBytes) ||				dwBytes!=sizeof(theThunk))			{				if(pstrError)					*pstrError = "ReadProcessMemory() failed\n";				return false;			}			// Should never happen, but check to be sure...			if(theThunk.u1.AddressOfData == 0)				break;			// Store address in function list			func.dwTargetAddress = theThunk.u1.AddressOfData;			theImport.vFunctions.push_back(func);		}		vImports.push_back(theImport);	}/*	// Dump all imports (DEBUG)	for(size_t i=0; i	{		OutputDebugString(vImports.strName.c_str());		OutputDebugString(":\n");		for(size_t j=0; j.vFunctions.size(); ++j)		{			OutputDebugString("+ ");			OutputDebugString(vImports.vFunctions[j].strName.c_str());			OutputDebugString("\n");		}		OutputDebugString("\n");	}*/	return true;}//============================================================================// Hook a function in the target process//============================================================================bool HookFunction(HANDLE hProcess, HookedFunction& funcInfo, const std::vector& vImports,				   const std::string& strDllName, const std::string& strFuncName,				   const std::string& strNewDllName, const std::string& strNewFuncName,				   std::string* pstrError){	// Check WE can load the new DLL and function, because if not, the target process won't...	HMODULE hModTarget = LoadLibraryA(strNewDllName.c_str());	if(!hModTarget)	{		if(pstrError)			*pstrError = "Failed to load " + strNewDllName + "\n";		return false;	}	if(!GetProcAddress(hModTarget, strNewFuncName.c_str()))	{		if(pstrError)			*pstrError = "Failed to find " + strNewFuncName + "\n";		return false;	}	FreeLibrary(hModTarget);	// Find the function to hook	const ImportedFunction* pFunc = NULL;	for(size_t i=0; i	{		if(_stricmp(vImports.strName.c_str(), strDllName.c_str()) == 0)		{			for(size_t j=0; j.vFunctions.size(); ++j)			{				if(vImports.vFunctions[j].strName == strFuncName.c_str())				{					pFunc = &vImports.vFunctions[j];					break;				}			}			break;		}	}	if(!pFunc)	{		if(pstrError)			*pstrError = "Failed to find " + strDllName + ":" + strFuncName + " to hook\n";		return false;	}	// Write the loader into the process address space	BYTE* pProcessMem;	DWORD dwBytes;	{		// Get length of loader code		BYTE* pLoaderCode = (BYTE*)&LoaderStubCode		DWORD dwLoaderCodeLen=0;		while(*(DWORD*)(pLoaderCode+dwLoaderCodeLen) != LOADER_EOF_TOKEN)			++dwLoaderCodeLen;		dwLoaderCodeLen += 4;		// Allocate memory for loader + data block		DWORD dwDataBlockLen = (DWORD)(strNewDllName.length()+1 + strNewFuncName.length()+1);		DWORD dwSize = dwLoaderCodeLen + dwDataBlockLen;		BYTE* pMemory = new BYTE[dwSize];		// Copy in loader code		memcpy(pMemory, pLoaderCode, dwLoaderCodeLen);		// Copy in DLL name		DWORD dwDLLNameOffset = dwLoaderCodeLen;		memcpy(pMemory + dwDLLNameOffset, strNewDllName.c_str(), strNewDllName.length()+1);		// Copy in function name		DWORD dwFuncNameOffset = dwLoaderCodeLen + strNewDllName.length()+1;		memcpy(pMemory + dwFuncNameOffset, strNewFuncName.c_str(), strNewFuncName.length()+1);		// Fixup function call tokens		FixupToken(pMemory, LOADLIB_TOKEN, (DWORD)&LoadLibrary);		FixupToken(pMemory, GETPROCADDRESS_TOKEN, (DWORD)&GetProcAddress);		FixupToken(pMemory, FREELIB_TOKEN, (DWORD)&FreeLibrary);		// Allocate buffer for loader		pProcessMem = (BYTE*)VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);		if(!pProcessMem)		{			if(pstrError)				*pstrError = "VirtualAllocEx() failed\n";			delete[] pMemory;			return false;		}		// Fixup string tokens		FixupToken(pMemory, DLLNAME_TOKEN, (DWORD)(pProcessMem+dwDLLNameOffset));		FixupToken(pMemory, FUNCNAME_TOKEN, (DWORD)(pProcessMem+dwFuncNameOffset));		// Copy buffer to target process		if(!WriteProcessMemory(hProcess, pProcessMem, pMemory, dwSize, &dwBytes) || dwBytes != dwSize)		{			if(pstrError)				*pstrError = "WriteProcessMemory () failed\n";			delete[] pMemory;			return false;		}		delete[] pMemory;	}	// Fill in function information	funcInfo.dwOriginalThunkAddr = pFunc->dwThunkAddress;	funcInfo.dwOriginalThunk = pFunc->dwTargetAddress;	funcInfo.pLoaderThunk = pProcessMem;	// Write the new thunk location	if(!WriteProcessMemory(hProcess, (void*)pFunc->dwThunkAddress, &pProcessMem, sizeof(pProcessMem), &dwBytes)		|| dwBytes != sizeof(pProcessMem))	{		if(pstrError)			*pstrError = "WriteProcessMemory () failed\n";		return false;	}	return true;}//============================================================================// Unhook a function in the target process//============================================================================bool UnhookFunction(HANDLE hProcess, const HookedFunction& funcInfo){	// Revert the thunk	DWORD dwBytes;	WriteProcessMemory(hProcess, (void*)funcInfo.dwOriginalThunkAddr, &funcInfo.dwOriginalThunk,		sizeof(DWORD), &dwBytes);	// Free the space used by the loader	VirtualFreeEx(hProcess, funcInfo.pLoaderThunk, 0, MEM_RELEASE);	return true;}

It's a bit of a pain to read through, but it's pretty well commented. There's also probably quite a lot of bugs in it, but I'll worry about them later.

As for what it does: You use the API to get all exports in a process (And I'll add support for all exports in a .exe file too at some point), then hook the import table to point at some loader code. The loader code is allocated in the target process using VirtualAllocEx(), and it loads a specified DLL, and calls a function in it - passing the parameters passed to the loader (Which would be the parameters passed to the hooked function) - and then unloads the DLL. That lets me quickly change the DLL without having to run code to unload the DLL first. If the loaded DLL wants to stick around, it can always call LoadLibrary() on itself (I think anyway).

Anyway, I'll dump something in my journal once I have the x64 version working (Or I give up), and I get all the code tidied up.

BED.
Previous Entry Untitled
Next 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