学习是对技术的祛魅
公众号:
或许我们的公众号会有更多你感兴趣的内容
Direct3D11 注入
在此之前,公众号已经简述了MinHook
的原理,那么利用这种原理我们就可以通过hook在d3d编写的游戏中实现窗口
相关代码:https://github.com/Joe1sn/dx11-hook-example
Direct3D简述
这里使用ImGui
的默认dx11版本示例来讲解,首先d3d的绘制依靠的是windows的窗口(window),接着是D3D设备、上下文和交换链以及各种绘制方法的d3dAPI传入操作系统和GPU,最后传输到显示器显示,大致如下
那么对于d3d API来说是这样的:
如何显示
那么最后如何得到渲染好的最终帧呢?答案是交换链的 IDXGISwapChain::Present
方法用于呈现最终渲染的帧
https://learn.microsoft.com/zh-cn/windows/win32/api/dxgi/nn-dxgi-idxgiswapchain
具体参数
https://learn.microsoft.com/zh-cn/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-present
那么我们可以hook这个函数,然后提交我们要现实的内容,最后再一同显示出来
编写DLL
这里我就是用visual studio 2022 默认的DLL项目
导入imgui和minhook
本篇文章将处理的是dx11版本的d3d,那么如何判断游戏是否使用了该dll呢?一般来说是凭借经验,不过也可以使用CE遍历一下DLL即可
如果发现调用了多种图形API,例如OpenGL,Vulkan等,可查看使用调用对应的呈现最终帧的渲染函数,也可以参考kiero的一些做法,链接:https://github.com/Rebzzel/kiero
那么如何找到该函数呢?别忘了最后的dll是注入到程序中的,那么我们的dll也可以使用d3d11.dll
,而且前文提到过IDXGISwapChain::Present
是IDXGISwapChain
下的方法,那么我们如果也要使用该方法,也是会跳转到同一个位置(因为注入后就是同一个程序了,那么对应的dll的内存也是同一块)
而且Present
是虚函数,位置在vtable[8]
,
那么可以得到定位函数,这里抄了下imgui例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 typedef long (__stdcall* DIGX_Present) (IDXGISwapChain*, UINT, UINT) ;DIGX_Present originPresent; DIGX_Present old_present; bool getPresentPtr () { DXGI_SWAP_CHAIN_DESC sd; ZeroMemory (&sd, sizeof (sd)); sd.BufferCount = 2 ; sd.BufferDesc.Width = 0 ; sd.BufferDesc.Height = 0 ; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.OutputWindow = GetForegroundWindow (); sd.SampleDesc.Count = 1 ; sd.Windowed = TRUE; sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; UINT createDeviceFlags = 0 ; IDXGISwapChain* swap_chain; ID3D11Device* device; D3D_FEATURE_LEVEL featureLevel; const D3D_FEATURE_LEVEL featureLevelArray[2 ] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, }; if (D3D11CreateDeviceAndSwapChain ( nullptr , D3D_DRIVER_TYPE_HARDWARE, nullptr , createDeviceFlags, featureLevelArray, 2 , D3D11_SDK_VERSION, &sd, &swap_chain, &device, &featureLevel, nullptr ) == S_OK) { void ** p_vtable = *reinterpret_cast <void ***>(swap_chain); swap_chain->Release (); device->Release (); old_present = (DIGX_Present)p_vtable[8 ]; return true ; } return false ; }
确定下主线程的主要结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 int WINAPI main (HMODULE hModule) { if (!getPresentPtr ()) return 1 ; if (MH_Initialize () != MH_OK) return 1 ; if (MH_CreateHook (reinterpret_cast <void **>(old_present), &myPresent, reinterpret_cast <void **>(&originPresent)) != MH_OK) return 1 ; if (MH_EnableHook (old_present) != MH_OK) return 1 ; while (true ) { Sleep (50 ); if (GetAsyncKeyState (VK_F1)) { break ; } } if (MH_DisableHook (MH_ALL_HOOKS) != MH_OK) return 1 ; if (MH_Uninitialize () != MH_OK) return 1 ; FreeLibraryAndExitThread (hModule, 0 ); return 0 ; } BOOL APIENTRY DllMain ( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { CreateThread (NULL , 0 , (LPTHREAD_START_ROUTINE)main, hModule, 0 , NULL ); } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break ; } return TRUE; }
完成myPresent
来替换旧的Present
,这里依旧是抄了写imgui的示例代码,主要还是参考示例代码中的初始化和绘制的步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 WNDPROC oWndProc; extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) ;LRESULT WINAPI WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (ImGui_ImplWin32_WndProcHandler (hWnd, msg, wParam, lParam)) return true ; return ::DefWindowProcW (hWnd, msg, wParam, lParam); } bool init = false ;HWND window = NULL ; static ID3D11Device* g_pd3dDevice = nullptr ;static ID3D11DeviceContext* g_pd3dDeviceContext = nullptr ;static ID3D11RenderTargetView* g_mainRenderTargetView = nullptr ;static long __stdcall myPresent (IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags) { if (!init) { if (SUCCEEDED (p_swap_chain->GetDevice (__uuidof(ID3D11Device), (void **)&g_pd3dDevice))) { g_pd3dDevice->GetImmediateContext (&g_pd3dDeviceContext); DXGI_SWAP_CHAIN_DESC sd; p_swap_chain->GetDesc (&sd); window = sd.OutputWindow; ID3D11Texture2D* pBackBuffer; p_swap_chain->GetBuffer (0 , __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer); g_pd3dDevice->CreateRenderTargetView (pBackBuffer, NULL , &g_mainRenderTargetView); pBackBuffer->Release (); oWndProc = (WNDPROC)SetWindowLongPtr (window, GWLP_WNDPROC, (LONG_PTR)WndProc); ImGui::CreateContext (); ImGuiIO& io = ImGui::GetIO (); (void )io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; ImGui::StyleColorsDark (); ImGuiStyle& style = ImGui::GetStyle (); if (io.ConfigFlags) { style.WindowRounding = 0.0f ; style.Colors[ImGuiCol_WindowBg].w = 1.0f ; } ImGui_ImplWin32_Init (window); ImGui_ImplDX11_Init (g_pd3dDevice, g_pd3dDeviceContext); init = true ; } else return originPresent (p_swap_chain, sync_interval, flags); } ImGui_ImplDX11_NewFrame (); ImGui_ImplWin32_NewFrame (); ImGui::NewFrame (); ImGui::ShowDemoWindow (); ImGui::EndFrame (); ImGui::Render (); g_pd3dDeviceContext->OMSetRenderTargets (1 , &g_mainRenderTargetView, nullptr ); ImGui_ImplDX11_RenderDrawData (ImGui::GetDrawData ()); return originPresent (p_swap_chain, sync_interval, flags); }