C++ 从注入的 DLL 中挂接 DirectX EndScene

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1994676/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 21:47:23  来源:igfitidea点击:

Hooking DirectX EndScene from an injected DLL

c++directxhookdll-injection

提问by Etan

I want to detour EndScenefrom an arbitrary DirectX 9 application to create a small overlay. As an example, you could take the frame counter overlay of FRAPS, which is shown in games when activated.

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

I know the following methods to do this:

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

  1. Creating a new d3d9.dll, which is then copied to the games path. Since the current folder is searched first, before going to system32 etc., my modified DLL gets loaded, executing my additional code.
    Downside:You have to put it there before you start the game.

  2. Same as the first method, but replacing the DLL in system32 directly.
    Downside:You cannot add game specific code. You cannot exclude applications where you don't want your DLL to be loaded.

  3. Getting the EndScene offset directly from the DLL using tools like IDA Pro 4.9 Free. Since the DLL gets loaded as is, you can just add this offset to the DLL starting address, when it is mapped to the game, to get the actual offset, and then hook it.
    Downside:The offset is not the same on every system.

  4. Hooking Direct3DCreate9to get the D3D9, then hooking D3D9->CreateDeviceto get the device pointer, and then hooking Device->EndScenethrough the virtual table.
    Downside:The DLL cannot be injected, when the process is already running. You have to start the process with the CREATE_SUSPENDEDflag to hook the initial Direct3DCreate9.

  5. Creating a new Device in a new window, as soon as the DLL gets injected. Then, getting the EndSceneoffset from this device and hooking it, resulting in a hook for the device which is used by the game.
    Downside:as of some information I have read, creating a second device may interfere with the existing device, and it may bug with windowed vs. fullscreen mode etc.

  6. Same as the third method. However, you'll do a pattern scan to get EndScene.
    Downside:doesn't look that reliable.

  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.
    缺点:看起来不那么可靠。

How can I hook EndScenefrom an injected DLL, which may be loaded when the game is already running, without having to deal with different d3d9.dll's on other systems, and with a method which is reliable? How does FRAPS for example perform it's DirectX hooks? The DLL should not apply to all games, just to specific processes where I inject it via CreateRemoteThread.

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

采纳答案by Christopher

You install a system wide hook. (SetWindowsHookEx) With this done, you get to be loaded into every process.

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

Now when the hook is called, you look for a loaded d3d9.dll.

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

If one is loaded, you create a temporary D3D9 object, and walk the vtable to get the address of the EndScene method.

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

Then you can patch the EndScene call, with your own method. (Replace the first instruction in EndScene by a call to your method.

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

When you are done, you have to patch the call back, to call the original EndScene method. And then reinstall your patch.

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

This is the way FRAPS does it. (Link)

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



You can find a function address from the vtable of an interface.

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

So you can do the following (Pseudo-Code):

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

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

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

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

EndSceneFunc does now contain a pointer to the function itself. We can now either patch all call-sites or we can patch the function itself.

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

Beware that this all depends on the knowledge of the implementation of COM-Interfaces in Windows. But this works on all windows versions (either 32 or 64, not both at the same time).

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

回答by Justin Stenning

A slightly old question I know - but in case anyone is interested in doing this with C#, here is my example on hooking the Direct3D 9 API using C#. This utilizes EasyHook an open source .NET assembly that allows you to 'safely' install hooks from managed code into unmanaged functions. (Note: EasyHook takes care of all the issues surrounding DLL injection - e.g. CREATE_SUSPENDED, ACL's, 32 vs 64-bit and so on)

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

I use a similar VTable approach as mentioned by Christopher via a small C++ helper dll to dynamically determine the address of the IDirect3DDevice9 functions to hook. This is done by creating a temporary window handle, and creating a throw-away IDirect3Device9 within the injected assembly before then hooking the desired functions. This allows your application to hook a target that is already running (Update: note that this is possible entirely within C# also - see comments on the linked page).

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

Update: there is also an updated version for hooking Direct3D 9, 10 and 11still using EasyHook and with SharpDX instead of SlimDX

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

回答by Fredaikis

I know this question is old, but this should work for any program using DirectX9, You are creating your own instance basically, and then getting the pointer to the VTable, then you just hook it. You will need detours 3.X btw:

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

And then your function:

然后你的功能:

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

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