April 07, 2020
Capture de la géométrie d’un jeu OpenGL par instrumentation des shaders OpenGL
Ce stage de Master 2 de Bastien Thomasson a porté sur la capture de la géométrie d’un niveau de jeu et ce, au fur et à mesure que l’on joue. Le travail portait sur l’écriture d’un programme permettant :
d’intercepter les appels OpenGL pour les dérouter vers des fonctions d’analyse et de capture. Cette fonctionnalité fait essentiellement appel à de la programmation système.
d’instrumenter les shaders pour capturer les informations géométriques. Cette fonctionnalité fait appel à l’analyse grammaticale (bison, flex ; nous avons aussi testé du code ANTLR)
de reconstituer un univers 3D en inférant la matrice de transformation globale à partir de la matrice de vue, image après image.
capture automatique une fois un paramétrage mis en place pendant que le jeu tourne, effectué une seule fois. Et ce, sans altérer le fonctionnement du jeu.
programmation de toute l’interface utilisateur nécessaire à cet effet
Un nuage de points est ainsi alimenté au fur et à mesure que le joueur évolue dans son environnement. Nous nous sommes arrêtés là, mais un maillage peut être reconstitué très rapidement avec des méthodes comme Raster2Mesh (video).
Nous avons testé la capture avec deux jeux, IOQuake 3 (dans la vidéo), et SuperTuxCart.
Les appels OpenGL sont d’abord détournés. Sous Windows, cela consiste à appeler CreateProcess(), attendre le chargement des bibliothèques OpenGL (OPENGL32.DLL et éventuellement les bibliothèques spécifiques aux drivers, fonctions récupérables par wglGetProcAddress()), à récupérer les adresses, puis à poker dans l’espace virtuel du process via WriteProcessMemory() pour détourner vers des fonctions typiquement chargées dans une DLL contenant l’implémentation des fonctions détournées. Ces dernières font leur travail puis appellent à leur tour les vraies fonctions OpenGL.
Il existe d’autres manières de détourner des appels, comme par exemple :
la bibliothèque Detours, de Microsoft Research, qui suit des principes semblables à ceux décrits ci-dessus.
pour les objets COM, la fonction CoTreatAsClass(), ou l’Universal Delegator. Ce dernier permet de faire de l’aggrégation d’objets après que les objets aient été créés (à l’instar de COM_INTERFACE_ENTRY_AGGREGATE_BLIND, mais au moment de l’exécution).
Le programme repose sur l’instrumentation de shaders, dont l’analyse est illustrée de manière basique dans la vidéo ci-dessous.