2

I can hook any other function, but not ExitProcess.

Here is the code to demonstrate this:

#include <iostream>
#include <cstdlib>

#include <Windows.h>
#include <Psapi.h>

void __stdcall NewSleep(DWORD milliseconds)
{
        std::cout << "Sleep." << std::endl;

        std::cin.get();
}

void __stdcall NewExitProcess(UINT exitCode)
{
        std::cout << "ExitProcess." << std::endl;

        std::cin.get();
}

FARPROC f1 = NULL;
FARPROC f2 = NULL;

int main()
{
        HMODULE kernel32Module = GetModuleHandle("KERNEL32.dll");
        f1 = GetProcAddress(kernel32Module, "Sleep");
        f2 = GetProcAddress(kernel32Module, "ExitProcess");

        std::cout << f1 << std::endl;

        unsigned char* baseAddress = (unsigned char*)GetModuleHandle(NULL);

        IMAGE_DOS_HEADER* idh = (IMAGE_DOS_HEADER*)baseAddress;
        IMAGE_NT_HEADERS* inh = (IMAGE_NT_HEADERS*)(baseAddress + idh->e_lfanew);      
        IMAGE_IMPORT_DESCRIPTOR* iid = (IMAGE_IMPORT_DESCRIPTOR*)(baseAddress + inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

        for (IMAGE_IMPORT_DESCRIPTOR* i = iid; i->Name != 0; ++i)
        {
                std::string moduleName = (char*)(baseAddress + i->Name);

                if (moduleName == "KERNEL32.dll")
                {
                        IMAGE_THUNK_DATA* itd = (IMAGE_THUNK_DATA*)(baseAddress + i->FirstThunk);

                        for (IMAGE_THUNK_DATA* j = itd; j->u1.Function != 0; ++j)
                        {
                                if ((FARPROC)j->u1.Function == f1)
                                {
                                        DWORD oldProtect = 0;
                                        VirtualProtect(&j->u1.Function, sizeof(DWORD), PAGE_READWRITE, &oldProtect);
                                        j->u1.Function = (DWORD)&NewSleep;
                                        VirtualProtect(&j->u1.Function, sizeof(DWORD), oldProtect, &oldProtect);
                                }

                                if ((FARPROC)j->u1.Function == f2)
                                {
                                        DWORD oldProtect = 0;
                                        VirtualProtect(&j->u1.Function, sizeof(DWORD), PAGE_READWRITE, &oldProtect);
                                        j->u1.Function = (DWORD)&NewExitProcess;
                                        VirtualProtect(&j->u1.Function, sizeof(DWORD), oldProtect, &oldProtect);
                                }
                        }

                        break;
                }
        }

        Sleep(0);
        Sleep(0);

        ExitProcess(0);
        //Crash.

        std::cin.sync();
        std::cin.get();
        return EXIT_SUCCESS;
}

It calls the hooked function, but when NewExitProcess returns I get an access violation. The calls to Sleep are fine, just like any hooked function other than ExitProcess.

EDIT: I get the same issue when hooking ExitThread though.

SSpoke
  • 5,656
  • 10
  • 72
  • 124
NFRCR
  • 5,304
  • 6
  • 32
  • 37
  • No repro. It is pretty unclear how you hope your program to ever exit. Getting your replacement function called again, now with the iostream plumbing destroyed, when your program terminates is going to end poorly. Avoid reinventing this wheel and use, say, Detours so this has a shot at working properly on any Windows version, even the ones that forward functions like ExitThread to another DLL. And allowing you to properly restore the detour before program exit. – Hans Passant Jan 20 '14 at 11:48
  • The program works as expected for me. No crash. Is this exactly the code you tested? – typ1232 Jan 21 '14 at 18:18
  • @typ1232, yes. I used VC++2013 and release mode. Somehow it did not crash in debug mode. – NFRCR Jan 22 '14 at 12:16
  • It seems that the compiler simply generates some global cleanup code when it sees ExitProcess. The code for the lines below that is not even generated. I can get away from that by doing this void (__stdcall *fPtr)(UINT exitCode) = &ExitProcess; fPtr(0);. However I would still like to know why it does that? I guess it assumes that ExitProcess never returns, but that's a really wild assumption. It also works when I call ExitProcess only on a certain condition. So yes, the problem is that the compiler optimizes away all code after ExitProcess. – NFRCR Jan 22 '14 at 12:38

1 Answers1

-1

When looking up the function declaration of ExitProcess you will find something like this:

WINBASEAPI
DECLSPEC_NORETURN
VOID
WINAPI
ExitProcess(
    _In_ UINT uExitCode
    );

The interesting part is DECLSPEC_NORETURN which is defined as __declspec(noreturn). It's also an attribute used by the ExitThread function that was also causing a crash for you. Looking up on the docs, we find this:

This __declspec attribute tells the compiler that a function does not return. As a consequence, the compiler knows that the code following a call to a __declspec(noreturn) function is unreachable.

According to your findings, it is not only used to disable compiler warnings, but is also used for optimization. This also explains why it would work in Debug mode.

I can't think of a good solution for this, as you are fighting the optimizer. The solution you wrote in a comment did not work for me (VS2013, Release mode, /O2). I came up with something a bit silly, but it seems to do the job for me:

int *ptr = (int*)&ExitProcess;
ptr++;
ptr--;
((VOID (WINAPI*)(UINT))ptr)(0);

In general, hooking ExitProcess of another unknown program should always exit the current thread, because it may be compiled to not have any code to return to.

typ1232
  • 5,535
  • 6
  • 35
  • 51