【渲染逆向】Hook D3D API

防止被跨省松却,只放一個(gè)預(yù)覽

前言

??RenderDoc等一系列抓幀工具的原理沫浆,是在運(yùn)行前,在圖形API初始化之前將自己的dll注入到目標(biāo)程序中洲尊,并hook一系列圖形API远豺,得到API調(diào)用時(shí)的參數(shù)。如果在圖形API初始化之后Hook坞嘀,很可能出現(xiàn)無法檢測(cè)到圖形API(打開UI后躯护,上面的API檢測(cè)顯示none)。
??我們自己寫的圖形程序如果沒加保護(hù)丽涩,能直接用RenderDoc注入進(jìn)去棺滞。

注入方式

??不管是注入自己寫的dll,還是renderdoc等已經(jīng)寫好的dll内狸,都需要一個(gè)注入器〖烀校現(xiàn)有的注入器,例如RemoteDLL就很不錯(cuò)昆淡,簡(jiǎn)單輕便:

RemoteDLL

??打開程序后锰瘸,選擇目標(biāo)進(jìn)程,然后選擇DLL昂灵,點(diǎn)擊注入避凝。
??注意:注入目標(biāo)程序、注入器眨补、DLL管削,三者的32位、64位必須一致撑螺,并且如果目標(biāo)程序有管理員權(quán)限含思,注入器沒有,就可能導(dǎo)致注入器找不到目標(biāo)程序。
??這只是基本需求含潘,但圖形API的特殊性饲做,很多函數(shù)需要在圖形API初始化之前Hook,這就需要在程序打開的第一時(shí)間注入dll遏弱,因此我們需要自己實(shí)現(xiàn)一個(gè)注入器盆均。
??實(shí)現(xiàn)基本注入器需要引入的頭文件:

#include <windows.h>
#include <iostream>
#include <tchar.h>

??
??首先我們?cè)O(shè)定要打開程序的exe路徑、命令行參數(shù)漱逸、以及工作目錄泪姨,后兩者并不是必須的。命令行參數(shù)自然不必多說饰抒,工作目錄一般是我們自己寫Visual Studio時(shí)肮砾,編譯鏈接出來的exe才會(huì)和工作路徑不一致,后兩者如果沒有特殊需求袋坑,都可以是NULL唇敞。

TCHAR szExePath[] = TEXT("你的exe路徑");//你的EXE路徑
TCHAR szForceDX12Cmdline[] = TEXT("-force-d3d12");//你的命令行參數(shù),這個(gè)事例參數(shù)是強(qiáng)制unity游戲以d3d12運(yùn)行咒彤,可以根據(jù)需要更改
TCHAR szWorkspace[] = TEXT("程序的工作目錄");//程序的工作目錄

??然后利用CreateProcess創(chuàng)建進(jìn)程,打開程序:

//CreateProcess的返回值
BOOL bSuccess = FALSE;
//CreateProcess傳出的進(jìn)程信息
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags |= STARTF_USESTDHANDLES;
bSuccess = CreateProcess(
    szExePath,//exe路徑
    szForceDX12Cmdline,//命令行參數(shù)
    NULL,
    NULL,
    TRUE,
    0,
    NULL,
    szWorkspace,//工作路徑
    &si,
    &pi
);

if (!bSuccess)
{
    std::cout << "創(chuàng)建失敗" << std::endl;
}
else
{
    std::cout << "成功咒精,進(jìn)程號(hào)為:" << pi.dwProcessId << std::endl;
}

??這樣就能打開進(jìn)程(如果成功)镶柱,并通過PROCESS_INFORMATION對(duì)象得到進(jìn)程信息。
??然后我們寫一個(gè)注入方法模叙,參數(shù)是dll的地址和目標(biāo)進(jìn)程號(hào):BOOL Inject(LPCTSTR DLLPath, DWORD ProcessID)歇拆。
??我們選擇遠(yuǎn)程線程注入方法。每個(gè)進(jìn)程之間的空間彼此隔離范咨,注入器無法操控目標(biāo)進(jìn)程的空間故觅,但可以通過開啟一個(gè)遠(yuǎn)程線程的方法。
??我對(duì)遠(yuǎn)程線程注入并不能描述的很明白渠啊,但網(wǎng)上資料有很多输吏,這里放上我的代碼:

BOOL Inject(LPCTSTR DLLPath, DWORD ProcessID)
{
    HANDLE hProcess = nullptr;
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, ProcessID);
    if (!hProcess)
    {
        std::cout << "打開目標(biāo)進(jìn)程句柄失敗" << std::endl;
        return FALSE;
    }

    SIZE_T PathSize = (_tcslen(DLLPath) + 1) * sizeof(TCHAR);

    LPVOID StartAddress = VirtualAllocEx(hProcess, NULL, PathSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    if (!StartAddress)
    {
        std::cout << "申請(qǐng)路徑地址空間失敗" << GetLastError() << std::endl;
        return FALSE;
    }

    if (!WriteProcessMemory(hProcess, StartAddress, DLLPath, PathSize, NULL))
    {
        std::cout << "傳入路徑地址空間失敗" << std::endl;
        return FALSE;
    }

    PTHREAD_START_ROUTINE pfnStartAddress = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "LoadLibraryW");

    if (!pfnStartAddress)
    {
        std::cout << "獲取LoadLibraryW函數(shù)地址失敗" << std::endl;
        return FALSE;
    }

    HANDLE hThread = CreateRemoteThreadEx(hProcess, NULL, NULL, pfnStartAddress, StartAddress, NULL, NULL, NULL);
    if (!hThread)
    {
        std::cout << "打開遠(yuǎn)程線程失敗" << std::endl;
        return FALSE;
    }

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
}

??大概是找到kernel32.dll的LoadLibraryW方法的地址,并調(diào)用加載DLL替蛉。
??最后注入就完事了:

TCHAR RenderDocDll[] = TEXT("我的renderdoc.dll的地址");
if (!Inject(RenderDocDll, pi.dwProcessId))
    std::cout << "創(chuàng)建遠(yuǎn)程線程失敗" << std::endl;
else
    std::cout << "成功創(chuàng)建遠(yuǎn)程線程" << std::endl;

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

??這樣就能通過遠(yuǎn)程注入的方式贯溅,將renderdoc注入圖形API程序中,分析渲染過程躲查。
??但假如有人不止想做這么多呢它浅?
??在在SwapChain執(zhí)行Present前,對(duì)RenderTarget進(jìn)行一次后處理镣煮,豈不是能做出類似調(diào)色個(gè)功能姐霍?
??如果把人物渲染DrawCall的深度測(cè)試關(guān)閉,豈不是就能透視?
??很多外掛或圖形調(diào)整插件都是這么做的镊折。我們也可以自己實(shí)現(xiàn)一個(gè)dll胯府,用來hook圖形API。

inline hook DLL

??首先要引入一票頭文件:

#include <Windows.h>
//提供_beginthreadex函數(shù)
#include <process.h>
//用于拍攝快照腌乡,檢查當(dāng)前進(jìn)程已經(jīng)加載了哪些dll
#include <TlHelp32.h>
#include <tchar.h>
//d3d的頭文件
#include <d3d11.h>
#include <dxgi.h>

#include <d3dx11tex.h>

//STL
#include <string>
#include <sstream>
#include <vector>
#include <iostream>

??d3dx11tex.h是我用來保存RT的盟劫,需要在微軟下載DirectX2010 SDK安裝,這里面d3d相關(guān)的庫都需要鏈接:

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

??如果找不到鏈接lib文件与纽,還要到項(xiàng)目屬性>鏈接器>常規(guī)>附加庫目錄中侣签,把lib目錄填入。(不知道在哪就用everything找急迂,找不到就上網(wǎng)查一查安裝)影所。
??除此外我也不打算手動(dòng)實(shí)現(xiàn)inline hook(因?yàn)椴?,所以hook交給微軟的hook庫Detours就好了僚碎。下載源碼后猴娩,打開VS (2017)的開發(fā)人員命令提示符(可以在VS工具菜單/命令行/開發(fā)者命令提示中找到),進(jìn)入src目錄下勺阐,輸入nmake命令卷中,在得到的include目錄下得到detours.h頭文件,并鏈接lib.X64目錄下detours.lib渊抽。
??dll的main函數(shù):

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID)
{
    DisableThreadLibraryCalls(hInstance);

    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        _beginthreadex(nullptr, 0, init, nullptr, 0, nullptr);
        break;
    }

    return TRUE;
}

??DLL_PROCESS_ATTACH是當(dāng)dll注入進(jìn)程后調(diào)用蟆豫。
??init函數(shù)用于初始化,我們還未實(shí)現(xiàn)懒闷,現(xiàn)在就來實(shí)現(xiàn)init函數(shù)十减。

unsigned int __stdcall init(void* data)
{
    return 0;
}

??嗯,這就是基礎(chǔ)的init函數(shù)愤估,假如在里面寫一個(gè)MessageBox帮辟,用注入器注入后,就可以彈出一個(gè)對(duì)話框玩焰。為了方便我們調(diào)試由驹,可以在目標(biāo)進(jìn)程中打開一個(gè)對(duì)話框:

bool OpenConsole()
{
    if (AllocConsole()) {
        freopen("CONOUT$", "w", stdout);
        SetConsoleTitle(L"Debug Console");
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
        std::cout << "Hello Inject!" << std::endl;
        return true;
    }

    return false;
}

??接下來要Hook D3D11的方法,有兩種震捣,一種是類似D3D11CreateDevice這樣荔棉,作用域是全局的函數(shù),另一種是類似IDXGISwapChain::Present這樣的成員函數(shù)蒿赢。先說后者润樱。

VMT Hook

??我不清楚怎么找一個(gè)成員函數(shù)的地址,或許直接用類名ClassName::*MethodName羡棵,不過D3D這些成員函數(shù)都有些特殊壹若,它們都是虛函數(shù),地址存儲(chǔ)在虛表中。
??根據(jù)C++對(duì)象內(nèi)存布局店展,如果對(duì)象有虛函數(shù)养篓,那么對(duì)象最前面就是虛表指針vfptr,我們可以用x64dbg赂蕴,或VS的命令行查看IDXGISwapChain的內(nèi)存布局:解決方案屬性>C++>命令行>添加 /d1 reportSingleClassLayoutIDXGISwapChain >應(yīng)用>編譯項(xiàng)目柳弄,就能看到SwapChain的布局:

IDXGISwapChain內(nèi)存布局
??可以看到其中的18個(gè)方法,對(duì)應(yīng)繼承結(jié)構(gòu):

IDXGISwapChain : public IDXGIDeviceSubObject
IDXGIDeviceSubObject : public IDXGIObject
IDXGIObject : public IUnknown
IUnknown

??為了方便查表概说,我們可以把生成的表單粘貼下來做成枚舉:

//D3D_VMT_Indices.h
//VMT是Virtual Method Table的縮寫
enum class IDXGISwapChainVMT{
    QueryInterface,
    AddRef,
    Release,
    SetPrivateData,
    SetPrivateDataInterface,
    GetPrivateData,
    GetParent,
    GetDevice,
    Present,
    GetBuffer,
    SetFullscreenState,
    GetFullscreenState,
    GetDesc,
    ResizeBuffers,
    ResizeTarget,
    GetContainingOutput,
    GetFrameStatistics,
    GetLastPresentCount
};

??在DX11中碧注,除了SwapChain外,最常用的還有ID3D11DeviceID3D11DeviceContext的虛表方法糖赔,用同樣的方法寫出這兩個(gè)類的虛表枚舉萍丐。
??要Hook這些虛函數(shù),先要獲取它們的虛表指針放典,我們獲取不到目標(biāo)程序創(chuàng)建的Device逝变、Context、SwapChain對(duì)象奋构,但好笑的是壳影,相同類型的對(duì)象共用一個(gè)虛表指針的地址,所以我們可以創(chuàng)建一個(gè)Device弥臼、Context态贤、SwapChain,雖然這些不能用于渲染醋火,但可以得到虛表指針,然后Hook其中的虛函數(shù)箱吕,當(dāng)D3D程序內(nèi)部Device等對(duì)象調(diào)用這些函數(shù)時(shí)芥驳,會(huì)自己把自己送給我們。
??當(dāng)然茬高,當(dāng)前的任務(wù)還是獲取虛表指針兆旬,為此我們創(chuàng)建這三個(gè)對(duì)象:

void** g_pDeviceVMT = nullptr;
void** g_pSwapchainVMT = nullptr;
void** g_pDeviceContextVMT = nullptr;

//用于創(chuàng)建Device、SwapChain怎栽、Context丽猬,只要能成功創(chuàng)建出來,參數(shù)是隨意的
bool GetD3D11VMT()
{
    //這些對(duì)象只是為了獲取虛表熏瞄,并不需要被使用
    ID3D11Device* l_pDevice = nullptr;
    IDXGISwapChain* l_pSwapchain = nullptr;
    ID3D11DeviceContext* l_pDeviceContext = nullptr;

    DXGI_SWAP_CHAIN_DESC scd;
    ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

    scd.BufferCount = 1;

    scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    scd.BufferDesc.Width = 1920;
    scd.BufferDesc.Height = 1080;

    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

    scd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    scd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;

    scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

    scd.OutputWindow = GetForegroundWindow();

    scd.BufferDesc.RefreshRate.Numerator = 60;
    scd.BufferDesc.RefreshRate.Denominator = 1;

    scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    scd.SampleDesc.Count = 1;
    scd.SampleDesc.Quality = 0;

    scd.Windowed = ((GetWindowLongPtr(GetForegroundWindow(), GWL_STYLE) & WS_POPUP) != 0) ? false : true;

    D3D_FEATURE_LEVEL featLevel;
    HRESULT hr = D3D11CreateDeviceAndSwapChain(
        nullptr,
        D3D_DRIVER_TYPE_REFERENCE,
        nullptr,
        0,
        nullptr,
        0,
        D3D11_SDK_VERSION,
        &scd,
        &l_pSwapchain,
        &l_pDevice,
        &featLevel,
        nullptr
    );

    if (FAILED(hr))
    {
        std::cout << "創(chuàng)建D3D11Device和SwapChain失敗" << std::endl;
        return false;
    }

    l_pDevice->GetImmediateContext(&l_pDeviceContext);

    //獲取虛表
    g_pSwapchainVMT = *(void***)l_pSwapchain;
    g_pDeviceVMT = *(void***)l_pDevice;
    g_pDeviceContextVMT = *(void***)l_pDeviceContext;

    std::cout << "獲取虛表成功" << std::endl;
    return true;
}

??此時(shí)我們就可以通過Detours Hook虛函數(shù)了脚祟,我這里演示下Present的Hook流程:

//定義Present的類型,注意因?yàn)槭浅蓡T虛函數(shù)强饮,第一個(gè)參數(shù)要傳入對(duì)象的地址
using vfn_SwapChain_Present = HRESULT(WINAPI*) (IDXGISwapChain* pThis, UINT SyncInterval, UINT Flags);
//原來Present的地址
vfn_SwapChain_Present oPresent = nullptr;

//替換Present的方法
HRESULT WINAPI HookFuncSwapChainPresent(IDXGISwapChain* pThis, UINT SyncInterval, UINT Flags)
{
    //輸出一句話由桌,并調(diào)用原來的Present方法
    std::cout << "Hook Present" << std::endl;
    return oPresent(pThis, SyncInterval, Flags);
}

bool HookPresent()
{
    void** p_SwapChain_VMT = g_pSwapchainVMT;
    oPresent = (vfn_SwapChain_Present)(p_SwapChain_VMT[(UINT)IDXGISwapChainVMT::Present]);

    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    //主要是這一句,將原來的Present替換成我們的Present
    DetourAttach((PVOID*)&oPresent, HookFuncSwapChainPresent);

    DetourTransactionCommit();

    return true;
}

??這樣就能做不少事,例如我們可以Hook IDXGISwapChain::PresentID3D11DeviceContext::DrawIndexed \ DrawIndexedInstanced行您,從而統(tǒng)計(jì)每渲染一幀的DrawCall數(shù)量铭乾。
??我們也可以試著像RenderDoc那樣,將每一個(gè)DrawCall后的RT保存下來娃循。
??我們可以Hook ID3D11CreateDeviceAndSwapChain直接獲取Device和SwapChain并全局保存炕檩,但這個(gè)方法一會(huì)再提,我們先偷個(gè)懶捌斧,在Present第一次運(yùn)行的時(shí)候笛质,通過傳遞過來的SwapChain的GetDevice方法獲取Device,然后通過Device的GetImmediateContext方法獲取Context:

//全局變量
IDXGISwapChain* g_pSwapchain = nullptr;
ID3D11Device* g_pDevice = nullptr;
ID3D11DeviceContext* g_pContext = nullptr;

bool IsInit()
{
    return (g_pDevice != nullptr) && (g_pSwapchain != nullptr) && (g_pContext != nullptr);
}

void InitD3D(IDXGISwapChain* pSwapChain)
{
    if (!IsInit())
    {
        g_pSwapchain = pSwapChain;
        pSwapChain->GetDevice(__uuidof(ID3D11Device), (void**)&g_pDevice);
        g_pDevice->GetImmediateContext(&g_pContext);
    }
}

//上面聲明的 HookFuncSwapChainPresent 方法內(nèi)加上
if (!IsInit())
{
    InitD3D(pThis);
}

??然后寫一些截屏的邏輯

//全局變量
bool doCapture = false;
bool Capturing = false;
int gCaptureNum = 0;

void TriggerCapture()
{
    doCapture = true;
}

//上面聲明的 HookFuncSwapChainPresent 中加入:
if (Capturing)//如果上一幀在截屏骤星,關(guān)閉截屏
    Capturing = false;

if (doCapture)//如果要截屏
{
    doCapture = false;
    Capturing = true;
    gCaptureNum = 0;
    std::cout << "截幀" << std::endl;
}

??要保存RT经瓷,就要獲取當(dāng)前RT,我們可以通過Hook Context的OMSetRenderTargets方法洞难,來維護(hù)一個(gè)全局當(dāng)前的RT變量舆吮。雖然下面的方法看起來一團(tuán)亂麻,但和上面一開始Hook Present的套路完全一樣

//RT數(shù)怎么也大不過8吧队贱?如果大過也沒事色冀,反正不會(huì)頻繁分配空間
std::vector<ID3D11RenderTargetView*> g_ppRenderTargetView(8);

using vfn_DeviceContext_OMSetRenderTargets = void(WINAPI*)(ID3D11DeviceContext*,
    __in_range(0, D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT)  UINT NumViews,
    __in_ecount_opt(NumViews)  ID3D11RenderTargetView* const* ppRenderTargetViews,
    __in_opt  ID3D11DepthStencilView* pDepthStencilView);
vfn_DeviceContext_OMSetRenderTargets oDeviceContext_OMSetRenderTargets = nullptr;

void WINAPI HookFuncDeviceContext_OMSetRenderTargets(ID3D11DeviceContext* pThis, 
    __in_range(0, D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT)  UINT NumViews,
    __in_ecount_opt(NumViews)  ID3D11RenderTargetView* const* ppRenderTargetViews,
    __in_opt  ID3D11DepthStencilView* pDepthStencilView)
{
    g_ppRenderTargetView.clear();

    for (int i = 0; i < NumViews; ++i)
    {
        ID3D11RenderTargetView* pRenderTargetView = *(ppRenderTargetViews + i);
        if (pRenderTargetView == nullptr)
            continue;

        g_ppRenderTargetView.push_back(pRenderTargetView);

    }
    return oDeviceContext_OMSetRenderTargets(pThis, NumViews, ppRenderTargetViews, pDepthStencilView);
}

bool HookDeviceContext_OMSetRenderTargets()
{
    void** p_DeviceContext_VMT = g_pDeviceContextVMT;
    oDeviceContext_OMSetRenderTargets = (vfn_DeviceContext_OMSetRenderTargets)p_DeviceContext_VMT[(UINT)ID3D11DeviceContextVMT::OMSetRenderTargets];
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());

    DetourAttach((PVOID*)&oDeviceContext_OMSetRenderTargets, HookFuncDeviceContext_OMSetRenderTargets);

    DetourTransactionCommit();

    return true;
}

??有了RT就能得到資源(Resource),就有辦法保存:

void CaptureFrame()
{
    if (g_ppRenderTargetView.size() == 0)
        return;

    ++gCaptureNum;

    for (int i = 0; i < g_ppRenderTargetView.size(); ++i)
    {
        std::wstringstream wss;
        wss << L"我的保存路徑\\Image_"
            << gCaptureNum << "_RT" << i << ".dds";
        
        ID3D11RenderTargetView* view = g_ppRenderTargetView[i];
        if (view == nullptr)
            continue;

        ID3D11Resource* pSourceResource;
        view->GetResource(&pSourceResource);
        D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
        view->GetDesc(&rtvDesc);

        std::cout << "Resource DXGIFormat: " << magic_enum::enum_name<DXGI_FORMAT>(rtvDesc.Format) << std::endl;

        HRESULT hr = D3DX11SaveTextureToFile(g_pContext, pSourceResource, D3DX11_IFF_DDS, wss.str().c_str());
        if (SUCCEEDED(hr))
            std::wcout << "Save To " << wss.str() << std::endl;
        else
            std::cout << "截圖錯(cuò)誤:" << hr << std::endl;

        pSourceResource->Release();
    }
}

??我試過在某個(gè)新游戲(對(duì)現(xiàn)在來說)用BMP格式保存柱嫌,可惜只有渲染UI時(shí)能正常保存锋恬,PNG也是,不過DDS格式竟然能正常保存编丘。
??然后是體力活与学,要Hook Context的DrawIndexed和DrawIndexed,如果有必要嘉抓,還有Draw和DrawInstanced索守,這些方法中先調(diào)用DrawCall,然后調(diào)用CaptureFrame抑片,這里我放下Hook DrawIndexed的事例:

using vfn_DeviceContext_DrawIndexed = void(STDMETHODCALLTYPE*)(ID3D11DeviceContext*, UINT, UINT, UINT);
vfn_DeviceContext_DrawIndexed oDrawIndexed = nullptr;

void STDMETHODCALLTYPE HookFuncDeviceContextDrawIndexed(ID3D11DeviceContext* Context,
    UINT IndexCount,
    UINT StartIndexLocation,
    UINT BaseVertexLocation)
{
    oDrawIndexed(Context, IndexCount, StartIndexLocation, BaseVertexLocation);

    if (Capturing)
        CaptureFrame();
}

bool HookDrawIndexed()
{
    void** p_DeviceContext_VMT = g_pDeviceContextVMT;
    oDrawIndexed = (vfn_DeviceContext_DrawIndexed)(p_DeviceContext_VMT[(UINT)ID3D11DeviceContextVMT::DrawIndexed]);
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach((PVOID*)&oDrawIndexed, HookFuncDeviceContextDrawIndexed);

    DetourTransactionCommit();
    return true;
}

全局空間函數(shù) Hook

??和上面基本同樣的套路卵佛,我Hook CreateWindowExW:

using fn_CreateWindowExW = HWND(WINAPI*)(
    DWORD, LPCWSTR, LPCWSTR, DWORD, int, int,
    int, int, HWND, HMENU, HINSTANCE, LPVOID
    );
fn_CreateWindowExW oCreateWindowExW = CreateWindowExW;

HWND WINAPI HookFuncCreateWindowExW(DWORD dwExStyle, LPCWSTR lpClassName,
    LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight,
    HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)
{
    std::wcout << L"HookCreateWindowExW! WindowName: " << lpWindowName << std::endl;
    std::cout << "X: " << X << ", Y:" << Y << ", width: " << nWidth << ", height: " << nHeight << std::endl;

    auto res = oCreateWindowExW(
        dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight,
        hWndParent, hMenu, hInstance, lpParam);
    return res;
}

bool HookCreateWindowExW()
{
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());

    DetourAttach((PVOID*)&oCreateWindowExW, HookFuncCreateWindowExW);

    DetourTransactionCommit();
    return true;
}

??嗯,這是能運(yùn)作的敞斋,可惜我Hook ID3D11CreateDeviceID3D11CreateDeviceAndSwapChain時(shí)獲取不到截汪,或許是做了Hook保護(hù)?還是因?yàn)?strong>CreateWindowExW是kernel32.dll植捎,地址空間所有進(jìn)程一樣衙解?
??我不清楚,但用了另一種方法成功了:

oD3D11CreateDevice = 
(fn_D3D11CreateDevice)GetProcAddress(GetModuleHandle(_T("d3d11.dll")), "D3D11CreateDevice");

??我可以用這個(gè)開啟Debug Layer:

HRESULT WINAPI HookFuncD3D11CreateDevice(
    _In_opt_ IDXGIAdapter* pAdapter,
    D3D_DRIVER_TYPE DriverType,
    HMODULE Software,
    UINT Flags,
    _In_reads_opt_(FeatureLevels) CONST D3D_FEATURE_LEVEL* pFeatureLevels,
    UINT FeatureLevels,
    UINT SDKVersion,
    _COM_Outptr_opt_ ID3D11Device** ppDevice,
    _Out_opt_ D3D_FEATURE_LEVEL* pFeatureLevel,
    _COM_Outptr_opt_ ID3D11DeviceContext** ppImmediateContext
)
{

    std::cout << "Hook D3D11CreateDevice!" << "Flag: " << Flags << std::endl;
    if (Flags == 1)
        Flags |= D3D11_CREATE_DEVICE_DEBUG;
    HRESULT hr =  oD3D11CreateDevice(pAdapter, DriverType, Software, Flags,
        pFeatureLevels, FeatureLevels, SDKVersion, ppDevice,
        pFeatureLevel, ppImmediateContext
    );

    return hr;
}

總結(jié)

??通過這樣的方法焰枢,我hook到游戲中并截幀丢郊,不過這個(gè)方法有很多缺陷盔沫,例如那個(gè)游戲在渲染數(shù)多時(shí),有1500左右DrawCall枫匾,因?yàn)橛昧搜舆t管線架诞,不少DrawCall都是MRT,最終保存的RT數(shù)要乘上3干茉、4倍的DrawCall數(shù)谴忧,14G左右,盡管是三星SSD硬盤角虫,也運(yùn)行了3-5分鐘沾谓,保存下來的DDS圖片也未必是都能看的,VisualStudio和RenderDoc各能讀取一些戳鹅。
??注入器的編寫也碰到過一些問題均驶,通過把注入器改成系統(tǒng)文件名解決了……
??如果有辦法,我還是想通過注入RenderDoc的方式分析并截幀枫虏,可惜現(xiàn)在注入后妇穴,能在dll列表中看到renderdoc.dll,但并沒有起作用隶债,或許我要去閱讀一下RenderDoc的源碼腾它。
??相關(guān)注入器代碼: crossous/RemoteInject

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末死讹,一起剝皮案震驚了整個(gè)濱河市瞒滴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赞警,老刑警劉巖妓忍,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異愧旦,居然都是意外死亡单默,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門忘瓦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人引颈,你說我怎么就攤上這事耕皮。” “怎么了蝙场?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵凌停,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我售滤,道長(zhǎng)罚拟,這世上最難降的妖魔是什么台诗? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮赐俗,結(jié)果婚禮上拉队,老公的妹妹穿的比我還像新娘。我一直安慰自己阻逮,他們只是感情好粱快,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叔扼,像睡著了一般事哭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓜富,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天鳍咱,我揣著相機(jī)與錄音,去河邊找鬼与柑。 笑死谤辜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仅胞。 我是一名探鬼主播每辟,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼干旧!你這毒婦竟也來了渠欺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤椎眯,失蹤者是張志新(化名)和其女友劉穎挠将,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體编整,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舔稀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掌测。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片内贮。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖汞斧,靈堂內(nèi)的尸體忽然破棺而出夜郁,到底是詐尸還是另有隱情,我是刑警寧澤粘勒,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布竞端,位于F島的核電站,受9級(jí)特大地震影響庙睡,放射性物質(zhì)發(fā)生泄漏事富。R本人自食惡果不足惜技俐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望统台。 院中可真熱鬧雕擂,春花似錦、人聲如沸饺谬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽募寨。三九已至族展,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拔鹰,已是汗流浹背仪缸。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留列肢,地道東北人恰画。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瓷马,于是被迫代替她去往敵國(guó)和親拴还。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345