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

下图显示了在修改之前该函数在内存中的外观(开始突出显示):

好的,所以我才知道除非我有10个声誉,否则不允许直接在此处发布图像,因此请使用此链接(代替DOT):
https://imgur DOT com / a / Jf53dYc

我不确定它到底在哪里崩溃,我相信程序会继续运行一会儿,但是它在绕行Present调用后肯定会在中间/很快崩溃,我知道这是因为我可以将指向SwapChain参数的指针写入崩溃之前从“Present”改道内部获取一个文件。

我使用IDA找到了原始的Present函数地址。您可以在imgur画廊中的图片上看到IDA所说的功能。

我一直在查看内存,并试图找出问题所在,当我使用Cheat引擎跟踪跳转时,它们会指向正确的位置,但是绕道而行会使程序崩溃。被覆盖的操作码似乎也已被正确替换。

我试图在我的Present函数上更改调用约定和返回类型,我在dxgi挂钩指南中读到返回类型是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();
};


初始化通过获取相关地址来启动该过程:

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 应用于绕行功能,不需要调用调用可能会不必要地更改我们不想做的事情,并且绕行函数在参数和其他方面与原始函数相同,因此它已经从与原始函数调用相同的位置读取了参数他们。这意味着 jmp 将可以很好地运行我们的绕行功能。组装时不需要额外的 push 或弹出即可使它起作用。

这是该过程的基本概述:

  • 通过在我们的汇编代码的开头放置一个 jmp 来放置我们的钩子(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

    09-06 20:12