Mysterie

IT Security researcher, bug hunter, code breaker

D3D Hooking

21 May 2009 » security

En ce moment, je m’intéresse à la programmation Direct3D. J’ai réalisé un programme bidon qui affiche un symbole en 3d et le fait tourner sur lui-même. Pas d’anti-aliasing ou autre joyeuseté du style. Je me suis simplement servi de vertex (une vertex/vertice pourrait se résumer à un point). Le but de ce programme est d’avoir une base pour réaliser ensuite un hook sur la fonction manipulant les vertex et leurs informations, histoire de retrouver les couleurs et coordonnées des points du symbole affiché.

Chaque point de la figure est stocké dans un tableau de CUSTOMVERTEX, la structure CUSTOMVERTEX est définie par la méthode SetFVF() qui se trouve dans l’objet IDirect3DDevice9. Je définis mon point par D3DFVF_XYZ|D3DFVF_DIFFUSE ce qui représente { (float)X, (float)Y, (float)Z, (DWORD)color, }.

Une autre méthode intéressante est SetStreamSource() faisant partie du même objet. Elle va aller toucher au buffer contenant tous les points, et cela à chaque fois qu’on affiche une image. Il suffit donc d’injecter une dll dans le programme et de retrouver IDirect3DDevice9::SetStreamSource() pour la hooker. Sauf que l’objet IDirect3DDevice9 est en mémoire, où? Impossible à savoir de l’extérieur.

Le code important dans le programme est:

// (LPDIRECT3DDEVICE9)		gPd3dDevice
// (LPDIRECT3DVERTEXBUFFER9)	gVertexBuff

// Préparation du vertex buffer
gPd3dDevice->SetStreamSource(0, gVertexBuff, 0, sizeof(CUSTOMVERTEX));
// Définition de la taille d'un point/vertex
gPd3dDevice->SetFVF(D3DFVF_XYZ|D3DFVF_DIFFUSE);
// on dessine un premier triangle
gPd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 1);
// puis le second... et ainsi de suite pour avoir le symbole
gPd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 3, 1);

Voici la version assembleur pour l’appel à gPd3dDevice->SetStreamSource(0, gVertexBuff, 0, sizeof(CUSTOMVERTEX));.

mov   edx,dword ptr ds:[gVertexBuff] ; on met l'adresse de gVertexBuff dans edx
mov   eax,dword ptr ds:[gPd3dDevice] ; on met l'adresse de gPd3dDevice dans eax
mov   ecx,dword ptr ds:[eax] ; ecx pointe vers la table de fonction de l'objet DIRECT3DDEVICE9
push  10 ; Stride (taille d'un point/vertex) 0x10 = 16 = DWORD*4
push  0 ; Offset
push  edx ; l'adresse de gVertexBuff
push  0 ; StreamNumber
push  eax ; l'adresse de gPd3dDevice
mov   eax,dword ptr ds:[ecx+190] ; 64eme fonction de l'objet d3dDevice
call  eax ; gPd3dDevice->SetStreamSource(0, edx, 0, 10);

Donc l’objet IDirect3DDevice9 contient une table de fonctions. La fonction SetStreamSource est la 64eme et fait référence au module d3d9.dll. En cherchant un peu, je retrouve comment est initialisé l’objet. En fait, il se base sur une autre table de fonctions qui se situe aussi dans le module d3d9.dll et dont SetStreamSource est la 134eme fonction. Comme je ne peux pas savoir comment est structuré l’objet en mémoire, je me baserai sur la table de fonctions de d3d9.dll pour retrouver SetStreamSource.

La table se situe dans la section .text à l’offset 0x15C38. C’est hardcodé donc c’est moche et ça ne marchera que pour certaines versions de la librairie. Mais je suis obligé de procéder de cette manière car SetStreamSource n’est pas exporté. D’ailleurs, la plupart des codes que j’ai trouvé utilisent des offsets.
Sinon, il y a plusieurs alternatives dont le hook de l’IAT sur des fonctions de type d3d8thk.* qui sont d’anciennes fonctions de d3d8.dll utilisées dans la version 9 par souci de compatibilité. Ou encore hooker la fonction Direct3DCreate9 qui elle est exportée.

Je préfère procéder par un hook inline. Donc la DLL que je vais injecter va retrouver SetStreamSource(), la rediriger vers une fonction intermédiaire qui aura pour but de récupérer les arguments de la pile. Comme la DLL injectée fait partie d’un nouveau thread, je vais faire un Sleep(100). Les autres thread du programme vont avoir le temps de s’exécuter et d’utiliser ma fonction hookée et c’est gagné. Après le Sleep() j’aurai accès aux arguments de SetStreamSource().

On aura donc l’adresse des objets IDirect3DDevice9, IDirect3DVertexBuffer9 ainsi que la taille du CUSTOMVERTEX. Ensuite on utilise les objets comme s’ils nous appartenaient. Donc, pour avoir accès aux fameux points de la figure, il suffit de faire un:

// pVertices contiendra l'adresse du début de la mémoire ou sont stoqué les vertex
// gVertexBuff représente l'objet IDirect3DVertexBuffer9
gVertexBuff->Lock(0, vertexSize, (void **)&pVertices;, 0);
gVertexBuff->Unlock();

Ensuite on peut manipuler ces objets à volonté, lire/changer la position des points, la couleur etc… Le code est disponible mais ça reste un PoC. Je change juste la couleur du symbole rien d’extraordinaire. J’ai surtout fait ça pour cheater à l’iut :] For Aiur!

Version de d3d9: 5.3.2600.2180
Le programme: d3d.exe - source
La dll à injecter: d3d.dll - source
Le screen: ici