stevemk14ebr / PolyHook_2_0

C++20, x86/x64 Hooking Libary v2.0
MIT License
1.6k stars 226 forks source link

Hooking function fails with "Warn: Couldn't decompile instructions at followed jmp" and "Error: Prologue jmp resolution failed" #184

Closed BullyWiiPlaza closed 1 year ago

BullyWiiPlaza commented 1 year ago

Hi,

I'm attempting to hook the function D3D11Present which works fine on my machine but a few users seem to run into the error

[!] Warn: Couldn't decompile instructions at followed jmp
[!] Error: Prologue jmp resolution failed
Successfully hooked: 0
SEV: Prologue jmp resolution failed
Called count: 0
Called count: 0
Called count: 0
Called count: 0
Called count: 0
Unhooking...
[!] Error: Detour unhook failed: no hook present
Successfully unhooked: 0
SEV: Detour unhook failed: no hook present
Press any key to continue...

Reproduction DLL code for DirectX11 hooking:

#include <Windows.h>
#include <stdexcept>
#include <cstdint>
#include <polyhook2/Detour/NatDetour.hpp>
#include <magic_enum.hpp>
#include <io.h>
#include <fcntl.h>
#include <iostream>
#include <d3d11.h>
#include <ostream>
#include <detours/detours.h>

using namespace std::chrono_literals;

#pragma comment(lib, "d3d11.lib")

using D3D_PRESENT_FUNCTION = HRESULT(__stdcall*)(IDXGISwapChain* pThis, UINT SyncInterval, UINT Flags);

constexpr D3D_DRIVER_TYPE driver_type_list[4] =
{
    D3D_DRIVER_TYPE_REFERENCE,
    D3D_DRIVER_TYPE_SOFTWARE,
    D3D_DRIVER_TYPE_HARDWARE,
    D3D_DRIVER_TYPE_WARP
};

// Function pasted from https://stackoverflow.com/a/62240300 with some modifications
bool GetD3D11SwapchainDeviceContext(IDXGISwapChain** pSwapchain, ID3D11Device** pDevice, ID3D11DeviceContext**)
{
    WNDCLASSEX window_class{};
    window_class.cbSize = sizeof(window_class);
    window_class.lpfnWndProc = DefWindowProc;
    window_class.lpszClassName = TEXT("dummy class");

    if (!RegisterClassEx(&window_class))
    {
        return false;
    }

    DXGI_SWAP_CHAIN_DESC swap_chain_desc{};

    swap_chain_desc.BufferCount = 1;
    swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swap_chain_desc.OutputWindow = GetForegroundWindow();
    swap_chain_desc.SampleDesc.Count = 1;
    swap_chain_desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    swap_chain_desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    swap_chain_desc.Windowed = TRUE;

    D3D_FEATURE_LEVEL feature_level{};
    auto success = false;

    for (const auto driver_type : driver_type_list)
    {
        if (const auto hr = D3D11CreateDeviceAndSwapChain(nullptr, driver_type, nullptr, 0, nullptr, 0,
            D3D11_SDK_VERSION, &swap_chain_desc, pSwapchain,
            pDevice, &feature_level, nullptr);
            FAILED(hr))
        {
            // We don't care
        }
        else
        {
            success = true;
            break;
        }
    }

    DestroyWindow(swap_chain_desc.OutputWindow);
    UnregisterClass(window_class.lpszClassName, GetModuleHandle(nullptr));

    if (!success)
    {
        // We don't care
        return false;
    }

    return true;
}

template <class T>
void safe_release(T releasable)
{
    if (releasable != nullptr)
    {
        releasable->Release();
        releasable = nullptr;
    }
}

D3D_PRESENT_FUNCTION GetD3D11PresentFunction()
{
    IDXGISwapChain* swap_chain;
    ID3D11Device* device;
    ID3D11DeviceContext* ctx;

    if (GetD3D11SwapchainDeviceContext(&swap_chain, &device, &ctx))
    {
        const auto vmt = *reinterpret_cast<void***>(swap_chain);

        safe_release(swap_chain);
        safe_release(device);
        /* #if !_DEBUG
                safe_release(ctx);
        #endif */

        return static_cast<D3D_PRESENT_FUNCTION>(vmt[8]);
    }

    return nullptr;
}

D3D_PRESENT_FUNCTION oD3D11Present;

auto called_count = 0;

HRESULT hD3D11Present(IDXGISwapChain* pThis, UINT SyncInterval, UINT Flags)
{
    called_count++;
    return oD3D11Present(pThis, SyncInterval, Flags);
}

// https://stackoverflow.com/a/25927081/3764804
void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr)
{
    // Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been
    // observed that the file number of our standard handle file objects can be assigned internally to a value of -2
    // when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our
    // call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value
    // before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to
    // use the "nul" device, which will place them into a valid state, after which we can redirect them to our target
    // using the "_dup2" function.
    if (bindStdIn)
    {
        FILE* dummyFile;
        freopen_s(&dummyFile, "nul", "r", stdin);
    }
    if (bindStdOut)
    {
        FILE* dummyFile;
        freopen_s(&dummyFile, "nul", "w", stdout);
    }
    if (bindStdErr)
    {
        FILE* dummyFile;
        freopen_s(&dummyFile, "nul", "w", stderr);
    }

    // Redirect unbuffered stdin from the current standard input handle
    if (bindStdIn)
    {
        HANDLE stdHandle = GetStdHandle(STD_INPUT_HANDLE);
        if (stdHandle != INVALID_HANDLE_VALUE)
        {
            int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
            if (fileDescriptor != -1)
            {
                FILE* file = _fdopen(fileDescriptor, "r");
                if (file != NULL)
                {
                    int dup2Result = _dup2(_fileno(file), _fileno(stdin));
                    if (dup2Result == 0)
                    {
                        setvbuf(stdin, NULL, _IONBF, 0);
                    }
                }
            }
        }
    }

    // Redirect unbuffered stdout to the current standard output handle
    if (bindStdOut)
    {
        HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        if (stdHandle != INVALID_HANDLE_VALUE)
        {
            int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
            if (fileDescriptor != -1)
            {
                FILE* file = _fdopen(fileDescriptor, "w");
                if (file != NULL)
                {
                    int dup2Result = _dup2(_fileno(file), _fileno(stdout));
                    if (dup2Result == 0)
                    {
                        setvbuf(stdout, NULL, _IONBF, 0);
                    }
                }
            }
        }
    }

    // Redirect unbuffered stderr to the current standard error handle
    if (bindStdErr)
    {
        HANDLE stdHandle = GetStdHandle(STD_ERROR_HANDLE);
        if (stdHandle != INVALID_HANDLE_VALUE)
        {
            int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
            if (fileDescriptor != -1)
            {
                FILE* file = _fdopen(fileDescriptor, "w");
                if (file != NULL)
                {
                    int dup2Result = _dup2(_fileno(file), _fileno(stderr));
                    if (dup2Result == 0)
                    {
                        setvbuf(stderr, NULL, _IONBF, 0);
                    }
                }
            }
        }
    }

    // Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the
    // standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In
    // versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything
    // has been read from or written to the targets or not.
    if (bindStdIn)
    {
        std::wcin.clear();
        std::cin.clear();
    }
    if (bindStdOut)
    {
        std::wcout.clear();
        std::cout.clear();
    }
    if (bindStdErr)
    {
        std::wcerr.clear();
        std::cerr.clear();
    }
}

enum hooking_library_t
{
    polyhook2,
    detours
};

hooking_library_t hooking_library = polyhook2;

void Run()
{
    // Allocate a console window for this process
    AllocConsole();

    // Update the C/C++ runtime standard input, output, and error targets to use the console window
    BindCrtHandlesToStdHandles(true, true, true);

    oD3D11Present = GetD3D11PresentFunction();
    if (oD3D11Present == nullptr)
    {
        MessageBox(nullptr, L"oD3D11Present was nullptr", L"Failure", 0);
    }

    std::stringstream ss;
    ss << std::hex << oD3D11Present;

    std::cout << "Function address: " << ss.str() << std::endl;

    std::shared_ptr<PLH::NatDetour> hook = nullptr;
    std::shared_ptr<PLH::ErrorLog> logger = nullptr;

    if (hooking_library == polyhook2)
    {
        // Register logger
        logger = std::make_shared<PLH::ErrorLog>();
        logger->setLogLevel(PLH::ErrorLevel::INFO);
        PLH::Log::registerLogger(logger);

        // Create the hook
        hook = std::make_shared<PLH::NatDetour>(reinterpret_cast<uint64_t>(oD3D11Present),
            reinterpret_cast<uint64_t>(hD3D11Present),
            reinterpret_cast<uint64_t*>(&oD3D11Present));

        const auto successfully_hooked = hook->hook();
        std::cout << "Successfully hooked: " << successfully_hooked << std::endl;
        if (!successfully_hooked)
        {
            const auto [message, level] = logger->pop();
            std::cerr << magic_enum::enum_name(level) << ": " << message << std::endl;
        }
    }
    else if (hooking_library == detours)
    {
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&reinterpret_cast<PVOID&>(oD3D11Present), hD3D11Present);
        DetourTransactionCommit();
    }

    for (auto index = 0; index < 5; index++)
    {
        std::cout << "Called count: " << called_count << std::endl;
        std::this_thread::sleep_for(1000ms);
    }

    if (hooking_library == polyhook2)
    {
        std::cout << "Unhooking..." << std::endl;
        const auto successfully_unhooked = hook->unHook();
        std::cout << "Successfully unhooked: " << successfully_unhooked << std::endl;
        if (!successfully_unhooked)
        {
            const auto [message, level] = logger->pop();
            std::cerr << magic_enum::enum_name(level) << ": " << message << std::endl;
        }
    }
    else if (hooking_library == detours)
    {
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourDetach(&reinterpret_cast<PVOID&>(oD3D11Present), hD3D11Present);
        DetourTransactionCommit();
    }

    std::system("pause");
    std::quick_exit(EXIT_SUCCESS);
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        {
            const auto thread_handle = CreateThread(nullptr, 0,
                reinterpret_cast<LPTHREAD_START_ROUTINE>(Run), nullptr, 0, nullptr);
            if (thread_handle == nullptr)
            {
                throw std::runtime_error("Creating main thread failed");
            }
            SetThreadDescription(thread_handle, L"Main");
        }

        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }

    return TRUE;
}

If I compile this simple C++ DLL code and inject it into some DirectX11 game, it fails with the error above for a French guy's machine. It seems like Zydis does not disassemble the code correctly but I don't have any further information. The PolyHook2 logging seems to be fairly lacking, e.g. it does not mention the address at which disassembling was attempted or other useful details.

Pre-compiled release build of the above DLL: PolyHook2D3D11PresentHookingBug.zip

In either case, Microsoft Detours is working flawlessly (also confirmed on the French guy's machine).

If you have any idea what could be causing this issue with PolyHook2 and how to fix it, it would be much appreciated.

stevemk14ebr commented 1 year ago

In this case I need you to get the assembly of the function you're trying to hook on that random french guys machine.

If the logging is insufficient please submit a PR that adds it or debug the hooking under a debugger.

BullyWiiPlaza commented 1 year ago

Since the default logging does not show any assembly, I'd have to make some changes for outputting the assembly.

Debugging directly on his machine would be troublesome but I might be able to achieve it if he's still interested in helping me out. However, we already know it fails at following the jmp: https://github.com/stevemk14ebr/PolyHook_2_0/blob/fecf3261d4fea2a448924df592179c9b0d7003ed/sources/x64Detour.cpp#L329 Disassembling does work successfully. Now that I think about it, it seems to be a similar issue as https://github.com/stevemk14ebr/PolyHook_2_0/pull/183. Maybe your thorough unit tests will have to be extended to catch more use cases.

Here's the improved logging output pull request: https://github.com/stevemk14ebr/PolyHook_2_0/pull/185

Here's the French guy's output with the logging changes applied:

[+] Info: m_fnAddress: 0x00007ff8341b4660

[+] Info: Original function:
7ff8341b4660 [5]: e9 69 c9 fc ff                          jmp 0x00007FF834180FCE -> 7ff834180fce
7ff8341b4665 [5]: 48 89 74 24 20                          mov qword ptr ss:[rsp+0x20], rsi
7ff8341b466a [1]: 55                                      push rbp
7ff8341b466b [1]: 57                                      push rdi
7ff8341b466c [2]: 41 56                                   push r14
7ff8341b466e [5]: 48 8d 6c 24 90                          lea rbp, ss:[rsp-0x70]
7ff8341b4673 [7]: 48 81 ec 70 01 00 00                    sub rsp, 0x170
7ff8341b467a [7]: 48 8b 05 d7 ed 0b 00                    mov rax, qword ptr ds:[0x00007FF834273458] -> 7ff834273458
7ff8341b4681 [3]: 48 33 c4                                xor rax, rsp
7ff8341b4684 [4]: 48 89 45 60                             mov qword ptr ss:[rbp+0x60], rax
7ff8341b4688 [3]: 45 33 f6                                xor r14d, r14d
7ff8341b468b [5]: 44 89 44 24 48                          mov dword ptr ss:[rsp+0x48], r8d
7ff8341b4690 [7]: 44 39 35 8d e9 0b 00                    cmp dword ptr ds:[0x00007FF834273024], r14d -> 7ff834273024
7ff8341b4697 [3]: 41 8b f0                                mov esi, r8d
7ff8341b469a [2]: 8b fa                                   mov edi, edx
7ff8341b469c [4]: 89 54 24 40                             mov dword ptr ss:[rsp+0x40], edx
7ff8341b46a0 [3]: 48 8b d9                                mov rbx, rcx
7ff8341b46a3 [4]: 48 89 4d 20                             mov qword ptr ss:[rbp+0x20], rcx
7ff8341b46a7 [5]: c6 44 24 50 00                          mov byte ptr ss:[rsp+0x50], 0x00
7ff8341b46ac [6]: 0f 85 9e b4 02 00                       jnz 0x00007FF8341DFB50 -> 7ff8341dfb50
7ff8341b46b2 [7]: f6 05 47 2b 0c 00 02                    test byte ptr ds:[0x00007FF834277200], 0x02 -> 7ff834277200
7ff8341b46b9 [6]: 0f 85 1a b5 02 00                       jnz 0x00007FF8341DFBD9 -> 7ff8341dfbd9
7ff8341b46bf [3]: 0f 57 c0                                xorps xmm0, xmm0

[!] Warn: Couldn't decompile instructions at followed jmp
[!] Error: Prologue jmp resolution failed
Successfully hooked: 0
SEV: Prologue jmp resolution failed
Called count: 0
Called count: 0
Called count: 0
Called count: 0
Called count: 0
Unhooking...
[!] Error: Detour unhook failed: no hook present
Successfully unhooked: 0
SEV: Detour unhook failed: no hook present
Appuyez sur une touche pour continuer...

Full logging output on my PC:

[+] Info: m_fnAddress: 0x00007ffa567414c0

[+] Info: Original function:
7ffa567414c0 [5]: e9 05 f2 11 04                          jmp 0x00007FFA5A8606CA -> 7ffa5a8606ca
7ffa567414c5 [5]: 48 89 74 24 20                          mov qword ptr ss:[rsp+0x20], rsi
7ffa567414ca [1]: 55                                      push rbp
7ffa567414cb [1]: 57                                      push rdi
7ffa567414cc [2]: 41 56                                   push r14
7ffa567414ce [5]: 48 8d 6c 24 90                          lea rbp, ss:[rsp-0x70]
7ffa567414d3 [7]: 48 81 ec 70 01 00 00                    sub rsp, 0x170
7ffa567414da [7]: 48 8b 05 b7 8f 0c 00                    mov rax, qword ptr ds:[0x00007FFA5680A498] -> 7ffa5680a498
7ffa567414e1 [3]: 48 33 c4                                xor rax, rsp
7ffa567414e4 [4]: 48 89 45 60                             mov qword ptr ss:[rbp+0x60], rax
7ffa567414e8 [3]: 45 33 f6                                xor r14d, r14d
7ffa567414eb [5]: 44 89 44 24 48                          mov dword ptr ss:[rsp+0x48], r8d
7ffa567414f0 [7]: 44 39 35 2d 8b 0c 00                    cmp dword ptr ds:[0x00007FFA5680A024], r14d -> 7ffa5680a024
7ffa567414f7 [3]: 41 8b f0                                mov esi, r8d
7ffa567414fa [2]: 8b fa                                   mov edi, edx
7ffa567414fc [4]: 89 54 24 40                             mov dword ptr ss:[rsp+0x40], edx
7ffa56741500 [3]: 48 8b d9                                mov rbx, rcx
7ffa56741503 [4]: 48 89 4d 20                             mov qword ptr ss:[rbp+0x20], rcx
7ffa56741507 [5]: c6 44 24 50 00                          mov byte ptr ss:[rsp+0x50], 0x00
7ffa5674150c [6]: 0f 85 e2 a7 03 00                       jnz 0x00007FFA5677BCF4 -> 7ffa5677bcf4
7ffa56741512 [7]: f6 05 a7 bf 0c 00 02                    test byte ptr ds:[0x00007FFA5680D4C0], 0x02 -> 7ffa5680d4c0
7ffa56741519 [6]: 0f 85 64 a8 03 00                       jnz 0x00007FFA5677BD83 -> 7ffa5677bd83
7ffa5674151f [3]: 0f 57 c9                                xorps xmm1, xmm1

[+] Info: Prologue to overwrite:
7ff9a9d48f10 [5]: 48 89 6c 24 18                          mov qword ptr ss:[rsp+0x18], rbp
7ff9a9d48f15 [5]: 48 89 74 24 20                          mov qword ptr ss:[rsp+0x20], rsi

[+] Info: Trampoline address: 0x000001c094103000
[+] Info: Jmp To Prol:
1c09410300a [6]: ff 25 48 00 00 00                       jmp [1c094103058] ->7ff9a9d48f1a
1c094103058 [8]: 1a 8f d4 a9 f9 7f 00 00                 dest holder

[+] Info: m_trampoline: 0x000001c094103000

[+] Info: m_trampolineSz: 0x0065

[+] Info: Trampoline:
1c094103000 [5]: 48 89 6c 24 18                          mov qword ptr ss:[rsp+0x18], rbp
1c094103005 [5]: 48 89 74 24 20                          mov qword ptr ss:[rsp+0x20], rsi
1c09410300a [6]: ff 25 48 00 00 00                       jmp qword ptr ds:[0x000001C094103058] -> 7ff9a9d48f1a

[+] Info: Hook instructions:
7ff9a9d48f10 [6]: ff 25 ea 70 08 80                       jmp [7ff929dd0000] ->7ff971ad3d00
7ff929dd0000 [8]: 00 3d ad 71 f9 7f 00 00                 dest holder

[+] Info: Hook size: 10

[+] Info: Prologue offset: 6

Successfully hooked: 1
Called count: 0
Called count: 60
Called count: 119
Called count: 178
Called count: 238
Unhooking...
Successfully unhooked: 1
Press any key to continue . . .
stevemk14ebr commented 1 year ago

"Maybe your thorough unit tests will have to be extended to catch more use cases."

Your snarkiness is becoming annoying. For help, I suggest not doing that or I'll just ban you. I do this for free in my own time for fun.

I need you to post the assembly of the function you are trying to hook. The failure case is likely exercising an uncovered case. There's many reasons hooks can fail, for example this french mans machine may be using an external software which already hooks the function you're targeting. This would potentially insert a jmp in the prologue which polyhook may attempt to follow and fail for many different reasons.

I appreciate you providing your hooking code but it's the target assembly code you're trying to hook that is the information I need. You could grab this via a debugger or something if you don't wish you extend the logging, otherwise you could extend the logging temporary to print all the instructions that polyhook attempts to disassemble while following jumps in the prologue.

BullyWiiPlaza commented 1 year ago

Yes, I understand, sorry for the inconvenience. If any other software is already hooking this function I'd still assume that PolyHook2 would find a way to still hook it without any issues. I decided to extend the logging further inside followJmp() and it was quite interesting how often the code would follow a jmp on my machine:

[+] Info: Front instruction: 7ffa567414c0 [5]: e9 05 f2 11 04                          jmp 0x00007FFA5A8606CA -> 7ffa5a8606ca

[+] Info: Destination: 0x00007ffa5a8606ca

[+] Info: Destination function instruction(s):
7ffa5a8606ca [5]: e9 41 88 47 99                          jmp 0x00007FF9F3CD8F10 -> 7ff9f3cd8f10
7ffa5a8606cf [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606d1 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606d3 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606d5 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606d7 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606d9 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606db [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606dd [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606df [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606e1 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606e3 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606e5 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606e7 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606e9 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606eb [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606ed [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606ef [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606f1 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606f3 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606f5 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606f7 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606f9 [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606fb [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606fd [2]: 00 00                                   add byte ptr ds:[rax], al
7ffa5a8606ff [6]: 00 89 54 24 10 53                       add byte ptr ds:[rcx+0x53102454], cl
7ffa5a860705 [5]: e9 db 86 f0 fb                          jmp 0x00007FFA56768DE5 -> 7ffa56768de5

[+] Info: Front instruction: 7ffa5a8606ca [5]: e9 41 88 47 99                          jmp 0x00007FF9F3CD8F10 -> 7ff9f3cd8f10

[+] Info: Destination: 0x00007ff9f3cd8f10

[+] Info: Destination function instruction(s):
7ff9f3cd8f10 [5]: 48 89 6c 24 18                          mov qword ptr ss:[rsp+0x18], rbp
7ff9f3cd8f15 [5]: 48 89 74 24 20                          mov qword ptr ss:[rsp+0x20], rsi
7ff9f3cd8f1a [2]: 41 56                                   push r14
7ff9f3cd8f1c [4]: 48 83 ec 20                             sub rsp, 0x20
7ff9f3cd8f20 [3]: 41 8b e8                                mov ebp, r8d
7ff9f3cd8f23 [2]: 8b f2                                   mov esi, edx
7ff9f3cd8f25 [3]: 4c 8b f1                                mov r14, rcx
7ff9f3cd8f28 [4]: 41 f6 c0 01                             test r8b, 0x01
7ff9f3cd8f2c [2]: 74 17                                   jz 0x00007FF9F3CD8F45 -> 7ff9f3cd8f45
7ff9f3cd8f2e [5]: 48 8b 6c 24 40                          mov rbp, qword ptr ss:[rsp+0x40]
7ff9f3cd8f33 [5]: 48 8b 74 24 48                          mov rsi, qword ptr ss:[rsp+0x48]
7ff9f3cd8f38 [4]: 48 83 c4 20                             add rsp, 0x20
7ff9f3cd8f3c [2]: 41 5e                                   pop r14
7ff9f3cd8f3e [7]: 48 ff 25 ab 96 0b 00                    jmp qword ptr ds:[0x00007FF9F3D925F0] -> 7ff9f3d925f0

[+] Info: Front instruction: 7ff9f3cd8f10 [5]: 48 89 6c 24 18                          mov qword ptr ss:[rsp+0x18], rbp

[+] Info: Instruction is NOT branching...

[+] Info: Prologue to overwrite:
7ff9f3cd8f10 [5]: 48 89 6c 24 18                          mov qword ptr ss:[rsp+0x18], rbp
7ff9f3cd8f15 [5]: 48 89 74 24 20                          mov qword ptr ss:[rsp+0x20], rsi

Here's my tester's output:

[+] Info: m_fnAddress: 0x00007ff8341b4660

[+] Info: Original function:
7ff8341b4660 [5]: e9 69 c9 fc ff                          jmp 0x00007FF834180FCE -> 7ff834180fce
7ff8341b4665 [5]: 48 89 74 24 20                          mov qword ptr ss:[rsp+0x20], rsi
7ff8341b466a [1]: 55                                      push rbp
7ff8341b466b [1]: 57                                      push rdi
7ff8341b466c [2]: 41 56                                   push r14
7ff8341b466e [5]: 48 8d 6c 24 90                          lea rbp, ss:[rsp-0x70]
7ff8341b4673 [7]: 48 81 ec 70 01 00 00                    sub rsp, 0x170
7ff8341b467a [7]: 48 8b 05 d7 ed 0b 00                    mov rax, qword ptr ds:[0x00007FF834273458] -> 7ff834273458
7ff8341b4681 [3]: 48 33 c4                                xor rax, rsp
7ff8341b4684 [4]: 48 89 45 60                             mov qword ptr ss:[rbp+0x60], rax
7ff8341b4688 [3]: 45 33 f6                                xor r14d, r14d
7ff8341b468b [5]: 44 89 44 24 48                          mov dword ptr ss:[rsp+0x48], r8d
7ff8341b4690 [7]: 44 39 35 8d e9 0b 00                    cmp dword ptr ds:[0x00007FF834273024], r14d -> 7ff834273024
7ff8341b4697 [3]: 41 8b f0                                mov esi, r8d
7ff8341b469a [2]: 8b fa                                   mov edi, edx
7ff8341b469c [4]: 89 54 24 40                             mov dword ptr ss:[rsp+0x40], edx
7ff8341b46a0 [3]: 48 8b d9                                mov rbx, rcx
7ff8341b46a3 [4]: 48 89 4d 20                             mov qword ptr ss:[rbp+0x20], rcx
7ff8341b46a7 [5]: c6 44 24 50 00                          mov byte ptr ss:[rsp+0x50], 0x00
7ff8341b46ac [6]: 0f 85 9e b4 02 00                       jnz 0x00007FF8341DFB50 -> 7ff8341dfb50
7ff8341b46b2 [7]: f6 05 47 2b 0c 00 02                    test byte ptr ds:[0x00007FF834277200], 0x02 -> 7ff834277200
7ff8341b46b9 [6]: 0f 85 1a b5 02 00                       jnz 0x00007FF8341DFBD9 -> 7ff8341dfbd9
7ff8341b46bf [3]: 0f 57 c0                                xorps xmm0, xmm0

[+] Info: Front instruction: 7ff8341b4660 [5]: e9 69 c9 fc ff                          jmp 0x00007FF834180FCE -> 7ff834180fce

[+] Info: Destination: 0x00007ff834180fce

[+] Info: Destination function instruction(s):

[!] Warn: Couldn't decompile instructions at followed jmp
[!] Error: Prologue jmp resolution failed
Successfully hooked: 0
SEV: Prologue jmp resolution failed
Called count: 0
Called count: 0
Called count: 0
Called count: 0
Called count: 0
Unhooking...
[!] Error: Detour unhook failed: no hook present
Successfully unhooked: 0
SEV: Detour unhook failed: no hook present
Appuyez sur une touche pour continuer...

Would you be okay with also extending the logging in the followJmp() code by default? I think that it's a great idea for other people who may want to debug issues or just for learning.

BullyWiiPlaza commented 1 year ago

I'm really ripping my hair out here. I just investigated the issue on the French guy's machine using Cheat Engine and I noticed his address 0x00007ff834180fce points to DiscordHook64.dll: 7FF834180FCE - FF25 00000000 607852F5FF7F0000 - jmp DiscordHook64.dll+17860 One remarkable thing here is that this is an absolute jmp. For me at the same spot in the call chain, I see something like: 7FFA5A8606CA - E9 4188C6E9 - jmp gameoverlayrenderer64.dll+88F10 This is a relative jmp. For testing, I rewrote mine to be an absolute jmp as well: 7FFA5A8606CA - FF25 00000000 108F4C44FA7F0000 - jmp gameoverlayrenderer64.dll+88F10 The disassembler logging then said the following:

[+] Info: Destination function instruction(s):
7ffa5a8606ca [6]: ff 25 00 00 00 00                       jmp qword ptr ds:[0x00007FFA5A8606D0] -> 7ffa444c8f10
7ffa5a8606d0 [6]: 10 8f 4c 44 fa 7f                       adc byte ptr ds:[rdi+0x7FFA444C], cl

It seems like Zydis disassembles absolute jmps wrongly in this context. However, resolving the jmp works as expected. I'd assume the output to only be a jmp instruction though. Does that really never cause any other problems?

I also checked the code at DiscordHook64.dll+17860 (7FFFF5527860) and it seemed like normal code starting the stack frame with DiscordHook64.dll+17860 - 55 - push rbp so this could've been hooked then if all would've went well.

At the end of the day I still couldn't reproduce the issue on my machine: The French guy's machine refuses to disassemble the jmp DiscordHook64.dll instruction part and therefore fails to hook, while mine disassembles wrongly, but hooks correctly and all that. It's truly very strange to me. I hope you still have any ideas what the issue could be. Is it a Zydis bug? Do you call Zydis wrong? Is Zydis up-to-date? Is it a problem with the vcpkg port or my local setup?

stevemk14ebr commented 1 year ago

The adc instruction you see following the ff25 is the immediate value encoding the final destination of the ff25. FF25 differs from E9 style jmp in how the destination is encoded.

FF25 goes to [destination] E9 goes to destination

Notice the brackets, it dereferenced the pointed to thing. So in this case FF2500000 says the pointer is immediately next, and the dereferenced value is stored there.

Specifically what is wrong I'm not sure I will have to test, I'm unfortunately not able to work on this until 2 weeks from now at the earliest.

It's good to know this is an external software causing the issue. As a temporary workaround the discord overlay can be disabled. I also would be curious to see if detours follows that ff25 or if it overwrites it (thus breaking discords hook)

stevemk14ebr commented 1 year ago

If you could please have the Frenchman create a dump of the full process that will be enough information to triage this when I am free in the coming weeks.

https://www.ibm.com/support/pages/how-manually-generate-process-dump-using-process-explorer

Can be followed as a reference, be sure to make a full process dump. This would be done on the target process you are hooking/injecting into (likely a game as you're hooking directx and discord overlay is involved)

BritishPiper commented 1 year ago

I think I fixed that problem in #186. I'm waiting for OP to test. The discord hook is at the very end of a memory region and polyhook attempts to read 100 = 0x64 bytes with ReadProcessMemory. It returns a failure "ERROR_PARTIAL_COPY", that is just skipped, but it doesn't actually read any bytes, even though it could've read 10-15 bytes. I've added some code to check for the end of a memory region and read less bytes. Of course, I maintained the pattern of not throwing any error/exception.

BullyWiiPlaza commented 1 year ago

I can confirm via the French guy's machine that the fix by @BritishPiper works! Thanks again.