web-dev-qa-db-fra.com

Accrochage de DirectX EndScene à partir d'un injecteur DLL

Je souhaite détourner EndScene d'une application arbitraire DirectX 9 pour créer une petite superposition. À titre d'exemple, vous pouvez prendre la superposition du compteur d'images de FRAPS, qui est affichée dans les jeux lorsqu'elle est activée.

Je connais les méthodes suivantes pour ce faire:

  1. Création d'un nouveau d3d9.dll , qui est ensuite copié dans le chemin des jeux. Étant donné que le dossier actuel est recherché en premier, avant de passer à system32, etc., mon DLL) modifié est chargé, exécutant mon code supplémentaire.

    Inconvénient: Vous devez le mettre là avant de commencer le jeu.

    • Identique à la première méthode, mais en remplaçant directement le DLL dans system32).

    Inconvénient: Vous ne pouvez pas ajouter de code spécifique au jeu. Vous ne pouvez pas exclure des applications où vous ne voulez pas que votre DLL soit chargé.

    • Obtenir le décalage EndScene directement à partir du DLL à l'aide d'outils comme IDA Pro 4.9 gratuit. Étant donné que le DLL est chargé tel quel, vous pouvez simplement ajouter ce décalage au DLL adresse de départ, lorsqu'elle est mappée au jeu, pour obtenir le décalage réel, puis le raccorder.

    Inconvénient: Le décalage n'est pas le même sur tous les systèmes.

    • Accrocher Direct3DCreate9 pour obtenir le D3D9, puis accrocher D3D9-> CreateDevice à récupérez le pointeur de périphérique, puis accrochez Device-> EndScene via la table virtuelle.

    Inconvénient: Le DLL ne peut pas être injecté, lorsque le processus est déjà en cours. Vous devez démarrer le processus avec le CREATE_SUSPENDED flag pour accrocher l'initiale Direct3DCreate9 .

    • Création d'un nouveau périphérique dans une nouvelle fenêtre, dès que le DLL est injecté. Ensuite, obtenir le décalage EndScene de ce périphérique et le raccorder, résultant en un crochet pour le appareil utilisé par le jeu.

    Inconvénient: d'après certaines informations que j'ai lues, la création d'un deuxième appareil peut interférer avec l'appareil existant, et il peut y avoir un bug avec le mode fenêtré par rapport au mode plein écran, etc.

    • Identique à la troisième méthode. Cependant, vous effectuerez une analyse de modèle pour obtenir EndScene.

    Inconvénient: ne semble pas aussi fiable.

Comment puis-je accrocher EndScene à partir d'une DLL injectée, qui peut être chargée lorsque le jeu est déjà en cours d'exécution, sans avoir à gérer différents d3d9.dll est sur d'autres systèmes, et avec une méthode fiable? Comment FRAPS, par exemple, exécute-t-il ses hooks DirectX? Le DLL ne devrait pas s'appliquer à tous les jeux, juste aux processus spécifiques où je l'injecte via CreateRemoteThread.

31
Etan

Vous installez un crochet large du système. (SetWindowsHookEx) Une fois cela fait, vous pouvez être chargé dans chaque processus.

Maintenant, lorsque le hook est appelé, vous recherchez un d3d9.dll chargé.

Si un est chargé, vous créez un objet D3D9 temporaire et parcourez la table virtuelle pour obtenir l'adresse de la méthode EndScene.

Ensuite, vous pouvez patcher l'appel EndScene, avec votre propre méthode. (Remplacez la première instruction dans EndScene par un appel à votre méthode.

Lorsque vous avez terminé, vous devez corriger le rappel pour appeler la méthode EndScene d'origine. Et puis réinstallez votre correctif.

C'est ainsi que FRAPS procède. ( Lien )


Vous pouvez trouver une adresse de fonction dans la table 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 désormais un pointeur sur la fonction elle-même. Nous pouvons maintenant soit patcher tous les sites d'appel, soit patcher la fonction elle-même.

Attention, 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
Christopher

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

J'utilise une approche VTable similaire à celle mentionnée par Christopher via une petite DLL d'aide C++ pour déterminer dynamiquement l'adresse des fonctions IDirect3DDevice9 à accrocher. Cela se fait en créant un handle de fenêtre temporaire et en créant un IDirect3Device9 jetable dans l'assembly injecté avant de raccorder ensuite les fonctions souhaitées. Cela permet à votre application d'accrocher une cible qui est déjà en cours d'exécution (mise à jour: notez que cela est possible entièrement dans C # également - voir les commentaires sur la page liée).

Mise à jour : il existe également une version mise à jour pour raccordement Direct3D 9, 10 et 11 toujours en utilisant EasyHook et avec SharpDX au lieu de SlimDX

4
Justin Stenning

Je sais que cette question est ancienne, mais cela devrait fonctionner pour tout programme utilisant DirectX9, vous créez votre propre instance essentiellement, puis obtenez le pointeur sur la VTable, puis vous le connectez. 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
Fredaikis