繁体   English   中英

从注入的 DLL 中挂接 DirectX EndScene

[英]Hooking DirectX EndScene from an injected DLL

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

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

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

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

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

  4. 钩子Direct3DCreate9得到D3D9,钩子D3D9->CreateDevice得到设备指针,再钩子Device->EndScene通过虚表。
    缺点:当进程已经在运行时,无法注入 DLL。 您必须使用CREATE_SUSPENDED标志启动进程以挂钩初始Direct3DCreate9

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

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

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

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

现在当钩子被调用时,你会寻找一个加载的 d3d9.dll。

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

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

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

这就是 FRAPS 的做法。 (链接)


您可以从接口的 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,不能同时使用)。

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

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

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

我知道这个问题很老,但这应该适用于任何使用 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);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM