Exetools

Exetools (https://forum.exetools.com/index.php)
-   General Discussion (https://forum.exetools.com/forumdisplay.php?f=2)
-   -   How can I hook DllMain ? (https://forum.exetools.com/showthread.php?t=16463)

ioannis 01-18-2015 16:12

How can I hook DllMain ?
 
I'm playing around with VLD which is inline patching LdrLoadDll in order to hook to dynamic library functions that allocate memory, in order to detect memory leaks.

At this point the dll has not been loaded yet so I dont have a module handle to setup library hooks. Also setting up hooks after the original LdrLoadDll is called means that DllMain has already been called, which might allocate some memory that is not recorded by the leak detector.

My question is, where is the best place to setup the hooks just before the call to DllMain, in order to be able to record memory allocations within DllMain?

DMichael 01-18-2015 16:28

at entrypoint?about memory you can just hook some kernel functions for memory allocation and follow it

ioannis 01-18-2015 16:59

Quote:

Originally Posted by DMichael (Post 96802)
at entrypoint?about memory you can just hook some kernel functions for memory allocation and follow it

If I hook at RtlImageNtHeaderEx, I can get the EntryPoint
0x0FD91154 e9 a7 19 00 00
which is a near relative jump to _DllMainCRTStartup

If i understand correctly i need a long jump (absolute address), which is a 2 byte op code, to enter the hook function in my module. So there is no space to add the additional op code...

__DllMainCRTStartup@12:
0x0FD91154 jmp _DllMainCRTStartup (0FD92B00h)
...
...
_CoGetMalloc@8:
0x0FD91276 jmp CoGetMalloc (0FD91518h)
0x0FD9127B int 3
0x0FD9127C int 3

Can i use the space after _CoGetMalloc@8 to make a near jump instruction there, and then a long jump to my module ?

Also is there any guarantee that there will always be space there to include an additional jump instruction ?

Archer 01-19-2015 01:14

Load your own DLL. At EP of your DLL get the return address from stack. C code can use MSVC intrinsics for this. It'll be address in the system DLL from which all DLL EPs are called. Hook it. Profit?!

Carbon 01-19-2015 01:29

I think the function is called LdrpCallInitRoutine. Just hook it. You can get the address from NTDLL debug symbols.


Code:

BOOLEAN NTAPI LdrpCallInitRoutine        (        IN PDLL_INIT_ROUTINE          EntryPoint,
                IN PVOID          BaseAddress,
                IN ULONG          Reason,
                IN PVOID          Context
        )


deroko 01-20-2015 03:01

You can do it also by hooking NtMapViewOfSection and getting name of mapped section, if it matches wanted dll, look in pe header of mapped dll for entrypoint and hook it :) That's the simplest way.

Somebody in the past also asked how to know when dlls are loaded, and I will also point to same code : http://deroko.phearless.org/itracer.zip <--- look for hook of NtMapViewOfSection. There is detailed code how to find dll name too :)

As you may see from previous answers, there are many ways to do it :)

ioannis 01-22-2015 05:43

I'll have to try every solution more extensively to find the one that requires the least amount of assembly knowledge, before I mark best answer.

I have already tried Archer's suggestion that gives me a pointer inside LdrpCallInitRoutine function at the red line below, so now I need to figure out how to change the function to call and return from my function pointer.
Code:

_LdrpCallInitRoutine@16:
7785998C 55                  push        ebp 
7785998D 8B EC                mov        ebp,esp 
7785998F 56                  push        esi 
77859990 57                  push        edi 
77859991 53                  push        ebx 
77859992 8B F4                mov        esi,esp 
77859994 FF 75 14            push        dword ptr [ebp+14h] 
77859997 FF 75 10            push        dword ptr [ebp+10h] 
7785999A FF 75 0C            push        dword ptr [ebp+0Ch] 
7785999D FF 55 08            call        dword ptr [ebp+8] 
778599A0 8B E6                mov        esp,esi 
778599A2 5B                  pop        ebx 
778599A3 5F                  pop        edi 
778599A4 5E                  pop        esi 
778599A5 5D                  pop        ebp 
778599A6 C2 10 00            ret        10h 
778599A9 90                  nop 
778599AA 90                  nop 
778599AB 90                  nop 
778599AC 90                  nop 
778599AD 90                  nop

I've also tried Carbon's solution but for some reason the following simplified code is failing on me at "SymFromName".
Code:

if (SymInitializeW(g_currentProcess, symbolpath, FALSE)) {
        DWORD64 dwBaseAddress = SymLoadModuleExW(g_currentProcess, NULL, L"ntdll.dll", NULL, (DWORD64)ntdll, NULL, NULL, NULL);

        IMAGEHLP_MODULE64 moduleinfo = { sizeof(IMAGEHLP_MODULE64) };
        BOOL bInfo = SymGetModuleInfo64(g_currentProcess, dwBaseAddress, &moduleinfo);

        TCHAR szSymbolName[MAX_SYM_NAME] = TEXT("LdrpCallInitRoutine");

        ULONG64 buffer[(sizeof(SYMBOL_INFO) +
            MAX_SYM_NAME * sizeof(TCHAR) +
            sizeof(ULONG64) - 1) /
            sizeof(ULONG64)] = { 0 };
        PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
        pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
        pSymbol->MaxNameLen = MAX_SYM_NAME;
        BOOL symfound = SymFromName(g_currentProcess, szSymbolName, pSymbol);
}


Carbon 01-23-2015 02:42

Look here for a simple PDB-GetProcAddress

https://bitbucket.org/NtQuery/pdb-getprocaddress/src/eebe9737d6de34261f6bec5b7b57ae973978c9e2/PDBReader/Source.cpp?at=master

SLV 01-24-2015 22:50

Hook LoadLibraryEx -> check if dll is your -> fix flags to DONT_RESOLVE_DLL_REFERENCES -> call original -> set hooks -> call DllMain.

Carbon 01-25-2015 04:41

Quote:

Originally Posted by SLV (Post 96944)
Hook LoadLibraryEx -> check if dll is your -> fix flags to DONT_RESOLVE_DLL_REFERENCES -> call original -> set hooks -> call DllMain.

This is a bad solution... quote from msdn:

If this value is used, and the executable module is a DLL, the system does not call DllMain for process and thread initialization and termination. Also, the system does not load additional executable modules that are referenced by the specified module.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179%28v=vs.85%29.aspx

SLV 01-28-2015 06:58

Ok, set hooks -> fill dll's imports -> call DllMain :)
There is no legal way to do this without durty hacks.

ioannis 05-09-2015 22:44

I have developed a working solution I wanted to run by your briliant minds for comments, feedback or any other considerations i might have missed.

I used Archer recommendation to get the ReturnAddress and work my way from there by creating a code cave.
This solution should work for all versions of Windows XP+ x86 and x64.

Code:

typedef BOOLEAN(NTAPI *PDLL_INIT_ROUTINE)(IN PVOID DllHandle, IN ULONG Reason, IN PCONTEXT Context OPTIONAL);
BOOLEAN WINAPI LdrpCallInitRoutine(IN PVOID BaseAddress, IN ULONG Reason, IN PVOID Context, IN PDLL_INIT_ROUTINE EntryPoint)
{
#ifdef _DEBUG
    TCHAR szName[MAX_PATH] = { 0 };
    GetModuleFileName((HMODULE)BaseAddress, szName, _countof(szName));
#endif

    return EntryPoint(BaseAddress, Reason, (PCONTEXT)Context);
}

PBYTE NtDllFindDetourAddress(const PBYTE pAddress, SIZE_T dwSize)
{
    MEMORY_BASIC_INFORMATION meminfo = { 0 };
    if (VirtualQuery(pAddress, &meminfo, sizeof(meminfo))) {
        // Find spare bytes at the end of the memory region that are unused
        // so we can jump to this address and set up the detour.
        PBYTE end = (PBYTE)meminfo.BaseAddress + meminfo.RegionSize;
        PBYTE begin = end;
       
        while (((SIZE_T)(end - begin) < dwSize) && (begin != pAddress)) {
            if (*(--begin) != 0x00)
                end = begin;
        }
        if (begin != pAddress)
            return begin;
    }
    return NULL;
}

PBYTE NtDllFindParamAddress(const PBYTE pAddress)
{
    PBYTE ptr = pAddress;
    // Test previous 32 bytes to find the begining address we need to patch
    // for 32bit find => push [ebp][14h] => parameters are pushed to stack
    // for 64bit find => mov r8,... => parameters are moved to registers r8, rdx, rcx
    while (pAddress - --ptr < 0x20) {
#ifdef _WIN64
        if (((ptr[0] & 0x4D) == ptr[0]) && (ptr[1] == 0x8B) && ((ptr[2] & 0xC7) == ptr[2])) {
#else
        if ((ptr[0] == 0xFF) && (ptr[1] == 0x75) && (ptr[2] == 0x14)) {
#endif
            return ptr;
        }
    }
    return NULL;
}

PBYTE NtDllFindCallAddress(const PBYTE pAddress)
{
    PBYTE ptr = pAddress;
    // Test previous 32 bytes to find the begining address we need to patch
    // for 32bit find => call [ebp][08h]
    // for 64bit find => call <register>
    while (pAddress - --ptr < 0x20) {
#ifdef _WIN64
        if ((ptr[0] == 0xFF) && ((ptr[1] & 0xD7) == ptr[1])) {
            if ((*(ptr - 1) & 0x41) == *(ptr - 1)) {
                --ptr;
            }
#else
        if ((ptr[0] == 0xFF) && (ptr[1] == 0x55) && (ptr[2] == 0x08)) {
#endif
            return ptr;
        }
    }
    return NULL;
}

typedef struct _NTDLL_LDR_PATCH {
    PBYTE pPatchAddress;
    SIZE_T nPatchSize;
    BYTE pBackup[0x20];
    PBYTE pDetourAddress;
    SIZE_T nDetourSize;
    BOOL bState;
} NTDLL_LDR_PATCH, *PNTDLL_LDR_PATCH;

NTDLL_LDR_PATCH patch;

BOOL NtDllPatch(const PBYTE pReturnAddress, NTDLL_LDR_PATCH &NtDllPatch)
{
    if (NtDllPatch.bState == FALSE) {
#ifdef _WIN64
        BYTE ptr[] = { '?', 0x87, '?' };                                    // xchg r.., r9
        BYTE mov[] = { 0x48, 0xB8, '?', '?', '?', '?', '?', '?', '?', '?' }; // mov rax, 0x0000000000000000
        BYTE call[] = { 0xFF, 0xD0, '?', 0x87, '?' };                        // call rax // xchg r.., r9
#else
        BYTE ptr[] = { 0xFF, 0x75, 0x08 };                                  // push [ebp][08h]
        BYTE mov[] = { 0x90, 0xB8, '?', '?', '?', '?' };                    // mov eax, 0x00000000
        BYTE call[] = { 0xFF, 0xD0 };                                        // call eax
#endif
        BYTE jmp[] = { 0xE9, '?', '?', '?', '?' };                          // jmp 0x00000000

        NtDllPatch.pPatchAddress = NtDllFindParamAddress(pReturnAddress);
        PBYTE pCallAddress = NtDllFindCallAddress(pReturnAddress);
        NtDllPatch.nPatchSize = pReturnAddress - NtDllPatch.pPatchAddress;
        SIZE_T nParamSize = pCallAddress - NtDllPatch.pPatchAddress;

        NtDllPatch.nDetourSize = _countof(ptr) + nParamSize + _countof(mov) + _countof(jmp);
        NtDllPatch.pDetourAddress = NtDllFindDetourAddress(pReturnAddress, NtDllPatch.nDetourSize);

        if (NtDllPatch.pPatchAddress && NtDllPatch.pDetourAddress && ((_countof(jmp) + _countof(call)) <= NtDllPatch.nPatchSize)) {
            memcpy(NtDllPatch.pBackup, NtDllPatch.pPatchAddress, NtDllPatch.nPatchSize);

            DWORD dwProtect = 0;
            if (VirtualProtect(NtDllPatch.pDetourAddress, NtDllPatch.nDetourSize, PAGE_EXECUTE_READWRITE, &dwProtect)) {
                memset(NtDllPatch.pDetourAddress, 0x90, NtDllPatch.nDetourSize);
#ifdef _WIN64
                // Copy original param instructions
                memcpy(&NtDllPatch.pDetourAddress[0], NtDllPatch.pPatchAddress, nParamSize);
                // Exchange the register that holds the EntryPoint with r9
                BYTE reg = ((pCallAddress[0] & 0x41) == 0x41 ? 0x08 : 0x00) + (pCallAddress[pReturnAddress - pCallAddress - 1] & 0x07);
                ptr[0] = 0x4C + ((reg & 0x08) ? 0x01 : 0x00); //ptr[0] = 0x49 + ((reg & 0x08) ? 0x04 : 0x00);
                ptr[2] = 0xC8 + (reg & 0x07);                //ptr[2] = 0xC1 + (((reg & 0x07) / 2) * 0x10) + ((reg & 0x07) % 2 ? 0x08 : 0x00);
                memcpy(&NtDllPatch.pDetourAddress[nParamSize], &ptr, _countof(ptr));
#else
                // Push EntryPoint as last parameter
                memcpy(&NtDllPatch.pDetourAddress[0], &ptr, _countof(ptr));
                // Copy original param instructions
                memcpy(&NtDllPatch.pDetourAddress[_countof(ptr)], NtDllPatch.pPatchAddress, nParamSize);
#endif
                // Move LdrpCallInitRoutine to eax/rax
                *(PSIZE_T)(&mov[2]) = (SIZE_T)LdrpCallInitRoutine;
                memcpy(&NtDllPatch.pDetourAddress[_countof(ptr) + nParamSize], &mov, _countof(mov));

                // Jump to original function
                *(DWORD*)(&jmp[1]) = (DWORD)(pReturnAddress - _countof(call) - (NtDllPatch.pDetourAddress + NtDllPatch.nDetourSize));
                memcpy(&NtDllPatch.pDetourAddress[_countof(ptr) + nParamSize + _countof(mov)], &jmp, _countof(jmp));

                VirtualProtect(NtDllPatch.pDetourAddress, NtDllPatch.nDetourSize, dwProtect, &dwProtect);

                if (VirtualProtect(NtDllPatch.pPatchAddress, NtDllPatch.nPatchSize, PAGE_EXECUTE_READWRITE, &dwProtect)) {
                    memset(NtDllPatch.pPatchAddress, 0x90, NtDllPatch.nPatchSize);

                    // Jump to detour address
                    *(DWORD*)(&jmp[1]) = (DWORD)(NtDllPatch.pDetourAddress - (pReturnAddress - _countof(call)));
                    memcpy(pReturnAddress - _countof(call) - _countof(jmp), &jmp, _countof(jmp));
#ifdef _WIN64
                    // Exchange r9 with the register that originally held the EntryPoint
                    memcpy(&call[2], &ptr, _countof(ptr));
#endif
                    // Call LdrpCallInitRoutine from eax/rax
                    memcpy(pReturnAddress - _countof(call), &call, _countof(call));

                    VirtualProtect(NtDllPatch.pPatchAddress, NtDllPatch.nPatchSize, dwProtect, &dwProtect);

                    NtDllPatch.bState = TRUE;
                }
            }
        }
    }
    return NtDllPatch.bState;
}


BOOL NtDllRestore(NTDLL_LDR_PATCH &NtDllPatch)
{
    // Restore patched bytes
    BOOL bResult = FALSE;
    if (NtDllPatch.bState && NtDllPatch.nPatchSize && &NtDllPatch.pBackup[0]) {
        DWORD dwProtect = 0;
        if (VirtualProtect(NtDllPatch.pPatchAddress, NtDllPatch.nPatchSize, PAGE_EXECUTE_READWRITE, &dwProtect)) {
            memcpy(NtDllPatch.pPatchAddress, NtDllPatch.pBackup, NtDllPatch.nPatchSize);
            VirtualProtect(NtDllPatch.pPatchAddress, NtDllPatch.nPatchSize, dwProtect, &dwProtect);

            if (VirtualProtect(NtDllPatch.pDetourAddress, NtDllPatch.nDetourSize, PAGE_EXECUTE_READWRITE, &dwProtect)) {
                memset(NtDllPatch.pDetourAddress, 0x00, NtDllPatch.nDetourSize);
                VirtualProtect(NtDllPatch.pDetourAddress, NtDllPatch.nDetourSize, dwProtect, &dwProtect);
                bResult = TRUE;
            }
        }
    }
    return bResult;
}

#define _DECL_DLLMAIN  // for _CRT_INIT
#include <process.h>  // for _CRT_INIT
#pragma comment(linker, "/entry:DllEntryPoint")

__declspec(noinline)
BOOL WINAPI DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
    // Patch/Restore ntdll address that calls the dll entry point
    if (fdwReason == DLL_PROCESS_ATTACH) {
        NtDllPatch((PBYTE)_ReturnAddress(), patch);
    }

    if (fdwReason == DLL_PROCESS_ATTACH || fdwReason == DLL_THREAD_ATTACH)
        if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
            return(FALSE);

    if (fdwReason == DLL_PROCESS_DETACH || fdwReason == DLL_THREAD_DETACH)
        if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
            return(FALSE);
   
    if (fdwReason == DLL_PROCESS_DETACH) {
        NtDllRestore(patch);
    }
    return(TRUE);
}


ioannis 07-29-2015 01:09

Guys, no love from you?

None of you gurus can review the code sample above to give me some comments/pointers ? I would appreciate any comments greatly.


All times are GMT +8. The time now is 08:20.

Powered by vBulletin® Version 3.8.8
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Always Your Best Friend: Aaron, JMI, ahmadmansoor, ZeNiX