Accrocher DirectX EndScene à partir d'une DLL injectée

je veux faire un détour EndScene à partir d'une application DirectX 9 arbitraire pour créer une petite superposition. À titre d'exemple, vous pouvez prendre la superposition de frame counter de FRAPS, qui est montré dans les jeux lorsqu'il est activé.

je connais les méthodes suivantes pour ce faire:

  1. Création d'un nouveau d3d9.dll, qui est ensuite copié le jeux chemin. Puisque le dossier courant est recherché en premier, avant de passer à system32 etc., ma DLL modifiée est chargée, l'exécution mon code additionnel.

    inconvénients: Vous devez le mettre là avant de commencer le jeu.

    • identique à la première méthode, mais remplaçant la DLL dans system32 directement.

    inconvénients: vous ne pouvez pas ajouter de code spécifique au jeu. Vous ne pouvez pas exclure les applications où vous ne voulez pas que votre DLL soit chargée.

    • obtenir le décalage EndScene directement à partir de la DLL en utilisant des outils comme IDA Pro 4.9 Free. Depuis la DLL est chargée telle quelle, vous pouvez simplement ajouter cet offset à l'adresse de départ DLL, quand il est mappé sur le jeu, pour obtenir l'offset réel, puis l'accrocher.

    inconvénients: Le décalage n'est pas le même sur chaque système.

    • Accrochage Direct3DCreate9 pour obtenir le D3D9, puis l'accrochage D3D9->CreateDevice pour obtenir le dispositif de pointeur, puis l'accrochage Appareil->EndScene à travers le virtuel table.

    inconvénients: la DLL ne peut pas être injectée, alors que le processus est déjà en cours. Vous devez commencer le processus avec l' CREATE_SUSPENDED drapeau pour accrocher l'initiale Direct3DCreate9.

    • créer un nouveau périphérique dans une nouvelle fenêtre, dès que la DLL est injectée. Ensuite, l'obtention de l' EndScene offset de ce dispositif et l'accrochage, résultant en un crochet pour le dispositif qui est utilisé par le jeu.

    inconvénients: d'après certaines informations que j'ai lues, la création d'un second périphérique peut interférer avec le périphérique existant, et il peut glisser avec le mode rétroviseur ou plein écran, etc.

    • identique à la troisième méthode. Cependant, vous allez faire un scan de motif pour obtenir EndScene.

    inconvénients: ne semble pas si fiable.

Comment puis-je brancher EndScene d'une DLL injectée, qui peut être chargé lorsque le jeu est déjà en cours d'exécution, sans avoir à traiter avec différents d3d9.dllsur d'autres systèmes, et avec une méthode fiable? Comment les FRAPS par exemple effectuent-ils ses crochets DirectX? La DLL ne devrait pas s'appliquer à tous les jeux, juste aux processus spécifiques où je l'injecte via CreateRemoteThread.

29
demandé sur AstroCB 2010-01-03 14:23:14

3 réponses

vous installez un crochet à l'échelle du système. (SetWindowsHookEx) avec ceci fait, vous obtenez d'être chargé dans chaque processus.

lorsque le crochet est appelé, vous cherchez un D3D9 chargé.DLL.

si on est chargé, on crée un objet temporaire D3D9, et on marche sur le vtable pour obtenir l'adresse de la méthode EndScene.

alors vous pouvez patcher L'appel EndScene, avec votre propre méthode. (Remplacer la première instruction EndScene par un appel à votre méthode.

Quand vous avez terminé, vous devez patcher le rappel, pour appeler la méthode originale EndScene. Puis réinstallez votre patch.

C'est la façon dont FRAPS le fait. (Lien)


Vous pouvez trouver une adresse de fonction à partir du vtable d'une interface.

Vous pouvez donc faire ce qui suit (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 contient maintenant un pointeur vers la fonction elle-même. Nous pouvons maintenant soit Patcher tous les sites d'appel ou nous pouvons patcher le fonction elle-même.

attention que tout cela dépend de la connaissance de l'implémentation des interfaces COM dans Windows. Mais cela fonctionne sur toutes les versions de windows (32 ou 64, pas les deux en même temps).

15
répondu Christopher 2010-01-03 19:01:48

une question un peu vieille que je connais - mais au cas où quelqu'un serait intéressé à le faire avec C#, voici mon exemple sur accrochage Direct3D 9 de l'API à l'aide de C#. Cela utilise EasyHook un assemblage open source .NET qui vous permet d'installer en toute sécurité des crochets à partir du code géré dans des fonctions non gérées. (Note: EasyHook s'occupe de tous les problèmes entourant L'injection DLL - par exemple CREATE_SUSPENDED, ACL's, 32 vs 64-bit et ainsi de suite)

j'utilise un semblable VTable approche mentionné par Christopher via un petit c++ helper dll pour déterminer dynamiquement l'adresse des fonctions IDirect3DDevice9 à accrocher. Ceci est fait en créant une poignée de fenêtre temporaire, et en créant un IDirect3Device9 jeter-away dans l'ensemble injecté avant d'accrocher les fonctions désirées. Cela permet à votre application d'accrocher une cible qui est déjà en cours d'exécution (Maj: notez que cela est possible entièrement à L'intérieur de C# also - voir les commentaires sur le lien page.)

mise à Jour: il y a aussi une version mise à jour pour accrochage Direct3D 9, 10 et 11 toujours utiliser EasyHook et avec SharpDX au lieu de SlimDX

4
répondu Justin Stenning 2017-05-03 05:49:11

je sais que cette question Est ancienne, mais cela devrait fonctionner pour n'importe quel programme utilisant DirectX9, vous créez votre propre instance fondamentalement, et puis obtenir le pointeur vers le VTable, puis vous l'accrochez juste. Vous aurez besoin de détours 3.X btw:

//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();

Et puis votre fonction:

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

    //Call the original (if you want)
    return EndScene(pDevice);
}
2
répondu Fredaikis 2015-06-05 10:38:40