从注入的 DLL 挂钩 DirectX EndScene

Posted

技术标签:

【中文标题】从注入的 DLL 挂钩 DirectX EndScene【英文标题】:Hooking DirectX EndScene from an injected DLL 【发布时间】:2010-12-31 23:14:15 【问题描述】:

我想从任意 DirectX 9 应用程序中绕过 EndScene 以创建一个小的覆盖。例如,您可以使用 FRAPS 的帧计数器覆盖,激活时会在游戏中显示。

我知道以下方法可以做到这一点:

    创建一个新的d3d9.dll,然后将其复制到游戏路径。由于首先搜索当前文件夹,在进入 system32 等之前,我修改的 DLL 被加载,执行我的附加代码。缺点:你必须在开始游戏之前把它放在那里。

    与第一种方法相同,但直接替换 system32 中的 DLL。缺点:您无法添加游戏特定代码。您不能排除不希望加载 DLL 的应用程序。

    使用 IDA Pro 4.9 Free 等工具直接从 DLL 获取 EndScene 偏移。由于 DLL 是按原样加载的,因此您只需将此偏移量添加到 DLL 起始地址,当它映射到游戏时,获取实际偏移量,然后将其挂钩。缺点: 每个系统上的偏移量都不相同。

    挂钩Direct3DCreate9获取D3D9,然后挂钩D3D9->CreateDevice获取设备指针,然后通过虚拟表挂钩Device->EndScene缺点:当进程已在运行时,无法注入 DLL。您必须使用CREATE_SUSPENDED 标志启动该过程以挂钩初始Direct3DCreate9

    在注入 DLL 后立即在新窗口中创建新设备。然后,从该设备获取EndScene 偏移量并将其挂钩,从而为游戏使用的设备生成挂钩。缺点:根据我阅读的一些信息,创建第二台设备可能会干扰现有设备,并且可能会出现窗口模式和全屏模式等问题。

    同第三种方法。但是,您将进行模式扫描以获取 EndScene缺点: 看起来并不那么可靠。

如何从注入的 DLL 中挂钩 EndScene,该 DLL 可能在游戏已经运行时加载,而无需处理其他系统上的不同 d3d9.dll,并且一种可靠的方法?例如,FRAPS 如何执行它的 DirectX 挂钩? DLL 不应该适用于所有游戏,只适用于我通过CreateRemoteThread 注入它的特定进程。

【问题讨论】:

列表编号是否有原因? 【参考方案1】:

您安装了一个系统范围的挂钩。 (SetWindowsHookEx) 完成此操作后,您将被加载到每个进程中。

现在,当调用钩子时,您会查找已加载的 d3d9.dll。

如果已加载,则创建一个临时 D3D9 对象,并遍历 vtable 以获取 EndScene 方法的地址。

然后您可以使用自己的方法修补 EndScene 调用。 (通过调用您的方法替换 EndScene 中的第一条指令。

完成后,您必须修补回调,以调用原始的 EndScene 方法。然后重新安装你的补丁。

这就是 FRAPS 的做法。 (Link)


你可以从接口的vtable中找到函数地址。

因此您可以执行以下操作(伪代码):

IDirect3DDevice9* pTempDev = ...;
const int EndSceneIndex = 26 (?);

typedef HRESULT (IDirect3DDevice9::* EndSceneFunc)( void );

BYTE* pVtable = reinterpret_cast<void*>( pTempDev );
EndSceneFunc = pVtable + sizeof(void*) * EndSceneIndex;

EndSceneFunc 现在确实包含指向函数本身的指针。我们现在可以修补所有调用站点,也可以修补函数本身。

请注意,这一切都取决于在 Windows 中实现 COM 接口的知识。但这适用于所有 Windows 版本(32 或 64,不能同时使用)。

【讨论】:

查看更多详细信息:Windows Via C/C++,第 22 章 - DLL 注入和 API 挂钩,使用 Windows 挂钩注入 DLL 我想通过CreateRemoteThread注入DLL。我不想要全局挂钩,只针对特定游戏。 那就放下第一步吧。 这基本上就是我在问题#5 中的内容。我读过它似乎时不时地崩溃,具体取决于窗口/全屏设置。你觉得#6怎么样?在加载 d3d9.dll 的内存中对EndScene 进行模式扫描?或者该功能在多个系统上看起来是否有很大不同? 虚函数表中的EndScene索引是42,afaik。【参考方案2】:

我知道这个问题很老,但这应该适用于任何使用 DirectX9 的程序,您基本上是在创建自己的实例,然后获取指向 VTable 的指针,然后您只需将其挂钩。顺便说一句,您将需要绕路 3.X:

//Just some typedefs:
typedef HRESULT (WINAPI* oEndScene) (LPDIRECT3DDEVICE9 D3DDevice);
static oEndScene EndScene;

//Do this in a function or whatever
HMODULE hDLL=GetModuleHandleA("d3d9");
LPDIRECT3D9(__stdcall*pDirect3DCreate9)(UINT) = (LPDIRECT3D9(__stdcall*)(UINT))GetProcAddress( hDLL, "Direct3DCreate9");

LPDIRECT3D9 pD3D = pDirect3DCreate9(D3D_SDK_VERSION);

D3DDISPLAYMODE d3ddm;
HRESULT hRes = pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm );
D3DPRESENT_PARAMETERS d3dpp; 
ZeroMemory( &d3dpp, sizeof(d3dpp));
d3dpp.Windowed = true;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = d3ddm.Format;

WNDCLASSEX wc =  sizeof(WNDCLASSEX),CS_CLASSDC,TempWndProc,0L,0L,GetModuleHandle(NULL),NULL,NULL,NULL,NULL,("1"),NULL;
RegisterClassEx(&wc);
HWND hWnd = CreateWindow(("1"),NULL,WS_OVERLAPPEDWINDOW,100,100,300,300,GetDesktopWindow(),NULL,wc.hInstance,NULL);

hRes = pD3D->CreateDevice( 
    D3DADAPTER_DEFAULT,
    D3DDEVTYPE_HAL,
    hWnd,
    D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_DISABLE_DRIVER_MANAGEMENT,
    &d3dpp, &ppReturnedDeviceInterface);

pD3D->Release();
DestroyWindow(hWnd);

if(pD3D == NULL)
    //printf ("WARNING: D3D FAILED");
    return false;

pInterface = (unsigned long*)*((unsigned long*)ppReturnedDeviceInterface);


EndScene = (oEndScene) (DWORD) pInterface[42];
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)EndScene, newEndScene);
DetourTransactionCommit();

然后是你的函数:

HRESULT WINAPI D3D9Hook::newEndScene(LPDIRECT3DDEVICE9 pDevice)
   
    //Do your stuff here

    //Call the original (if you want)
    return EndScene(pDevice);

【讨论】:

为什么我在返回原始 EndScene 时得到“崩溃应用”? @Duracell 这真的取决于你如何使用它。你是从主线程挂钩的吗?也许您指向原始 EndScene 的指针为空。我需要更多信息才能为您提供帮助。 我在应用程序中注入此代码pastebin.com/ui0ezFyW(使用 d3d9),并且我在控制台“printf_s(“Hooked!”)中收到消息,来自函数“HRESULT WINAPI newEndScene(LPDIRECT3DDEVICE9 pDevice) ",但是当我调用 "return EndScene(pDevice);" 时- 获取崩溃应用,请检查我的错误。 在不创建线程的情况下尝试挂钩。此外,您的 while 循环不会在那里做任何事情,因为一旦它被钩住,您就可以结束其余的。【参考方案3】:

我知道一个有点老的问题 - 但如果有人有兴趣使用 C# 来做这件事,这是我在 hooking the Direct3D 9 API using C# 上的示例。这利用了 EasyHook 一个开源 .NET 程序集,它允许您“安全地”将挂钩从托管代码安装到非托管函数中。 (注意:EasyHook 会处理与 DLL 注入相关的所有问题 - 例如 CREATE_SUSPENDED、ACL、32 位与 64 位等)

我使用 Christopher 提到的类似 VTable 方法,通过一个小的 C++ 帮助程序 dll 来动态确定要挂钩的 IDirect3DDevice9 函数的地址。这是通过创建一个临时窗口句柄并在注入的程序集中创建一个丢弃的 IDirect3Device9 来完成的,然后再挂钩所需的函数。这允许您的应用程序挂钩已在运行的目标(更新:请注意,这也完全可以在 C# 中实现 - 请参阅链接页面上的 cmets)。

更新:还有一个更新版本hooking Direct3D 9, 10 and 11 仍然使用 EasyHook 和 SharpDX 而不是 SlimDX

【讨论】:

以上是关于从注入的 DLL 挂钩 DirectX EndScene的主要内容,如果未能解决你的问题,请参考以下文章

DirectX 游戏挂钩

在调用导入之前将挂钩 DLL 注入进程?

Windows挂钩注入DLL

如何将 DLL 注入 Adob​​e Reader X

防止来自Dll C ++的Dll注入

根据代码顺序,挂钩 sendto() 会导致崩溃