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.