RCE Endeavors 😅

May 25, 2023

Function Hooking: Software Breakpoints (4/7)

Filed under: Programming,Reverse Engineering — admin @ 11:43 PM

Table of Contents:

Software breakpoints are another technique to perform non-invasive function hooking. Unlike hardware breakpoints, there is no limit on the amount of software breakpoints that you can set. The downside, however, is that using software breakpoints can greatly reduce the performance of the application that is being monitored. As their name implies, these breakpoints are not set by any hardware registers; instead, they rely on changing the memory page permissions of executable code.

Installing the hook

To set a software breakpoint, the page that the target instruction is on must be a guard page, meaning that its page protection flags must have PAGE_GUARD set. Once an instruction in a guard page is executed, the program will raise a STATUS_GUARD_PAGE_VIOLATION exception. Doing this in code is rather straightforward since the Windows API provides convenient functions to retrieve and change page protections.

void SetMemoryBreakpoint(const void* const targetAddress) {

    DWORD oldProtections{};
    MEMORY_BASIC_INFORMATION memoryInfo{};
    auto result{ VirtualQuery(targetAddress, &memoryInfo,
        sizeof(MEMORY_BASIC_INFORMATION)) };
    if (result == 0) {
        PrintErrorAndExit("VirtualQuery");
    }

    result = VirtualProtect(memoryInfo.BaseAddress, memoryInfo.RegionSize,
        memoryInfo.AllocationProtect | PAGE_GUARD, &oldProtections);
    if (result == 0) {
        PrintErrorAndExit("VirtualProtect");
    }
}

The permissions of the page are retrieved and PAGE_GUARD is added to them.

Defining the exception handler

As with hardware breakpoints, a custom exception handler will need to be installed to catch this exception. Inside the exception handler, you will have access to the thread’s context and can perform the custom hooking logic. The software breakpoint exception handler has the same logical purpose as the hardware breakpoint version; however, the implementation details are a bit different. When a STATUS_GUARD_PAGE_VIOLATION is raised, the PAGE_GUARD protection on the entire page will be removed. This means that if you have a memory breakpoint at an address, the execution of any instruction on the same page as your target address will end up removing your breakpoint!

Overcoming the problem of having the PAGE_GUARD protection removed is a multi-step process. First, on a STATUS_GUARD_PAGE_VIOLATION exception, you can check to see whether the exception address matches the address of the function that you want to hook. If so, then carry out the hook logic as normal. However, before returning from the exception handler, you will need to set the trap flag in the EFlags register. This will put the CPU in single step mode for the next instruction execution. Now, when you return from the exception handler, you will immediately jump back into it with an EXCEPTION_SINGLE_STEP exception. Now you can set the page permissions to have PAGE_GUARD so that your memory breakpoint will continue to get hit in the future. While this may sound like a lot, the implementation is rather straightforward and is shown below:

LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS* const exceptionInfo) {

    if (exceptionInfo->ExceptionRecord->ExceptionCode ==
        STATUS_GUARD_PAGE_VIOLATION) {

        if (exceptionInfo->ExceptionRecord->ExceptionAddress ==
            drawTextAddress) {

            static std::string replacementMessage{ "Hooked Hello World!" };

            // Set to replacement message address
            exceptionInfo->ContextRecord->Rdx = reinterpret_cast<DWORD64>(
                replacementMessage.c_str());
        }

        // Set single step flag so that memory breakpoints are re-enabled
        // on the next instruction execution.
        exceptionInfo->ContextRecord->EFlags |= 0x100;

        return EXCEPTION_CONTINUE_EXECUTION;
    }

    if (exceptionInfo->ExceptionRecord->ExceptionCode ==
        EXCEPTION_SINGLE_STEP) {

        // Re-enable memory breakpoint since a different address might
        // have caused the guard page violation.
        SetMemoryBreakpoint(drawTextAddress);

        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

The implementation of an exception handler for memory breakpoints. This handler performs function hooking by overwriting a parameter that is being passed to the target function.

To make things a bit more exciting, this example will hook a Windows API, DrawTextA, instead of a custom function that was written. This is also done to prevent issues with the function to be hooked and the exception handler residing in the same memory page, as may be the case if you are hooking a function in your own code for some reason. The code for using DrawTextA is shown below:

int main(int argc, char* argv[]) {

    // Add a custom exception handler
    AddVectoredExceptionHandler(true, ExceptionHandler);

    SetMemoryBreakpoint(drawTextAddress);

    auto hDC{ GetDC(nullptr) };
    const auto fontHandle{ GetStockObject(DEFAULT_GUI_FONT) };
    LOGFONT logFont{};
    GetObject(fontHandle, sizeof(LOGFONT), &logFont);

    logFont.lfHeight = 200;

    const auto newFontHandle{ CreateFontIndirect(&logFont) };
    SelectObject(hDC, newFontHandle);

    const std::string message{ "Hello World!" };

    while (true) {
        RECT rect{};
        DrawTextA(hDC, message.c_str(), -1, &rect, DT_CALCRECT);

        DrawTextA(hDC, message.c_str(), -1, &rect, DT_SINGLELINE | DT_NOCLIP);

        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    return 0;
}

The main function of the example code.

Verifying the hook

Instead of outputting to a console, this program will draw the text “Hello World!” on the screen. However, when the memory breakpoint is set, the text will be replaced with “Hooked Hello World!” from the exception handler. This again demonstrates an additional technique to perform function hooking without any code modifications. With no limit set on the amount that you can set, memory breakpoints are the preferred way to hook functions of any application that may be doing integrity checks. As mentioned earlier though, the downside is performance. Since two exceptions (STATUS_GUARD_PAGE_VIOLATION and EXCEPTION_SINGLE_STEP) will be raised for every instruction execution on the memory page of the target address, the program may run noticeably slower.

Running the demo

The SoftwareBreakpoint project provides the full implementation that was presented in this section. As with hardware breakpoints, the best way to see software breakpoints in action is to set a breakpoint in the custom exception handler. Begin by placing two breakpoints: one on the if statement, and the other on the first statement inside of the if block.

Setting breakpoints in the custom exception handler.

After launching the application, look at the value of ExceptionAddress when the breakpoint gets hit. The first time that the breakpoint gets hit, ExceptionAddress should match drawTextAddress and the if block will be entered. Continue execution in the debugger and notice that the breakpoint on the if statement gets hit repeatedly, although ExceptionAddress will be different. This shows how the STATUS_GUARD_PAGE_VIOLATION exception gets raised for every instruction that is on the same memory page as drawTextAddress.

12 Comments »

  1. Code doesn’t work for anticheat NexonGameSecurity(BlackCipher64.aes). I can’t set the memory protection to page guard to trigger the exception. Any tips? I’m trying to hook NtOpenProcess. They have integrity checks so I need to do a VEH hook.

    Comment by Matthew McNutt — June 8, 2023 @ 3:55 PM

  2. The code is meant as an example of the technique; there is not any additional logic that would prevent this from being detected by an anti-cheat. If your breakpoint is not getting triggered, then there are a couple of things that you can check:

    – After you set it, does the page actually have the PAGE_GUARD permission? Does it get set immediately, and if so, does the permission stay set without your handler being hit?
    – Is your exception handler the first one in the exception chain? If so, does the game call AddVectoredExceptionHandler again at any point and add its own handler in front of yours?
    – Does this anti-cheat use a kernel driver that would strip the guard page permission or remove the exception handler?

    There are lot of different ways to go about this. If this anti-cheat is entirely in usermode (doesn’t seem to be the trend these days), then you can try to see if the anti-cheat is using NtProtectVirtualMemory, RtlAddVectoredExceptionHandler, or RtlRemoveVectoredExceptionHandler and go from there. If the anti-cheat lives in the kernel, and your hack is in usermode, then you’re out of luck.

    Comment by admin — June 9, 2023 @ 12:56 PM

  3. Thanks for replying.

    Calling VirtualProtect crash the anticheat.The anticheat is NexonGameSecurity(BlackCipher64.aes). It is a usermode 64bit anticheat, there is no driver. It uses the WinAPI NtOpenProcess, NtQuerySystemInformation to detect your process list. You can hook theses functions and return STATUS_ACCESS_DENIED (0xC0000022). It also use NtReadVirtualMemory to check for memory edit, you can dump a clean copy of the client and feed the clean copy to bypass. Hooking theses function is very easy with Microsoft Detours 4.0.1, the problems are the integrity checks. Someone released a working bypass here: https://github.com/davidjoseph9/NexonGameSecurity-Bypass
    It is done through memory edit. I’m just interested in bypassing using a Page Guard VEH hook, but can’t seem to do it.

    VirtualProtect calls NtProtectVirtualMemory. I tried setting the page guard using the nt function, but it returns the NTSTATUS: 0xC0000005 (STATUS_ACCESS_VIOLATION)

    Any ideas what does it mean?

    #include
    #include

    typedef NTSTATUS(NTAPI* NtProtectVirtualMemory_t)(HANDLE ProcessHandle, PVOID BaseAddress, ULONG NumberOfBytesToProtect, ULONG NewAccessProtection, PULONG OldAccessProtection);
    NtProtectVirtualMemory_t _NtProtectVirtualMemory = reinterpret_cast(GetProcAddress(GetModuleHandleA(“ntdll”), “NtProtectVirtualMemory”));

    BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
    {
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    AllocConsole();

    // Add a custom exception handler
    AddVectoredExceptionHandler(true, ExceptionHandler);

    // Set Page Guard
    HANDLE hProcessVMOperation = OpenProcess(PROCESS_VM_OPERATION, FALSE, GetCurrentProcessId());
    DWORD dwOldProtect;
    NTSTATUS r = _NtProtectVirtualMemory(hProcessVMOperation, NtOpenProcessAddress, 4096, PAGE_EXECUTE_READ | PAGE_GUARD, &dwOldProtect);
    if (!NT_SUCCESS(r))
    {
    printf(“NtProtectVirtualMemory NTSTATUS: %016I64x\n”, r);
    }

    return TRUE;
    }

    return TRUE;
    }

    Comment by Matthew McNutt — June 9, 2023 @ 9:36 PM

  4. It seems like you are using NtProtectVirtualMemory incorrectly; see the parameter definitions here – https://doxygen.reactos.org/d2/dfa/ntoskrnl_2mm_2ARM3_2virtual_8c.html#a2cab978ee136ac00a0c06b56aa9170ef

    The third argument is a pointer that points to the size instead of the direct size.

    Comment by admin — June 9, 2023 @ 10:14 PM

  5. I fixed the parameters:

    typedef NTSTATUS(NTAPI* NtProtectVirtualMemory_t)(HANDLE ProcessHandle, PVOID* BaseAddress, SIZE_T* NumberOfBytesToProtect, ULONG NewAccessProtection, PULONG OldAccessProtection);
    NtProtectVirtualMemory_t _NtProtectVirtualMemory = reinterpret_cast(GetProcAddress(GetModuleHandleA(“ntdll”), “NtProtectVirtualMemory”));

    SIZE_T uSize = 10;
    NTSTATUS r = _NtProtectVirtualMemory(hProcessVMOperation, &NtOpenProcessAddress, &uSize, PAGE_EXECUTE_READ | PAGE_GUARD, &dwOldProtect);

    It doesn’t return STATUS_ACCESS_VIOLATION anymore. Calling VirtualProtect or NtProtectVirtualMemory crashes the anticheat.

    Comment by Matthew McNutt — June 10, 2023 @ 11:19 AM

  6. Try rolling your own stub to call it. See here: https://pastebin.com/k3nmajzN

    If the crash happens some time after the call (due to the anti-cheat scanning the process memory for the page permissions), then you will need to see what is calling NtQueryVirtualMemory, or find the anti-cheat logic that eventually makes the syscall if it does it indirectly.

    Comment by admin — June 10, 2023 @ 3:20 PM

  7. Your code works, no crash at all when I inject it into the anti cheat.

    Changing the page protection of NtOpenProcess doesn’t work. It crashes instantly no NTSTATUS error code is shown.

    https://pastebin.com/DWFMcQ7H

    Comment by Matthew McNutt — June 10, 2023 @ 7:38 PM

  8. What is the exception that it crashes with? I also don’t see any exception handler being installed in the code that you linked. Once you set the PAGE_GUARD permission, any code executing on that page will trigger an exception. Right now nothing in your code indicates that it will be caught, and if that is the case then the process will terminate. There will be a lot of native syscalls in that 4KB page, so what might be happening is that another one is being called on that page, the STATUS_GUARD_PAGE_VIOLATION exception is raised, nothing catches it, and the process terminates.

    Comment by admin — June 11, 2023 @ 12:24 AM

  9. https://pastebin.com/1yCtPrFU

    There’s no exception that is raised.

    When I inject the dll into the anticheat using Cheat Engine.
    I get an error message:
    dllInject failed: Unknown error during injection
    Force load module failed: Failed to execute the dll loader.

    Calling the NtProtectVirtualMemory Stub to change NtOpenProcess page protection doesn’t seem to allow the BOOL APIENTRY DllMain to return TRUE when injected into the anticheat.

    Calling the NtProtectVirtualMemory Stub to change the page protection of a memory you allocated using VirtualAlloc worked though when injected into the anticheat.

    Comment by Matthew McNutt — June 11, 2023 @ 8:22 AM

  10. The code itself looks fine. I’m not familiar with the anti-cheat so I can’t really say what is happening at this point. Your best bet would be to step through DllMain when the DLL is injected and see what happens after the call to the NtProtectVirtualMemory stub is made.

    Comment by admin — June 11, 2023 @ 12:00 PM

  11. Thank you for trying to help me. It’s greatly appreciated. I enjoy reading your site. In the past when the anticheat was a WoW64Process, theses articles you wrote helped:
    https://www.codereversing.com/archives/243
    https://www.codereversing.com/archives/date/2015/06

    It is nice to see the dedication you put on the site, from 2011 to 2023, you are still active!

    Just another quick question, are you this person? The code looks similar if not the same:
    https://medium.com/@fsx30/hooking-heavens-gate-a-wow64-hooking-technique-5235e1aeed73
    https://gist.github.com/hoangprod/4f5e821525cd199c3ca3134a0596e263

    Comment by Matthew McNutt — June 11, 2023 @ 12:29 PM

  12. Thanks, I’m glad the posts have been able to help! I am not the person that you linked in those two posts. My posts are all on this site and on my GitHub repo, which just links back to here.

    Comment by admin — June 11, 2023 @ 12:45 PM

RSS feed for comments on this post. TrackBack URL

Leave a comment

 

Powered by WordPress