Using module imports to hijack executable entry point

It's known that lpReserved in DllMain can be used to override the program importing the module's entrypoint, due to a quirk in how ntdll invokes entry point functions but I was researching the entire windows loading process and made a proof of concept

typedef DWORD(__cdecl *thread_proc_t)(PVOID arg);

thread_proc_t g_thread_proc = nullptr;

DWORD __cdecl new_thread_start(PVOID arg)  
{
    // PEB pointer is passed as an argument to your "thread"
    // Your "thread" pointer is (when using CRT) _mainCRTStartup
    // Even though _mainCRTStartup is defined as not having a parameter,
    // it actually does have one
    printf("NEW MAIN! (0x%p, %d)\n", arg, ((PPEB)arg)->BeingDebugged);

    DWORD ret = g_thread_proc(arg);

    printf("Return: %d\n", ret);

    return ret;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)  
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)
    {
        PCONTEXT pContext = (PCONTEXT)lpReserved;

        printf("Context: 0x%p\n", pContext);

#if defined(_WIN64)
        g_thread_proc = (thread_proc_t)pContext->Rcx;

        pContext->Rcx = (ULONG_PTR)new_thread_start;
#elif defined(_WIN32)
        g_thread_proc = (thread_proc_t)pContext->Eax;

        pContext->Eax = (DWORD)new_thread_start;
#endif
    }

    return TRUE;
}

So the j00ru article mentions (maybe it is detailed elsewhere, but this specific one I couldn't find a reference to it) a CONTEXT pointer in DllMain on DLL_PROCESS_ATTACH - but it doesn't mention what the CONTEXT pointer is for.

The CONTEXT pointer is the CONTEXT of RtlUserThreadStart which is invoked with two arguments. Thread start (entrypoint of the application) and the argument passed to the entrypoint. In this case (At least on Windows 10) the argument passed to your EP is a PEB pointer. This is interesting because looking at the CRT, and your own applications in IDA, your EP never takes an actual argument. Because it's __cdecl, the argument can be unhandled and ignored and the stack won't be all messed up.

You can have the process environment block pointer "for free" without having to execute fs/gs instructions. For people who do not know what lpReserved is, or what argument is passed to RtlUserThreadStart by default, this can be pretty confusing to analyze.

Anyway, the j00ru article points out some valid uses for this undocumented functionality. A binary packer without needing to manipulate TLS callbacks or the EP of a program could be pretty neat.

Have fun!

Andrew Artz

Read more posts by this author.