【C++】實現D3D9 的 Inline hook

【C++】實現D3D9 的 Inline hook
 
簡單介紹一下HOOK原理:
    函數調用的過程大致是 先push 參數 進去,再執行 call 函數地址 ,進入函數。此時將所調用的函數的前五個位元組修改,將開頭改成 jmp xxxxxxxx(地址偏移) ,則可以進入自己的函數,執行自己函數的內容,執行完自己的函數後,還原修改的函數,使原本函數的功能不受影響,完成HOOK。D3D9 的hook 就是HOOK d3d函數
 
 
一些必備知識點:
    jmp 的硬編碼是E9 ,在構造位元組的時候,第一個位元組為' \xe9'
    在修改記憶體前,應當先修改記憶體的屬性,防止記憶體的屬性是不可寫的狀態
    地址偏移的演算法:自己函數的地址 - (原來函數的地址 + 位元組數),此處替換5個位元組,所以是自己函數的地址 - (原來函數的地址 + 5)
 
大致思路:
  寫一個DLL,在DLL裡面完成HOOK,隨便寫一個DLL注入器注入到進程即可
 
  DLL部分:
#include<process.h>
int __stdcall DllMain(void* _DllHandle, unsigned long _Reason, void*_Reserved)
{
    if (_Reason == DLL_PROCESS_ATTACH)
    {
        _beginthreadex(nullptr, 0, initialize_d3d9, nullptr, 0, nullptr);
    }
    return 1;
}
// 註:
initialize_d3d9是d3d9部分的函數

 
  HOOK部分:
    構造一個inline hook類,傳入 被修改函數的地址 和 自己函數的地址 ,根據傳入的兩個參數算出偏移,保存原函數的前五個位元組
    一個函數,修改原函數,將構造的位元組寫入到記憶體
    一個函數,還原原函數,將保存下來的位元組寫入記憶體
    一個函數,修改記憶體屬性 
  HOOK部分完成

  D3D部分:
    初始化D3D9
    用上面的函數HOOK掉D3D9的函數
  D3D部分完成
 
 
程式碼部分(HOOK部分):
#pragma once
#include<Windows.h>
constexpr int byte_length = 5; //修改函數的前五個位元組,修改為jmp

class inline_hook
{
private:
    using uchar = unsigned char;
    using dword = DWORD;
    uchar m_original_byte[byte_length];
    uchar m_self_byte[byte_length];
    int m_original_address; //原始函數的地址
    int m_self_address;//自己函數的地址
    dword motify_memory_attribute(int address, dword attribute = PAGE_EXECUTE_READWRITE)
    {
        dword old_attributes;
        VirtualProtect(reinterpret_cast<void*>(address), byte_length, attribute, &old_attributes);
        return old_attributes;
    }
public:
    inline_hook(int original_address, int self_address) :m_original_address(original_address), m_self_address(self_address)
    {
        m_self_byte[0] = '\xe9'; //jmp的硬編碼
        int offset = self_address - (original_address + byte_length);   //計算偏移
        memcpy(&m_self_byte[1], &offset, byte_length - 1); //構造位元組
        dword attribute = motify_memory_attribute(original_address); //修改記憶體屬性
        memcpy(m_original_byte, reinterpret_cast<void*>(original_address), byte_length); //將程式原來的函數程式碼保存到 m_original_byte 
        motify_memory_attribute(original_address, attribute); // 還原記憶體屬性
    }
    void motify_address() //修改原來的函數
    {
        dword attribute = motify_memory_attribute(m_original_address); //修改記憶體屬性
        //寫入自己構造的位元組
        memcpy(reinterpret_cast<void*>(m_original_address), m_self_byte, byte_length);// 將構造好的位元組,覆蓋到原來的函數的地方
        motify_memory_attribute(m_original_address, attribute); //還原記憶體屬性
    }
    void restore_address() //恢復原來的函數
    {
        dword attribute = motify_memory_attribute(m_original_address);
        memcpy(reinterpret_cast<void*>(m_original_address), m_original_byte, byte_length);
        motify_memory_attribute(m_original_address, attribute);
    }
};

 

程式碼部分(D3D9部分):

/*需要包含的頭文件*/
#include<d3d9.h>
#include<HOOK部分程式碼>
#pragma comment(lib,"d3d9.lib")
/*一些必要的全局變數*/
IDirect3D9 * g_direct3d9 = nullptr;
IDirect3DDevice9 *g_direct3ddevice9 = nullptr;
D3DPRESENT_PARAMETERS g_present;
HWND g_hwnd = FindWindowA(nullptr, "Counter-Strike: Global Offensive - Direct3D 9");
//HWND g_hwnd = FindWindowA("Valve001",nullptr);

inline_hook*g_Reset_hook = nullptr; //hook Reset 函數
inline_hook*g_EndScene_hook = nullptr;
inline_hook*g_DrawIndexedPrimitive_hook = nullptr;

/*如果需要劫持Reset函數,則寫一個函數代替原來的Reset*/

HRESULT __stdcall self_Reset(IDirect3DDevice9 *g_direct3ddevice9, D3DPRESENT_PARAMETERS* pPresentationParameters)
{
printf("join\n"); //自己需要實現的功能寫在這裡
HRESULT result = D3D_OK;
g_Reset_hook->restore_address();
if (g_direct3ddevice9)
{
ImGui_ImplDX9_InvalidateDeviceObjects();
HRESULT result = g_direct3ddevice9->Reset(pPresentationParameters); //調用原來的Reset函數
ImGui_ImplDX9_CreateDeviceObjects();
}
g_Reset_hook->motify_address();
return result;
}
 

/*DLL注入後執行此函數*/

unsigned int __stdcall initialize_d3d9(void*data)
{
AllocConsole();
freopen("CON", "w", stdout);
g_direct3d9 = Direct3DCreate9(D3D_SDK_VERSION);
check_error(g_direct3d9, "Direct3DCreate9失敗");
memset(&g_present, 0, sizeof(g_present));
g_present.Windowed = TRUE;
g_present.SwapEffect = D3DSWAPEFFECT_DISCARD;
g_present.BackBufferFormat = D3DFMT_UNKNOWN;
g_present.EnableAutoDepthStencil = TRUE;
g_present.AutoDepthStencilFormat = D3DFMT_D16;
HRESULT result = g_direct3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &g_present, &g_direct3ddevice9);
check_error(result == 0, "CreateDevice失敗");
int *direct3d9_table = (int*)*(int *)g_direct3d9;
int *direct3ddevice9_table = (int*)*(int *)g_direct3ddevice9;//這裡是一個表,儲存了direct的函數,通過劫持這些函數,轉接到自己的函數,通過速覽IDirect3DDevice9的定義可以看到函數
g_Reset_hook = new inline_hook(direct3ddevice9_table[16], (int)self_Reset); //hook掉了reset
g_Reset_hook->motify_address(); //還原了Reset
//direct3ddevice9_table[下標],下標通過速覽定義IDirect3DDevice9結構可知,看需要hook掉的函數在結構的第幾位,減去一則是下標
return 0;
}

 

此時以DLL編譯,完成後用DLL注入器注入,則完成D3D9的inline hook,需要HOOK其他函數,則仿照Reset_hook 的寫法

g_Reset_hook = new inline_hook(direct3ddevice9_table[16], (int)self_Reset); //hook掉了reset
g_Reset_hook->motify_address(); //還原了Reset

 HOOK 掉 DrawIndexedPrimitive舉例

先寫自己的函數

HRESULT __stdcall self_DrawIndexedPrimitive(IDirect3DDevice9 *g_direct3ddevice9,D3DPRIMITIVETYPE type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
    HRESULT result = D3D_OK;
    {
        /*自己的功能*/
    }
    g_DrawIndexedPrimitive_hook->restore_address();

    result = g_direct3ddevice9->DrawIndexedPrimitive(type, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);

    g_DrawIndexedPrimitive_hook->motify_address();
    
    return result;
}

在 initialize_d3d9 函數中增加

g_DrawIndexedPrimitive_hook = new inline_hook(direct3ddevice9_table[82], (int)self_DrawIndexedPrimitive); //hook掉了DrawIndexedPrimitive
g_DrawIndexedPrimitive_hook->motify_address();

至此,完成對DrawIndexedPrimitive的HOOK

 

 

ENDING.............