#include "stdafx.h" #include #include "dbghelpapi.h" #define BLOCKMAPRESERVE 64 // This should strike a balance between memory use and a desire to minimize heap hits. #define HEAPMAPRESERVE 2 // Usually there won't be more than a few heaps in the process, so this should be small. #define MAXSYMBOLNAMELENGTH 256 // Maximum symbol name length that we will allow. Longer names will be truncated. #define MODULESETRESERVE 16 // There are likely to be several modules loaded in the process. typedef struct patchentry_s { LPCSTR exportmodulename; // The name of the module exporting the patched API. LPCSTR importname; // The name (or ordinal) of the imported API being patched. LPSTR modulepath; // (Optional) module path. If the module is loaded, then VLD will fill this in at startup. LPCVOID replacement; // Pointer to the function to which the imported API should be patched through to. } patchentry_t; static HMODULE __dbghelp; ////////////////////////////////////////////////////////////////////////// static BOOL linkdebughelplibrary (void) { TCHAR dbghelppath [MAX_PATH] = { 0 }; LPCSTR functionname; DWORD length = MAX_PATH; strncpy(dbghelppath, "dbghelp.dll", MAX_PATH); // Load the copy of dbghelp.dll installed by Visual Leak Detector. __dbghelp = LoadLibrary(dbghelppath); if (__dbghelp == NULL) abort(); // Obtain pointers to the exported functions that we will be using. functionname = "EnumerateLoadedModules"; if ((pEnumerateLoadedModules = (EnumerateLoadedModules_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "ImageDirectoryEntryToDataEx"; if ((pImageDirectoryEntryToDataEx = (ImageDirectoryEntryToDataEx_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "StackWalk"; if ((pStackWalk = (StackWalk_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymCleanup"; if ((pSymCleanup = (SymCleanup_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymFromAddr"; if ((pSymFromAddr = (SymFromAddr_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymFunctionTableAccess"; if ((pSymFunctionTableAccess = (SymFunctionTableAccess_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymGetLineFromAddr"; if ((pSymGetLineFromAddr = (SymGetLineFromAddr_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymGetModuleBase"; if ((pSymGetModuleBase = (SymGetModuleBase_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymInitialize"; if ((pSymInitialize = (SymInitialize_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymLoadModule"; if ((pSymLoadModule = (SymLoadModule_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymGetModuleInfo"; if ((pSymGetModuleInfo = (SymGetModuleInfo_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymSetOptions"; if ((pSymSetOptions = (SymSetOptions_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } functionname = "SymUnloadModule"; if ((pSymUnloadModule = (SymUnloadModule_t) GetProcAddress(__dbghelp, functionname)) == NULL) { goto getprocaddressfailure; } return TRUE; getprocaddressfailure: // One of the required exports was not found. abort(); return FALSE; } ////////////////////////////////////////////////////////////////////////// typedef LONG (__stdcall *LdrLoadDll_t) (LPSTR, PDWORD, LPSTR, PHANDLE); typedef LPVOID (__stdcall *RtlAllocateHeap_t) (HANDLE, DWORD, SIZE_T); typedef BOOL (__stdcall *RtlFreeHeap_t) (HANDLE, DWORD, LPVOID); typedef LPVOID (__stdcall *RtlReAllocateHeap_t) (HANDLE, DWORD, LPVOID, SIZE_T); static HANDLE currentprocess; // Pseudo-handle for the current process. static HANDLE currentthread; // Pseudo-handle for the current thread. static HANDLE processheap; // Handle to the process's heap (COM allocations come from here). static LdrLoadDll_t LdrLoadDll; static RtlAllocateHeap_t RtlAllocateHeap; static RtlFreeHeap_t RtlFreeHeap; static RtlReAllocateHeap_t RtlReAllocateHeap; typedef void* (__cdecl *_calloc_dbg_t) (size_t, size_t, int, const char*, int); typedef void* (__cdecl *_malloc_dbg_t) (size_t, int, const char *, int); typedef void* (__cdecl *_realloc_dbg_t) (void *, size_t, int, const char *, int); typedef void* (__cdecl *calloc_t) (size_t, size_t); typedef void* (__cdecl *malloc_t) (size_t); typedef void* (__cdecl *realloc_t) (void *, size_t); static _calloc_dbg_t pcrtd__calloc_dbg = NULL; static _malloc_dbg_t pcrtd__malloc_dbg = NULL; static _realloc_dbg_t pcrtd__realloc_dbg = NULL; static CRITICAL_SECTION loadLock; ////////////////////////////////////////////////////////////////////////// #define R2VA(modulebase, rva) (((PBYTE)modulebase) + rva) // Relative Virtual Address to Virtual Address conversion. static BOOL patchimport (HMODULE importmodule, LPCSTR exportmodulename, LPCSTR exportmodulepath, LPCSTR importname, LPCVOID replacement) { HMODULE exportmodule; IMAGE_THUNK_DATA *iate; IMAGE_IMPORT_DESCRIPTOR *idte; FARPROC import; DWORD protect; IMAGE_SECTION_HEADER *section; ULONG size; assert(exportmodulename != NULL); // Locate the importing module's Import Directory Table (IDT) entry for the // exporting module. The importing module actually can have several IATs -- // one for each export module that it imports something from. The IDT entry // gives us the offset of the IAT for the module we are interested in. idte = (IMAGE_IMPORT_DESCRIPTOR*)pImageDirectoryEntryToDataEx( (PVOID)importmodule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size, §ion); if (idte == NULL) { // This module has no IDT (i.e. it imports nothing). return FALSE; } while (idte->OriginalFirstThunk != 0x0) { if (_stricmp((PCHAR)R2VA(importmodule, idte->Name), exportmodulename) == 0) { // Found the IDT entry for the exporting module. break; } idte++; } if (idte->OriginalFirstThunk == 0x0) { // The importing module does not import anything from the exporting // module. return FALSE; } // Get the *real* address of the import. If we find this address in the IAT, // then we've found the entry that needs to be patched. if (exportmodulepath != NULL) { // Always try to use the full path if available. There seems to be a bug // (or is this a new feature of the "side-by-side" kruft?) where // GetModuleHandle sometimes fails if the full path is not supplied for // DLLs in the side-by-side cache. exportmodule = GetModuleHandle(exportmodulepath); } else { // No full path available. Try using just the module name by itself. exportmodule = GetModuleHandle(exportmodulename); } assert(exportmodule != NULL); import = GetProcAddress(exportmodule, importname); assert(import != NULL); // Perhaps the named export module does not actually export the named import? // Locate the import's IAT entry. iate = (IMAGE_THUNK_DATA*)R2VA(importmodule, idte->FirstThunk); while (iate->u1.Function != 0x0) { if (iate->u1.Function == (DWORD_PTR)import) { // Found the IAT entry. Overwrite the address stored in the IAT // entry with the address of the replacement. Note that the IAT // entry may be write-protected, so we must first ensure that it is // writable. VirtualProtect(&iate->u1.Function, sizeof(iate->u1.Function), PAGE_READWRITE, &protect); iate->u1.Function = (DWORD_PTR) replacement; VirtualProtect(&iate->u1.Function, sizeof(iate->u1.Function), protect, &protect); // The patch has been installed in the import module. return TRUE; } iate++; } // The import's IAT entry was not found. The importing module does not // import the specified import. return FALSE; } static BOOL patchmodule (HMODULE importmodule, patchentry_t patchtable [], UINT tablesize) { patchentry_t *entry; UINT index; BOOL patched = FALSE; // Loop through the import patch table, individually patching each import // listed in the table. for (index = 0; index < tablesize; index++) { entry = &patchtable[index]; if (patchimport(importmodule, entry->exportmodulename, entry->modulepath, entry->importname, entry->replacement) == TRUE) { patched = TRUE; } } return patched; } ////////////////////////////////////////////////////////////////////////// static HANDLE _HeapCreate (DWORD options, SIZE_T initsize, SIZE_T maxsize) { HANDLE heap; // Create the heap. heap = HeapCreate(options, initsize, maxsize); return heap; } static BOOL _HeapDestroy (HANDLE heap) { return HeapDestroy(heap); } static LPVOID _RtlAllocateHeap (HANDLE heap, DWORD flags, SIZE_T size) { LPVOID block; // Allocate the block. //heap = processheap; if (heap == 0) return 0; block = RtlAllocateHeap(heap, flags, size); return block; } static BOOL _RtlFreeHeap (HANDLE heap, DWORD flags, LPVOID mem) { BOOL status; status = RtlFreeHeap(heap, flags, mem); return status; } static LPVOID _RtlReAllocateHeap (HANDLE heap, DWORD flags, LPVOID mem, SIZE_T size) { LPVOID newmem; // Reallocate the block. newmem = RtlReAllocateHeap(heap, flags, mem, size); return newmem; } static patchentry_t __hook_table[] = { // Win32 heap APIs. //"kernel32.dll", "GetProcAddress", NULL, _GetProcAddress, // Not heap related, but can be used to obtain pointers to heap functions. { "kernel32.dll", "HeapAlloc", NULL, _RtlAllocateHeap }, { "kernel32.dll", "HeapCreate", NULL, _HeapCreate }, { "kernel32.dll", "HeapDestroy", NULL, _HeapDestroy }, { "kernel32.dll", "HeapFree", NULL, _RtlFreeHeap }, { "kernel32.dll", "HeapReAlloc", NULL, _RtlReAllocateHeap }, }; static BOOL CALLBACK attachtomodule(PSTR modulepath, ULONG modulebase, ULONG modulesize, PVOID userContext) { UINT tablesize = sizeof(__hook_table) / sizeof(patchentry_t); // Attach to the module. patchmodule((HMODULE)modulebase, __hook_table, tablesize); return (TRUE); } static LONG _LdrLoadDll (LPSTR searchpath, PDWORD flags, LPSTR modulename, PHANDLE modulehandle) { LONG status; EnterCriticalSection(&loadLock); // Load the DLL. status = LdrLoadDll(searchpath, flags, modulename, modulehandle); if (0 == status) { // Attach to any newly loaded modules. pEnumerateLoadedModules(currentprocess, attachtomodule, NULL); } LeaveCriticalSection(&loadLock); return status; } class CHeapHook { public: CHeapHook() { HMODULE kernel32 = GetModuleHandle("kernel32.dll"); HMODULE ntdll = GetModuleHandle("ntdll.dll"); // Initialize global variables. currentprocess = GetCurrentProcess(); currentthread = GetCurrentThread(); LdrLoadDll = (LdrLoadDll_t)GetProcAddress(ntdll, "LdrLoadDll"); processheap = GetProcessHeap(); RtlAllocateHeap = (RtlAllocateHeap_t)GetProcAddress(ntdll, "RtlAllocateHeap"); RtlFreeHeap = (RtlFreeHeap_t)GetProcAddress(ntdll, "RtlFreeHeap"); RtlReAllocateHeap = (RtlReAllocateHeap_t)GetProcAddress(ntdll, "RtlReAllocateHeap"); InitializeCriticalSection(&loadLock); linkdebughelplibrary(); // Patch into kernel32.dll's calls to LdrLoadDll so that VLD can // dynamically attach to new modules loaded during runtime. //patchimport(kernel32, "ntdll.dll", NULL, "LdrLoadDll", _LdrLoadDll); pEnumerateLoadedModules(currentprocess, &attachtomodule, NULL); } ~CHeapHook() { } }; // The one and only VisualLeakDetector object instance. //__declspec(dllexport) CHeapHook vld; ////////////////////////////////////////////////////////////////////////// #include #include "lib_acl.h" #include "detours.h" #include "heap_hook.h" LPVOID WINAPI Real_HeapAlloc( HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes); BOOL WINAPI Real_HeapFree( HANDLE hHeap, DWORD dwFlags, LPVOID lpMem); LPVOID WINAPI Real_HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes); DETOUR_TRAMPOLINE(LPVOID WINAPI Real_HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes), HeapAlloc); DETOUR_TRAMPOLINE(BOOL WINAPI Real_HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem), HeapFree); DETOUR_TRAMPOLINE(LPVOID WINAPI Real_HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes), HeapReAlloc); BOOL WINAPI DetourFunctionWithTrampoline(PBYTE pbTrampoline, PBYTE pbDetour); LPVOID WINAPI HookHeapAlloc( HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes ); BOOL WINAPI HookHeapFree( HANDLE hHeap, DWORD dwFlags, LPVOID lpMem ); LPVOID WINAPI HookHeapReAlloc( HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes ); LPVOID WINAPI HookHeapAlloc( HANDLE hHeap, // handle to private heap block DWORD dwFlags, // heap allocation control SIZE_T dwBytes // number of bytes to allocate ) { const char* myname = "HookHeapAlloc\r\n"; LPVOID p = Real_HeapAlloc(hHeap, dwFlags, dwBytes); //TraceMemoryAllocation( p, dwBytes, fnHeapAlloc ); //write(1, myname, strlen(myname)); return p; } BOOL WINAPI HookHeapFree( HANDLE hHeap, // handle to heap DWORD dwFlags, // heap free options LPVOID lpMem // pointer to memory ) { BOOL res = Real_HeapFree(hHeap, dwFlags, lpMem); const char* myname = "HookHeapFree\r\n"; //TraceMemoryRelease( lpMem ); //write(1, myname, strlen(myname)); return res; } LPVOID WINAPI HookHeapReAlloc( HANDLE hHeap, // handle to heap block DWORD dwFlags, // heap reallocation options LPVOID lpMem, // pointer to memory to reallocate SIZE_T dwBytes // number of bytes to reallocate ) { const char* myname = "HookHeapReAlloc\r\n"; LPVOID p = Real_HeapReAlloc(hHeap, dwFlags, lpMem, dwBytes); // the previous block was freed //if( !!lpMem ) // TraceMemoryRelease( lpMem ); // new block allocated (may be in same address) //TraceMemoryAllocation( p, dwBytes, fnHeapAlloc ); //write(1, myname, strlen(myname)); return p; } static void* malloc_hook(const char* fname, int lineno, size_t len) { printf(">>>%s\n", __FUNCTION__); void* ptr = Real_HeapAlloc(processheap, 0, len); if (ptr == NULL) { char buf[1024]; int n = WSAGetLastError(); printf("error: %d, %s\n", n, acl_strerror(n, buf, sizeof(buf))); } return ptr; } static void *calloc_hook(const char* fname, int lineno, size_t n, size_t len) { printf(">>>%s\n", __FUNCTION__); void* ptr = Real_HeapAlloc(processheap, 0, n * len); if (ptr == NULL) return NULL; memset(ptr, 0, n * 140); return ptr; } static void *realloc_hook(const char* fname, int lineno, void* ptr, size_t len) { printf(">>>%s\n", __FUNCTION__); void* newptr = Real_HeapReAlloc(processheap, 0, ptr, len); return newptr; } static char *strdup_hook(const char* fname, int lineno, const char* ptr) { printf(">>>%s\n", __FUNCTION__); size_t len = strlen(ptr); char* s = (char*) malloc_hook(fname, lineno, len + 1); memcpy(s, (const char*) ptr, len); s[len] = 0; return s; } static char *strndup_hook(const char* fname, int lineno, const char* ptr, size_t n) { printf(">>>%s\n", __FUNCTION__); size_t len = strlen(ptr); if (len > n) len = n; char* s = (char*) malloc_hook(fname, lineno, len + 1); memcpy(s, (const char*) ptr, len); s[len] = 0; return s; } static void *memdup_hook(const char* fname, int lineno, const void* ptr, size_t n) { printf(">>>%s\n", __FUNCTION__); char* p = (char*) malloc_hook(fname, lineno, n); memcpy(p, (const char*) ptr, n); return p; } static void free_hook(const char* fname, int lineno, void* ptr) { printf(">>>%s\n", __FUNCTION__); Real_HeapFree(processheap, 0, ptr); } CHeapHook2::CHeapHook2() { processheap = GetProcessHeap(); DetourFunctionWithTrampoline((PBYTE)Real_HeapAlloc, (PBYTE)HookHeapAlloc); DetourFunctionWithTrampoline((PBYTE)Real_HeapFree, (PBYTE)HookHeapFree); DetourFunctionWithTrampoline((PBYTE)Real_HeapReAlloc, (PBYTE)HookHeapReAlloc); FILE *fp; AllocConsole(); fp = freopen("CONOUT$","w+t",stdout); acl_mem_hook(malloc_hook, calloc_hook, realloc_hook, strdup_hook, strndup_hook, memdup_hook, free_hook); char* p = acl_mystrdup("hello world!\n"); printf(">>%s\n", p); acl_myfree(p); acl_msg_open("heap.log", "test"); } CHeapHook2::~CHeapHook2() { DetourRemove((PBYTE)Real_HeapAlloc, (PBYTE)HookHeapAlloc); DetourRemove((PBYTE)Real_HeapFree, (PBYTE)HookHeapFree); DetourRemove((PBYTE)Real_HeapReAlloc, (PBYTE)HookHeapReAlloc); } //__declspec(dllexport) CHeapHook2 vld2;