Breaking into the DllMain

This post is a walkthrough to seting up a breakpoint into DllMain using the Windows Debugger.

The Code

Continuing the research on DLL files, we used as ground the same skeleton code we used to generate a DLL that exports a function by its actual name (without compiler decorations). We added a small enhancement in the code: an empty function that takes the ordinal number 1 so it can be called and facilitate the demonstration purposes.

#include <windows.h>
#include <debugapi.h>

#define DllExport1 comment(linker, "/EXPORT:CallMe=?CallMe@@YGXXZ,@1,NONAME")
#define DllExport comment(linker, "/EXPORT:DoSomeMagic=?DoSomeMagic@@YGXXZ")

void WINAPI CallMe(void)
{
    #pragma DllExport1
}

void WINAPI DoSomeMagic(void)
{
    #pragma DllExport
    OutputDebugStringA("DoSomeMagic was executed");
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch(fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            OutputDebugStringA("DllMain was executed");
            DoSomeMagic();
            break;
        case DLL_PROCESS_DETACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
            break;
    }

    return TRUE;
}

Execute the generated DLL

In order to execute the generated DLL, we use the Windows native utility rundll32.exe:

rundll32.exe test.dll, #1

As you can see in the code above, upon execution, debug strings are printed. We can see this strings using SysInternal’s DebugView:

output

Following the path to DllMain

In order to debug the DLL and reach to DllMain, we have to load the DLL in memory. We can do this by opening rundll32.exe on Windbg and setting it to execute our DLL. We follow this path: File -> Open Executable. We the set the appropriate arguments as shown in the following image:

windbg

The first break point we hit is on ntdll!LdrInitShimEngineDynamic. Before we continue, we set up a breakpoint to hit when our module gets loaded. This is done with sxe ld test. We carry on with debuggging and we hit the breakpoint as soon as our module is loaded:

sxeld

As the DLL is now mapped in memory, we can use lm m test to identify where it was mapped in memory:

lmmtest

Next step is to locate the entry point of our DLL by static analysis so we know where to break. For this, we can use a tool like dumpbin, CFF Explorer, Ghidra or IDA.

To get the entry point using dumpbin:

dumpbin.exe test.dll findstr entry

The entry point as shown on Ghidra:

dumpbinentryghidra

In order to find the actual bytes of the DllMain we’ll have to add the number dumpbin reports, to the start address that lm m test shows.

?0x00000000`5a930000 + 0x1368

So, the DllMain should start at: 00000000`5a931368

We can confirm that by unassembling the byte that exist on and after this memory address:

unassembleentrypoint

In theory, if we set a breakpoint at 00000000`5a931368 and continue debugging, we should hit the breakpoint and land into DllMain. However that’s what we get instead:

dllmainbreakpoint

Tools

For this article, we used the following tools:

tags: #reversing