Licence CC 0

Ge0 vs Guild Wars 2 - Acte 1

Dans les entrailles du binaire.

En plus d’être narcissique comme le titre le sous-entend, j’ai un côté sombre. En plus d’être un windowsien à mes heures perdues (bouuuh! :P) je suis friand de Reverse Engineering. Est-il besoin de le rappeler ?

Sans m’attarder sur pourquoi j’adore ça / je fais ça (j’en parlerais peut-être si on me pose vraiment la question), je vais rentrer dans le vif du sujet avec quelque chose que j’ai essayé de faire il y a 2 ans et que je vais recommencer aujourd’hui : Faire de la rétro-ingénierie sur Guild Wars 2.

Guild Wars 2 est un MMORPG développé par ArenaNet et sorti en Juillet 2012. Il a reçu plusieurs mises à jour de contenu et ne cesse d’avoir du contenu publié depuis. C’est un jeu en vogue auquel j’ai passé beaucoup d’heures (beaucoup trop !). Il est temps de rentabiliser ça.

Le hic, c’est que j’avais commencé quelque chose sur la version x86 du binaire (compilée sur 32-bit, donc). J’étais dans une petite zone de confort (mais pas trop, car le reverse, c’est long et chiant). Depuis, il y a le binaire x64. Je me dis que c’est l’occasion d’apprendre de nouvelles choses et de me heurter à une difficulté certaine.

Dans cette tribune je vais détailler pas à pas quelques tripatouillages pour comprendre comment fonctionne le client du jeu.

Le débogueur à la rescousse

Ici il est question de s’attaquer à quelque chose tout en évitant de dépasser la ligne rouge. Et puis il faut des outils tranchants pour désosser une partie d’un programme dont on ne dispose pas des sources.

Ceux qui ont connu Ollydbg ou Immunity Debugger pour reverse des binaires 32-bit seront ravis d’apprendre qu’il existe x64dbg avec une expérience utilisateur similaire pour les binaires 64-bit. Evidemment, il y a aussi l’incontournable IDA. Dommage qu’il ne soit pas gratuit ou open source, mais peut-on en vouloir à des gens qui veulent en vivre ? ;)

Pour le moment, parce que je suis une tête brûlée, je vais commencer par utiliser x64dbg. On ouvre le binaire du client du jeu et on atterrit sur une fenêtre comme celle-là :

x64dbg - vue d’ensemble

C’est pas beau, hein ? ;)

Grâce à mes talents sur Paint, je vous ai numéroté les 4 zones principales quand on analyse le code binaire d’un exécutable.

  1. C’est le code désassemblé, lui-même découpé en quatre colonnes. De gauche à droite, on a les adresses mémoires, les opcodes représentés en hexadécimal (parce que le binaire, c’est bien, mais c’est masochiste à ce niveau), les mnémoniques correspondants et des indications textuelles pour aider à la compréhension de la micro-instruction concernée.
  2. Il s’agit du contexte du thread courant, notamment la valeur des registres généraux ou encore des flags processeurs.
  3. Il s’agit d’une zone permettant de faire un dump mémoire. Un peu comme dans un éditeur hexadécimal. Contrairement à la fenêtre 1, dans cette fenêtre, on considère qu’on a des données alors que dans la fenêtre du dessus, c’est supposé être du code exécutable.
  4. Il s’agit d’une zone un peu comme la numéro 3, sauf qu’on y représente par convention la stack, la pile d’exécution. Par ailleurs, vous remarquerez que l’adresse surlignée en noir, 000000E52F92F4A0, est la valeur qui est contenue dans le registre RSP au niveau de la fenêtre 2. Ca nous permet d’avoir un visuel sur l’état de la stack à chaque déroulement d’instructions. Pratique, hein ? :)

Si vous ne savez pas ce qu’est la stack, je vous renvoie ici : https://zestedesavoir.com/articles/97/introduction-a-la-retroingenierie-de-binaires/

Vous ne comprenez pas tout ? Ce n’est pas grave. Je n’ai pas le temps de passer en revue chaque détail. Vous comprendrez ceux-ci de vous-mêmes au fur et à mesure de ma progression. A vrai dire moi-même je ne sais pas tout ce que je fais. On y va ensemble à tâtons.

Un clic sur l’onglet Memory Map dans la barre juste au-dessus des fenêtres 1 et 2 nous mène sur une nouvelle vue comme vous pouvez le voir ci-dessous :

x64dbg - vue sur l’espace d’adressage du processus

Les colonnes parlent d’elles-mêmes. Ici on va parler de segments mémoires qui comportent une adresse, une taille, une information sur son nom/son contenu, son type, son niveau de protection actuel et initial.

Par niveau de protection d’un segment mémoire, on désigne les différentes permissions que le processeur possède sur ces segments :

  • R: initiale de Read, il s’agit de la possibilité de lire la mémoire.
  • W: initiale de Write, il s’agit de la possibilité d’écrire la mémoire.
  • E: initiale de Execute, il s’agit de la possibilité d’exécuter la mémoire (qui contient du code exécutable, très probablement, à 99.99%, donc).

J’ignore ce à quoi fait référence le C, mais la lecture des sources de x64dbg est laissée à l’appréciation du lecteur qui a du temps à perdre à lire ma maudite tribune.

On peut voir aussi que dans l’espace mémoire du processus, il y a les sections de gw2-64.exe mais aussi de usp10.dll ou encore de gdiplus.dll. Et il y en a encore d’autres. Les DLL qui sont utilisées par le processus en cours sont chargées dans son espace d’adressage. Et c’est pareil pour les autres processus qui sont en cours d’exécution sur votre machine Windows.

En fait, sur nos systèmes d’exploitations récents, les processus utilisent de la mémoire virtuelle. Ils ont l’illusion d’accéder à toute la mémoire adressable du système d’exploitation. Lorsqu’une zone mémoire est sollicitée via son adresse, celle-ci, qui est virtuelle, va être traduite en adresse mémoire physique. Ce mécanisme est géré par la Memory Management Unit, qu’on abrège souvent en MMU. Elle a pour but d’isoler chaque processus pour éviter qu’un processus ne vienne modifier directement la mémoire d’un autre processus.

Et comme une DLL est partagée entre plusieurs processeurs, son code résidera au même emplacement physique en mémoire mais chaque processus qui l’utilise l’aura dans son espace d’adressage virtuel, et peut-être à des adresses mémoires différentes.

… C’est pas clair, hein ? :D

Allez, un exemple ! Ici, sur gw2-64.exe, on voit que la DLL usp10.dll est chargée à partir de l’adresse mémoire 00007FFA993F0000. Dans un autre processus, cette adresse pourrait tout à fait être (au pif) 00007FFA996F0000. Ces deux adresses virtuelles peuvent être traduites à la même adresse physique (au pif) DEADBEEF00000000. Il s’agit de la même zone mémoire physique !

Mais si on arrive à écrire dans la mémoire qui correspond à notre DLL partagée entre les autres processus, on en revient donc à pouvoir écrire dans la mémoire des autres processus qui utilisent cette DLL, non ?

Eh bien… Pas tout à fait ! ;) Vous vous douteriez bien qu’il s’agirait autrement d’une faille de sécurité très sévère pour un système d’exploitation quelconque !

L’intérêt de partager la mémoire d’une DLL entre les processus qui utilisent cette même DLL fait gagner du temps. Cela serait trop lourd de charger et recharger la même DLL pour chaque processus (en plus d’être gourmand en mémoire). Mais si une modification d’une zone partagée intervient, le mécanisme de Copy On Write va permettre de copier seulement la page mémoire modifiée dans une autre zone de mémoire physique et ainsi éviter que les processus utilisant la DLL soient victime des modifications apportées par un processus voisin.

Si on reprend notre exemple (qui reste très théorique), sur gw2-64.exe on a usp10.dll est chargée à partir de 00007FFA993F0000 et on a dans un autre processus cette même usp10.dll en 00007FFA996F0000. Elles font toujours référence à DEADBEEF00000000. Mais si l’autre processus se met à réécrire la mémoire de usp10.dll pour une raison X ou Y, alors la page mémoire modifiée par être copiée à un autre emplacement physique, admettons DEADBEED00000000.

Mais si les adresses physiques changent, les adresses virtuelles, elles, ne changent pas ! Elle est vachement cool, la MMU, quand même ;) sans parler de l’OS qui s’occupe de tous ces détails sordides (mais ô combien croustillants) pour nous !

Ca fait beaucoup d’un coup et on n’a pas beaucoup parlé de Guild Wars 2. Mais il nous faut un minimum de bases pour savoir ce qu’on fait. Et il m’a semblé important d’expliquer comment un processus sous un système d’exploitation comme Windows est grossièrement situé en mémoire. Vous avez tous les détails techniques de l’agencement de la mémoire virtuelle ici : https://docs.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/virtual-address-spaces.

Par exemple, on voit que les adresses virtuelles 0000000000000000 jusqu’à 7FFFFFFFFFFFFFFF correspondent à l’espace mémoire utilisateur de votre processus, tandis que l’espace d’adressage 8000000000000000 à FFFFFFFFFFFFFFFF correspond à l’espace d’adressage du noyau. A l’instar des DLL, chaque processus avait le noyau chargé en mémoire entre 8000000000000000 et FFFFFFFFFFFFFFFF. Maintenant, depuis que les vulnérabilités Meltdown et Spectre ont été soulevées, c’est… Un peu plus compliqué ! ;)

Enfin ! On a beaucoup parlé de mémoire virtuelle car c’était l’occasion, et je suis au regret de vous dire qu’on va s’arrêter là pour que cette tribune ne prenne pas trop de proportions démesurées. Mais ça tombe bien, j’ai déjà une idée de ce dont je parlerai dans la tribune suivante.


On a vu grosso-modo comment un processus sous Windows était géré au niveau de sa mémoire. Grâce à la MMU, il a l’illusion d’être le seul processus du système et de disposer de l’intégralité de l’espace d’adresse mémoire (utilisateur, bien entendu ! Puisqu’un accès direct à une adresse résidant dans l’espace noyau se soldera par une erreur).

Dans la prochaine tribune, on parlera du format Portable Executable, c’est-à-dire le format qui structure les fichiers exécutables .exe, .dll ou encore .sys.

Score actuel :

Ge0 0 - 1 Guild Wars

10 commentaires

J’ai juste survolé pour le moment, j’ai hâte d’avoir le temps de lire tout ça et surtout de voir la suite.
Tu ne dit pas pourquoi tu veux faire ça, simple curiosité/défi ou un but autre comme développer des outils comme ce qui se faisait pour la cartographie sur GW1 par exemple ?
Ton "sauf si on me pose vraiment la question" donne vraiment envie de vraiment te la poser.

Hello! :)

Tu ne dit pas pourquoi tu veux faire ça, simple curiosité/défi ou un but autre comme développer des outils comme ce qui se faisait pour la cartographie sur GW1 par exemple ?

backmachine

En fait j’ai découvert le reversing il y a 10 ans sur https://www.newbiecontest.org/. Je trouvais ça "ouf" de pouvoir trouver un mot de passe dans un binaire. A côté de ça j’étais assez curieux pour comprendre ce qui se cache derrière un .exe. D’autant plus lorsque je découvrais le langage C et que je voulais comprendre comment fonctionnait le code généré par la compilation d’un programme. Ca m’embêtait de ne pas savoir, comme si j’étais incomplet à ce niveau.

À côté je joue un peu (un de mes gros défauts et ce pourquoi je reste sous Windows…) et à l’époque, je jouais à un petit MMORPG amateur, et ça a été mon terrain de jeu au grand dam de certains. J’ai trouvé ça amusant de :

  • comprendre comment un programme dont on ne dispose pas des sources fonctionne ;
  • pouvoir modifier un programme dont on ne dispose pas des sources pour lui faire faire ce qu’on veut ;
  • pouvoir récupérer des techniques utilisées par des programmes dont on ne dispose pas des sources, pour savoir "comment les développeurs de tels logiciels font telles ou telles choses".

C’est borderline, mais ça a été un vecteur pour apprendre beaucoup de choses en cryptographie, sécurité et même en développement ! Genre se rendre compte de ce qu’il se passe derrière l’utilisation du polymorphisme en C++ (chaque instance possède un pointeur sur la fameuse vtable…). Et j’en passe.

Là, Guild Wars 2 c’est assez énorme. C’est un défi personnel avant tout, et savoir si c’est si compliqué / hors d’atteinte de se concentrer sur le développement d’un client de MMORPG comme celui-ci.

J’espère que ça répond à ta question.

Ben justement, tu dis que c’est borderline, mais ils ont pas précisé dans leurs conditions que le reverse engineering est interdit ? Je sais que c’est le cas sur Civilization V parce qu’ils l’affichaient au lancement du jeu. Mais je pense que ça doit être assez courant, et pour le coup ça placerait directement ta série de billets sous le coup de l’illégalité ?

+2 -0

Ben justement, tu dis que c’est borderline, mais ils ont pas précisé dans leurs conditions que le reverse engineering est interdit ? Je sais que c’est le cas sur Civilization V parce qu’ils l’affichaient au lancement du jeu. Mais je pense que ça doit être assez courant, et pour le coup ça placerait directement ta série de billets sous le coup de l’illégalité ?

Phigger

Interdit (comprendre : aller à l’encontre des conditions d’utilisation du logiciel) et illégal (comprendre : enfreindre la loi), c’est différent. Je vais bien évidemment contre leurs conditions d’utilisation et c’est un motif pour qu’ils m’interdisent l’accès au jeu (ils faut qu’ils puissent faire le lien avec mon identifiant en jeu s’ils voient ce message).

Après quelques recherches sur internet, je suis tombé sur ça : https://www.legifrance.gouv.fr/affichCodeArticle.do?cidTexte=LEGITEXT000006069414&idArticle=LEGIARTI000006278920 où il est question de pratiquer la rétro-ingénierie à des fins d’inter-opérabilité (ce qui n’est pas mon cas, ici on veut juste comprendre ce qu’il se passe).

Je vois aussi sur : https://fr.wikipedia.org/wiki/R%C3%A9tro-ing%C3%A9nierie#France

I. - Est puni de 3 750 EUR d’amende le fait de porter atteinte sciemment, à des fins autres que la recherche […]

C’est là qu’on parle de flirter avec la ligne rouge.

Si ça dérange véritablement la communauté qu’une ribambelle de billets sur ce sujet voient le jour sur ZdS, alors je peux les dépublier d’un clic. Tu te doutes bien que ça ne m’empêchera pas de continuer. Ca sera moins motivant, certes, mais je ne fais pas ça dans le but de nuire. Juste de comprendre.

Ok, je comprends mieux ce que tu signifiais du coup dans flirter avec la ligne rouge :) Moi je voulais juste être prudent, mais pour le coup ça m’a l’air vraiment réfléchi. L’important étant bien entendu que ZdS ne soit pas impliqué dans un problème.

edit : quand je dis que je voulais être prudent, je parlais bien uniquement de mon précédent message, je ne dis pas que j’incitais à prendre une quelconque mesure.

+0 -0

J’ignore ce à quoi fait référence le C, mais la lecture des sources de x64dbg est laissée à l’appréciation du lecteur qui a du temps à perdre à lire ma maudite tribune.

1
2
3
4
5
6
7
8
9
case PAGE_WRITECOPY:
    strcpy_s(Rights, RIGHTS_STRING_SIZE, "-RWC");
    break;

/* ou */

case PAGE_EXECUTE_WRITECOPY:
    strcpy_s(Rights, RIGHTS_STRING_SIZE, "ERWC");
    break;

Fichier src/dbg/memory.cpp. ;)
Ben quoi ? On ne peut pas avoir du « temps à perdre » ? :-°

Édit

Ah ! Et pour le G.

1
strcat_s(Rights, RIGHTS_STRING_SIZE, ((Protect & PAGE_GUARD) == PAGE_GUARD) ? "G" : "-");
+1 -0

Aaaahhhh…

A guard page provides a one-shot alarm for memory page access. This can be useful for an application that needs to monitor the growth of large dynamic data structures. For example, there are operating systems that use guard pages to implement automatic stack checking.

C’est donc utilisé pour limiter la taille de la stack (entre autre ?) et d’autres zones de mémoire qui sont amenées à grossir au fil de l’exécution.

Je suis heureux que tu m’aies aidé à apprendre ça, Taurre. :) Merci.

Aaaahhhh…

A guard page provides a one-shot alarm for memory page access. This can be useful for an application that needs to monitor the growth of large dynamic data structures. For example, there are operating systems that use guard pages to implement automatic stack checking.

C’est donc utilisé pour limiter la taille de la stack (entre autre ?) et d’autres zones de mémoire qui sont amenées à grossir au fil de l’exécution.

Je suis heureux que tu m’aies aidé à apprendre ça, Taurre. :) Merci.

Ge0

Avec plaisir, mais je ne connaissais pas spécialement non plus. :p
À ce sujet, ce message est intéressant, il parle notamment de la mise en place plutôt récente dans le noyau Linux d’une protection contre le « stack clash » ou le « stack smash » c’est-à-dire qui évite que le tas et la pile entre en collision, entre autre. ^^"

+1 -0

Hello,

Juste une petite question, suite à ce passage:

Mais si les adresses physiques changent, les adresses virtuelles, elles, ne changent pas ! Elle est vachement cool, la MMU, quand même

Comment cette MMU mémorise le lien entre les adresses virtuels et physique ? Et est-ce que ça ne prend pas de la place en mémoire ?

Et sinon comme d’hab billet que j’admire tout plein ! Continue comme ça ;)

Et petite précision tout de même, je ne suis pas dans des études d'informatique, du moins pas encore et pratique uniquement la programmation par passion. Donc mes questions peuvent être absurde..

Hello!

Comment cette MMU mémorise le lien entre les adresses virtuels et physique ? Et est-ce que ça ne prend pas de la place en mémoire ?

Si ! :D Chaque processus possède un tableau (précision, j’essaie de ne plus dire "une table" par ce que a table est un faux ami, haha !) en mémoire où tu as un équivalent de mémoire physique pour chaque adresse mémoire virtuelle.

Chaque processus possède un contexte (cf. la section "2" du premier screen), c’est-à-dire des valeurs pour chaque registre. Chez les processeurs Intel, par exemple, il existe un registre CR3 (Control Register 3). Ce registre contient une adresse pointant sur ce fameux tableau de traduction d’adresses mémoires virtuelles vers des mémoires physiques… Et l’adresse contenue dans CR3 est physique ! :D

Accéder au registre CR3 requiert un certain niveau de privilège. Tu ne peux évidemment pas le faire en mode utilisateur. Il faut que tu sois en mode noyau pour ça.

Bref ! Grâce à ce fameux tableau en mémoire, qu’on appelle PDE (pour Page Directory Entry), tu peux traduire ton adresse virtuelle en adresse physique.

La pagination mériterait une tribune tout à elle seule quand on y pense… Je t’invite à consulter ce lien si tu veux en savoir plus sur le fonctionnement de la pagination.

Il y a aussi un livre que j’adore, The Rootkit Arsenal: Escape and Evasion: Escape and Evasion in the Dark Corners of the System, qui explique également le fonctionnement de la pagination dans un contexte de création de rootkits.

Sans m’avancer, je pense aussi que le célèbre ouvrage Windows Internals a sûrement dédié un passage à la pagination (sous Windows) et doit donc expliquer comment tout cela fonctionne.

Et petite précision tout de même, je ne suis pas dans des études d’informatique, du moins pas encore et pratique uniquement la programmation par passion. Donc mes questions peuvent être absurde..

Les questions absurdes sont de mon point de vue celles qu’on ne pose pas. Si tu savais le nombre de questions "absurdes" que je pouvais poser, dans ce cas… On n’en aurait pas fini. ;)

Merci pour ton message.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte