为什么此绕行功能会使程序崩溃?

时间:2019-05-29 00:03:12

标签: c++ assembly dll hook x86-64

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

下面是该功能在内存中的外观的图像修改之前(突出显示开始):

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

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

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

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

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

我还仔细研究了堆栈或寄存器是否因函数绕行而损坏。但是我对汇编不是很好,不能确定是否是这种情况。

我有一个名为Core的类来处理钩子,这是头文件,其中包含一些相关的定义:

#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

1 个答案:

答案 0 :(得分:0)

已经有一段时间了,我一直在忙于其他事情,但是现在我已经使绕道功能成功运行了。

在查看了网络上的资源并进行了很多思考之后。答案很简单。在我的 FixAndReturn 汇编代码中,我要做的就是 jmp 绕行功能,不需要调用调用可能会不必要地更改我们不希望做的事情,并且绕行函数在参数方面与原始函数完全相同,因此它已从同一位置读取了参数原始函数调用将其放置。这意味着 jmp 可以很好地运行我们的绕行功能。组装时不需要额外的推动或弹出即可使它起作用。

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

  • 通过在我们的汇编代码的开头放置一个 jmp 来放置我们的钩子。
  • 我们的汇编代码立即跳转到我们的绕行/挂钩功能。
  • 完成绕行功能后,它将返回一个函数调用。

此函数调用使用的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