D3D Hooking

En se 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 pourrais 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 stoqué dans un tableau de CUSTOMVERTEX, la structure CUSTOMVERTEX est défini par la méthode SetFVF() qui se trouve dans l’objet IDirect3DDevice9. Je défini mon point par D3DFVF_XYZ|D3DFVF_DIFFUSE se 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 touts 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 fonction, 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 fonction 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 fonction 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 certaine version de la librairie. mais je suis obliger 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 offset.
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és dans la version neuf par souci de compatibilité. ou encore hooker la fonction Direct3DCreate9 qui elle est exporté.

Je préfère procéder par un hook inline, sodomite inside. Donc la DLL que je vais injecté vas 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é 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é et c’est gagné. Après le Sleep() j’aurai accès au argument de SetStreamSource().

On aura donc l’adresse des objets IDirect3DDevice9, IDirect3DVertexBuffer9 ainsi qu’a 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.exesource
La dll à injecter: d3d.dllsource
Le screen: ici

Aujourd’hui c’est coloriage

Je me suis demandé comment fonctionnait l”affichage sous windows (afficher un pixel à l”écran). Quand j”ai fait mes premiers pas en assembleur je me suis amusé à afficher une lettre, c”était sous win98 grâce au mode cga qu”on obtient avec ces instructions INT 10h / AH 6h. Puis j”ai essayé de lancer le même code sous XP et la c”est le drame, rien. Depuis windows xp & co il y a eu pas mal de changement et mon programme ne fonctionne plus. En réalité on ne peut plus lire/écrire directement dans la mémoire physique réservée au mode cga car:

  • L”espace mémoire en question se situe après l”adresse 0x7ffeffff (Ring 0).

 

Je me suis mis à la recherche de la mémoire vidéo, le mode cga n”étant plus utilisé depuis longtemps. Une des façons de procéder est d”écrire un driver (pour accéder au noyau) et d”envoyer des requêtes au bus pci pour trouver la carte graphique et ses caractéristiques. Je me suis basé sur un article de rAsM à lire absolument si vous voulez comprendre le code. J”ai ajouté des KeAcquireSpinLock et KeReleaseSpinLock histoire de ne pas avoir de surprise avec les READ/WRITE_PORT.

Le code du driver, Le résultat:

Base address #0 (BAR0) // domaines de port I/O
Physical [0x1401 - 0x1411]
Base address #1 (BAR1) // 1er partie de la mémoire vidéo
Physical [0xf0000000 - 0xf8000000]
Base address #2 (BAR2) // 2eme partie de la mémoire vidéo
Physical [0xec000000 - 0xec800000]

Bien sûr se sont des adresses physique, on peu ensuite utiliser la fonction MmMapIoSpace pour y accéder mais sa reviendrai à faire une copie de la mémoire physique vers la mémoire virtuelle et on se retrouve avec DEUX buffer vidéo en ram :/.
Alors on vas essayer de retrouver le premier qui est déjà en mémoire. J”ai donc fait un programme en C qui affiche un pixel rose en haut à gauche de l”écran.

//Rien de complexe pour le programme.
HDC hScreenDC;
hScreenDC = GetDC(0); // screen
DWORD color = 0x00FF00FF;  // rose :}
SetPixel(hScreenDC, 0, 0, color);
ReleaseDC(0, hScreenDC);

Et ensuite je débugge tout ça, donc dans notre programme on commence par un appel a la fonction SetPixel qui se trouve dans gdi32.dll. La GDI permet de faire le lien entre les applications et les pilotes graphiques. et à la fin de gdi32!SetPixel on as un appel à une petite routine assez spécifique:

mov     eax, 111Ah
mov     edx, 7FFE0300h
call    dword ptr [edx] ; ntdll!KiFastSystemCall

Pour ceux qui n”ont pas compris la subtilité de ses quelques instructions, en fait on a un appel à KiFastSystemCall() avec comme argument 0x111A. Puis vient le SYSENTER et on entre dans le noyau (pas directement bien sûr), la fonction KiFastCallEntry() récupère notre argument puis on passe par la fonction KiSystemService() qui va se charger de trouver la fonction équivalente a 0x111A. Ce numéro ne représente pas vraiment une fonction, je pense qu”il y a un 0×1000 AND 0x111A, pour indiquer si on a affaire à la SSDT ou à la SSDT Shadow.

0: kd> dd nt!KeServiceDescriptorTableShadow l 0x8
805614c0    804e48b0   00000000    0000011c    80517fc4
805614d0    bf999c00   00000000    0000029b    bf99a910

On voit bien que le nombre de fonction dans la SSDT Shadow est de 0x29b, donc 0x111A c”est trop grand. Par contre après avoir viré ce 0×1000 on se retrouve avec le numéro 0x11A et là ça le fait.

0: kd> dd bf999c00 l 0x11b
...
bf99a060    bf949a50    bf841b72    bf8751a3
0: kd> ln bf8751a3
(bf8751a3)   win32k!NtGdiSetPixel

Le nom de la fonction est assez explicite, elle fait appel a de moult autre fonctions dans win32k.sys. je trace un peu tout ça pour en ressortir les fonctions qui nous intéressent.

// arbre d''appel
win32k!NtGdiSetPixel
 -> win32k!SURFACE::pfnBitBlt
 -> win32k!SpBitBlt
   -> win32k!OffBitBlt
    -> win32k!CLIPOBJ_vOffset
     -> win32k!EngBitBlt
      -> win32k!PDEVOBJ::vSync
        -> win32k!vDIBSolidBlt
        -> win32k!vSolidFillRect1
// code qui rox
mov eax,dword ptr [ebp+18h] ; eax=00ff00ff code de la couleur
mov dword ptr [edi],eax ; edi=f9234000 adresse pointant dans la mémoire vidéo.

Je vérifie que cette adresse corresponde bien à l”adresse physique 0xf0000000.

0: kd> !vtop 0 f9234000
X86VtoP: Virt f9234000, pagedir d4a6000
X86VtoP: PDE d4a6f90 - 01014163
X86VtoP: PTE 10148d0 - f000017b
X86VtoP: Mapped phys f0000000
Virtual address f9234000 translates to physical address f0000000.

Woot! Je ne sais pas comment procède windows pour retrouver l’adresse 0xf9234000 (si quelqu’un peu m”éclairer). Par contre je m’aperçois qu’après f9234000+1D4C00 (1D4C00 = 800*600*4 la résolution de l’écran) la mémoire est à zéro, de 0xf9408c00 à 0xf9434000 se qui fait 177ko de libre. Vu la place je pense à la technique du Shadow Walker, le principe serai de faire croire au système que les pages non utilisées de la mémoire vidéo sont swappées. On pourrai y cacher du code ou des datas. Mais cette technique commence à dater et nécessite un hook dans l’idt ce qui peut être détectable. Et dans le cas où l’utilisateur changerai sa résolution d’écran il y aurai surement un conflit, dommage…

“We’re all mad here. I’m mad. You’re mad.” said the Cheshire Cat

Je me suis enfin décidé à virer mon vieux blog, codé par mes soins il y a maintenant plusieurs années. Et cela pour en ouvrir un nouveau, exposant mes pérégrinations dans le vaste monde de l’informatique.
En espérant que cela vous plaise.