From 6adafd598b4ee659cdfd37cb1a2ee8a08447f40a Mon Sep 17 00:00:00 2001 From: Jacob Farnsworth <6502enthusiast@gmail.com> Date: Wed, 2 Apr 2025 16:47:04 +0200 Subject: [PATCH] Unify heap API hooks, address debug CRT init issues --- qiling/os/windows/dlls/kernel32/heapapi.py | 55 +++++-- qiling/os/windows/dlls/msvcrt.py | 163 +++++++++------------ 2 files changed, 119 insertions(+), 99 deletions(-) diff --git a/qiling/os/windows/dlls/kernel32/heapapi.py b/qiling/os/windows/dlls/kernel32/heapapi.py index 6746c6d8..871a933f 100644 --- a/qiling/os/windows/dlls/kernel32/heapapi.py +++ b/qiling/os/windows/dlls/kernel32/heapapi.py @@ -49,6 +49,17 @@ def hook_HeapCreate(ql: Qiling, address: int, params): return ql.os.heap.alloc(dwInitialSize) +def _HeapAlloc(ql: Qiling, address: int, params): + dwFlags = params["dwFlags"] + dwBytes = params["dwBytes"] + + ptr = ql.os.heap.alloc(dwBytes) + + if ptr and (dwFlags & HEAP_ZERO_MEMORY): + __zero_mem(ql.mem, ptr, dwBytes) + + return ptr + # DECLSPEC_ALLOCATOR LPVOID HeapAlloc( # HANDLE hHeap, # DWORD dwFlags, @@ -60,15 +71,7 @@ def hook_HeapCreate(ql: Qiling, address: int, params): 'dwBytes' : SIZE_T }) def hook_HeapAlloc(ql: Qiling, address: int, params): - dwFlags = params["dwFlags"] - dwBytes = params["dwBytes"] - - ptr = ql.os.heap.alloc(dwBytes) - - if ptr and (dwFlags & HEAP_ZERO_MEMORY): - __zero_mem(ql.mem, ptr, dwBytes) - - return ptr + return _HeapAlloc(ql, address, params) # DECLSPEC_ALLOCATOR LPVOID HeapReAlloc( # HANDLE hHeap, @@ -86,6 +89,14 @@ def hook_HeapReAlloc(ql: Qiling, address: int, params): base = params["lpMem"] newSize = params["dwBytes"] + if not base: + return _HeapAlloc(ql, address, params) + + if newSize == 0: + ql.os.heap.free(base) + + return 0 + oldSize = ql.os.heap.size(base) oldData = bytes(ql.mem.read(base, oldSize)) @@ -150,3 +161,29 @@ def hook_HeapSetInformation(ql: Qiling, address: int, params): @winsdkapi(cc=STDCALL, params={}) def hook_GetProcessHeap(ql: Qiling, address: int, params): return ql.os.heap.start_address + +# BOOL HeapValidate( +# HANDLE hHeap, +# DWORD dwFlags, +# LPCVOID lpMem +# ); +@winsdkapi(cc=STDCALL, params={ + 'hHeap': PVOID, + 'dwFlags': DWORD, + 'lpMem': PVOID +}) +def hook_HeapValidate(ql: Qiling, address: int, params): + hHeap = params['hHeap'] + lpMem = params['lpMem'] + + if not hHeap: + return 0 + + # TODO: Maybe _find is a heap manager implementation + # detail, in which case we shouldn't rely on it. + chunk = ql.os.heap._find(lpMem) + + if not chunk: + return 0 + + return chunk.inuse diff --git a/qiling/os/windows/dlls/msvcrt.py b/qiling/os/windows/dlls/msvcrt.py index b209b20d..763e74bd 100644 --- a/qiling/os/windows/dlls/msvcrt.py +++ b/qiling/os/windows/dlls/msvcrt.py @@ -135,10 +135,9 @@ def hook__controlfp(ql: Qiling, address: int, params): # ); @winsdkapi(cc=CDECL, params={ 'func' : POINTER -}) +}, passthru=True) def hook_atexit(ql: Qiling, address: int, params): - ret = 0 - return ret + return # char*** __p__environ(void) @winsdkapi(cc=CDECL, params={}) @@ -211,20 +210,12 @@ def hook___p___argc(ql: Qiling, address: int, params): return ret # TODO: this one belongs to ucrtbase.dll -@winsdkapi(cc=CDECL, params={}) +@winsdkapi(cc=CDECL, params={}, passthru=True) def hook__get_initial_narrow_environment(ql: Qiling, address: int, params): - ret = 0 - - for i, (k, v) in enumerate(ql.env.items()): - entry = bytes(f'{k}={v}', 'ascii') + b'\x00' - p_entry = ql.os.heap.alloc(len(entry)) - - ql.mem.write(p_entry, entry) - - if i == 0: - ret = p_entry - - return ret + # If the native version of this function does not + # get to run, then debug versions of the CRT DLLs can fail + # their initialization. + return # int sprintf ( char * str, const char * format, ... ); @winsdkapi(cc=CDECL, params={ @@ -339,6 +330,18 @@ def __stdio_common_vsprintf(ql: Qiling, address: int, params, wstring: bool): def hook___stdio_common_vsprintf(ql: Qiling, address: int, params): return __stdio_common_vsprintf(ql, address, params, False) +@winsdkapi(cc=CDECL, params={ + '_Options' : PARAM_INT64, + '_Buffer' : POINTER, + '_BufferCount' : SIZE_T, + '_MaxCount' : SIZE_T, + '_Format' : STRING, + '_Locale' : DWORD, + '_ArgList' : POINTER +}) +def hook___stdio_common_vsnprintf(ql: Qiling, address: int, params): + return __stdio_common_vsprintf(ql, address, params, False) + @winsdkapi(cc=CDECL, params={ '_Options' : PARAM_INT64, '_Buffer' : POINTER, @@ -350,6 +353,18 @@ def hook___stdio_common_vsprintf(ql: Qiling, address: int, params): def hook___stdio_common_vswprintf(ql: Qiling, address: int, params): return __stdio_common_vsprintf(ql, address, params, True) +@winsdkapi(cc=CDECL, params={ + '_Options' : PARAM_INT64, + '_Buffer' : POINTER, + '_BufferCount' : SIZE_T, + '_MaxCount' : SIZE_T, + '_Format' : WSTRING, + '_Locale' : DWORD, + '_ArgList' : POINTER +}) +def hook___stdio_common_vsnwprintf(ql: Qiling, address: int, params): + return __stdio_common_vsprintf(ql, address, params, True) + # all the "_s" versions are aliases to their non-"_s" counterparts @winsdkapi(cc=CDECL, params={ @@ -383,6 +398,18 @@ def hook___stdio_common_vfwprintf_s(ql: Qiling, address: int, params): def hook___stdio_common_vsprintf_s(ql: Qiling, address: int, params): return hook___stdio_common_vsprintf.__wrapped__(ql, address, params) +@winsdkapi(cc=CDECL, params={ + '_Options' : PARAM_INT64, + '_Buffer' : POINTER, + '_BufferCount' : SIZE_T, + '_MaxCount' : SIZE_T, + '_Format' : STRING, + '_Locale' : DWORD, + '_ArgList' : POINTER +}) +def hook___stdio_common_vsnprintf_s(ql: Qiling, address: int, params): + return hook___stdio_common_vsnprintf.__wrapped__(ql, address, params) + @winsdkapi(cc=CDECL, params={ '_Options' : PARAM_INT64, '_Buffer' : POINTER, @@ -394,6 +421,18 @@ def hook___stdio_common_vsprintf_s(ql: Qiling, address: int, params): def hook___stdio_common_vswprintf_s(ql: Qiling, address: int, params): return hook___stdio_common_vswprintf.__wrapped__(ql, address, params) +@winsdkapi(cc=CDECL, params={ + '_Options' : PARAM_INT64, + '_Buffer' : POINTER, + '_BufferCount' : SIZE_T, + '_MaxCount' : SIZE_T, + '_Format' : WSTRING, + '_Locale' : DWORD, + '_ArgList' : POINTER +}) +def hook___stdio_common_vsnwprintf_s(ql: Qiling, address: int, params): + return hook___stdio_common_vsnwprintf.__wrapped__(ql, address, params) + @winsdkapi(cc=CDECL, params={}) def hook___lconv_init(ql: Qiling, address: int, params): return 0 @@ -449,50 +488,18 @@ def hook_strncmp(ql: Qiling, address: int, params): return result -def __malloc(ql: Qiling, address: int, params): - size = params['size'] - - return ql.os.heap.alloc(size) - @winsdkapi(cc=CDECL, params={ 'size' : UINT -}) +}, passthru=True) def hook__malloc_base(ql: Qiling, address: int, params): - return __malloc(ql, address, params) + return # void* malloc(unsigned int size) @winsdkapi(cc=CDECL, params={ 'size' : UINT -}) +}, passthru=True) def hook_malloc(ql: Qiling, address: int, params): - size = params['size'] - - return ql.os.heap.alloc(size) - -def __realloc(ql: Qiling, address: int, params): - block = params['block'] - size = params['size'] - - if not block: - return ql.os.heap.alloc(size) - - if size == 0: - ql.os.heap.free(block) - return 0 - - oldSize = ql.os.heap.size(block) - oldData = bytes(ql.mem.read(block, size)) - ql.os.heap.free(block) - - if size < oldSize: - oldData = oldData[0:size] - - newBase = ql.os.heap.alloc(size) - - if newBase: - ql.mem.write(newBase, oldData) - - return newBase + return # void* __cdecl _realloc_base( # void* const block, @@ -501,27 +508,22 @@ def __realloc(ql: Qiling, address: int, params): @winsdkapi(cc=CDECL, params={ 'block' : POINTER, 'size' : UINT -}) +}, passthru=True) def hook__realloc_base(ql: Qiling, address: int, params): - return __realloc(ql, address, params) - -def __free(ql: Qiling, address: int, params): - address = params['address'] - - ql.os.heap.free(address) + return @winsdkapi(cc=CDECL, params={ 'address': POINTER -}) +}, passthru=True) def hook__free_base(ql: Qiling, address: int, params): - return __free(ql, address, params) + return # void* free(void *address) @winsdkapi(cc=CDECL, params={ 'address': POINTER -}) +}, passthru=True) def hook_free(ql: Qiling, address: int, params): - return __free(ql, address, params) + return # _onexit_t _onexit( # _onexit_t function @@ -546,32 +548,16 @@ def hook__onexit(ql: Qiling, address: int, params): 'dest' : POINTER, 'c' : INT, 'count' : SIZE_T -}) +}, passthru=True) def hook_memset(ql: Qiling, address: int, params): - dest = params["dest"] - c = params["c"] - count = params["count"] - - ql.mem.write(dest, bytes([c] * count)) - - return dest - -def __calloc(ql: Qiling, address: int, params): - num = params['num'] - size = params['size'] - - count = num * size - ret = ql.os.heap.alloc(count) - ql.mem.write(ret, bytes([0] * count)) - - return ret + return @winsdkapi(cc=CDECL, params={ 'num' : SIZE_T, 'size' : SIZE_T -}) +}, passthru=True) def hook__calloc_base(ql: Qiling, address: int, params): - return __calloc(ql, address, params) + return # void *calloc( # size_t num, @@ -580,9 +566,9 @@ def hook__calloc_base(ql: Qiling, address: int, params): @winsdkapi(cc=CDECL, params={ 'num' : SIZE_T, 'size' : SIZE_T -}) +}, passthru=True) def hook_calloc(ql: Qiling, address: int, params): - return __calloc(ql, address, params) + return # void * memmove( # void *dest, @@ -593,12 +579,9 @@ def hook_calloc(ql: Qiling, address: int, params): 'dest' : POINTER, 'src' : POINTER, 'num' : SIZE_T -}) +}, passthru=True) def hook_memmove(ql: Qiling, address: int, params): - data = ql.mem.read(params['src'], params['num']) - ql.mem.write(params['dest'], bytes(data)) - - return params['dest'] + return # int _ismbblead( # unsigned int c