c++ - 为什么这个绕行函数会使程序崩溃?

标签 c++ assembly dll hook x86-64

我有一个代理 dxgi.dll,我试图绕过原始 dxgi.dll 中的 Present 函数,以便在屏幕上呈现内容。 .dll加载成功,绕道而行。然而,一旦我的新 Present 被调用,绕道就会使程序崩溃。 请记住 .dll 和程序是 64 位的。

下面是函数在修改之前在内存中的样子(开始突出显示):

好吧,我刚刚发现除非我有 10 个声誉,否则我不能直接在这里发布图片,所以使用这个链接(替换 DOT): https://imgur DOT com/a/Jf53dYc

我不确定它崩溃的确切位置,我相信程序会继续运行一段时间,但它肯定会在中间/在调用 detour Present 后不久崩溃,我知道这一点,因为我可以将指针写到在 Present 崩溃之前将 SwapChain 参数传递给文件。

我使用 IDA 找到了原始的 Present 函数地址。你可以在 imgur 画廊的图片上看到 IDA 对函数的描述。

我一直在查看内存并试图找出问题所在,当我使用 Cheat 引擎跟随跳跃时,它们会引导到正确的位置,但是绕行的某些东西导致程序崩溃。覆盖的操作码似乎也被正确替换。

我试图更改我的 Present 函数的调用约定和返回类型,我在 dxgi Hook 指南中读到返回类型是 HRESULT,我尝试更改为此无济于事。至于调用约定,我已经尝试过 WINAPI。

我还调查了堆栈或寄存器是否因我的函数迂回而被破坏。但是我不太擅长组装,我不能确定是否是这种情况。

我有一个名为 Core 的类负责 Hook ,这是包含一些相关定义的头文件:

#pragma once
#include <iostream>
#include <Windows.h>
#include <intrin.h>
#include <dxgi.h>
#include <fstream>

// Seems my C++ doesn't have QWORD predefined, defining it myself
typedef unsigned __int64 QWORD; 

// Definition of the structure of the DXGI present function
typedef __int64 (__fastcall* PresentFunction)(IDXGISwapChain *pSwapChain, UINT SyncInterval, UINT Flags);

class Core
{
private:
    QWORD originalDllBaseAddress;
    QWORD originalPresentFunctionOffset;

public:
    void Init();
    bool Hook(PresentFunction originalFunction, void* newFunction, int bytes);
    ~Core();
};

Init 通过获取相关地址来启动进程:

void Core::Init()
{
originalDllBaseAddress = (QWORD)GetModuleHandleA("dxgi_.dll");
originalPresentFunctionOffset = 0x5070;
originalPresentFunction = (PresentFunction)(originalDllBaseAddress + (QWORD)originalPresentFunctionOffset);
Hook(originalPresentFunction, FixAndReturn, 14);
}

Hook 试图在目标地址中放置一个跳转,我坚信问题出在此处,(评论现在改变了我的想法,它可能与汇编、寄存器或堆栈)更具体地说是对 originalFunction 的赋值:

bool Core::Hook(PresentFunction originalFunction, void* newFunction, int length)
{
    DWORD oldProtection;

    VirtualProtect(originalFunction, length, PAGE_EXECUTE_READWRITE, &oldProtection);

    memset(originalFunction, 0x90, length);

    // Bytes are flipped (because of endianness), could alternatively use _byteswap_uint64()
    *(QWORD*)originalFunction = 0x0000000025FF;

    // The kind of jump I'm doing here seems to only use 6 bytes,
    // and then grabs the subsequent memory address,
    // I'm not quite sure if I'm doing this right
    *(QWORD*)((QWORD)originalFunction + 6) = (QWORD)newFunction;

    DWORD temp;
    VirtualProtect(originalFunction, length, oldProtection, &temp);

    originalPresentFunction = (PresentFunction)((QWORD)originalFunction + length);

    presentAddr = (QWORD)Present;
    jmpBackAddr = (QWORD)originalPresentFunction;

    return true;
}

在将字节写入内存时,我尝试了很多方法,但都没有解决我的问题。

在函数末尾对“originalPresentFunction”的赋值是绕道将尝试跳回的地址。

这里是绕行函数的定义,位于 Core.cpp 中:

__int64 __fastcall Present(IDXGISwapChain *pSwapChain, UINT SyncInterval, UINT Flags)
{
    //The program crashes with and without these file writes.
    std::ofstream file;
    file.open("HELLO FROM PRESENT.txt");
    file << pSwapChain;
    file.close();

    return originalPresentFunction(pSwapChain, SyncInterval, Flags);
}

这是调用时导致崩溃的函数。如您所见,我正在将 pSwapChain 参数写入此处的文件。我这样做是为了测试参数是否从原始函数传递。这次写成功了,文件的内容看起来像一个有效的指针。因此崩溃发生在这次写入之后。 FixAndReturn() 是一个汇编函数。

includelib legacy_stdio_definitions.lib

.data
extern presentAddr : qword
extern jmpBackAddr : qword

; This performs instructions originally performed by dxgi.dll in the
; memory that we've replaced, and then returns

.code
    FixAndReturn PROC 
        call [presentAddr]
        mov [rsp+10h],rbx
        mov [rsp+20h],rsi
        push rbp
        push rdi
        push r14
        jmp qword ptr [jmpBackAddr]
    FixAndReturn ENDP
end

如果需要更多代码,我已将完整代码上传到 Github: https://github.com/techiew/KenshiDXHook

最佳答案

好久没忙其他事情了,现在绕行功能成功了。

看了网上的资源,想了很多。答案很简单。在我的FixAndReturn 汇编代码中,我需要做的只是jmp 到detour 函数,不需要call调用 可能会不必要地改变我们不想要的东西,并且我们的绕行函数在参数和其他方面已经与原始函数相同,所以它已经从相同的地方读取参数原始函数调用放置了它们。这意味着 jmp 可以很好地运行我们的绕行函数。组装时不需要额外的插入或弹出来工作。

以下是该过程的基本概述:

  • 我们的钩子(Hook)是通过将 jmp 放在我们的汇编代码的开头来放置的。
  • 我们的汇编代码立即跳转到我们的迂回/ Hook 函数。
  • 当绕行函数完成时,它返回一个函数调用。

这个函数调用使用了一个与我们 Hook 的原始函数相同的 typedef。它看起来像这样:

typedef HRESULT (__fastcall* PresentFunction)(IDXGISwapChain *pSwapChain, UINT SyncInterval, UINT Flags);

使用 typedef 返回函数是这样完成的,带有原始参数值:

return ((PresentFunction)coreRef->newPresentReturn)(swapChain, syncInterval, flags);

基本上这里发生的是我们第二个汇编代码 jmp 指令之后指向我们的绕行函数的地址被返回并作为函数调用,因此我们跳转到绕行,跳回并执行原始代码。 (coreRef->newPresentReturn 包含紧跟在 jmp 指令之后的地址)。

我们现在坚持原始 Present 函数的调用约定,我们传入的参数放在正确的位置,寄存器和堆栈等任何地方都没有以任何方式损坏。

使用的资源:Guidedhacking.com - D3D11 barebones hook

完整代码在我的 Github 上:https://github.com/techiew/KenshiHook

关于c++ - 为什么这个绕行函数会使程序崩溃?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56351467/

相关文章:

c++ - 如何 Hook MFC 自定义控件类中的控件关闭

c - 当堆栈增长时,谁负责向操作系统请求页面?

c++ - 按引用和按值传递时的 gcc 程序集

string - 交换字符串的第一个和最后一个字符会导致段错误

multithreading - 仅按需从 TThread 动态初始化和调用 LoadLibrary 一次

c++ - 动态链接与静态链接效率

c++ - 做个圈子把别人推开?

python - 导入错误 : DLL load failed with pybind11 and PCL

c# - 如何在 Visual Studio C# 2010 速成版中创建 DLL 文件?

c++ - 返回对 Armadillo vector 元素的引用