🇫🇷 AMSI CTF 2025 - Cavormice [reverse]
Difficulty : Medium
Team : Phreaks 2600
Description : La petite souris est perdue dans cette grotte labyrinthique ! Peux-tu l’aider à trouver le chemin vers le trésor dans la salle du haut ?
Le format du drapeau est AMSI{XXXXXXXX…} oĂą X est une direction parmi {U,D,L,R}, respectivement pour Up, Down, Left, Right.
Trouve le bon chemin.
Author : Hexwreaker
Topics :
TL;DR
- Il y a un endroit en mémoire qui stocke tous nos déplacements.
- Et chaque paire de déplacement sera xorée et comparée avec un octet prédeterminé, si on arrive à valider ces valeurs, on valide le jeu et on peut gagner la partie.
Introduction
Ce challenge sort du lot car il s’agit de faire du reverse sur un jeu de gameboy. Le but est de mener notre souris vers le bon chemin pour dĂ©couvrir un trĂ©sor. Voici un quoi ressemble le jeu :
Prise en main
Pour Ă©muler le jeu j’ai utilisĂ© mgba-qt
qui peut Ă©muler des jeu gameboy et gameboy advance et intĂ©gre une fonction pour voir la mĂ©moire et chercher des valeurs Ă l’intĂ©rieur.
Je me baladais mais je trouvais rien de spĂ©cial, j’ai remarquĂ© que la salle du haut Ă©tait toujours la meme peut importe Ă quel point on se baladait avec la souris, mĂŞme si on descendait puis on remontait, la salle est toujours lĂ ..
Et au bout d’un moment après avoir assez jouĂ©, on finit par perdre, et notre souris rebrousse chemin sans un sou.
Donc on comprend qu’il y a des conditions Ă remplir pour pouvoir gagner.
Repérage dans la mémoire
J’ai dĂ©cidĂ© de jeter un oeil en mĂ©moire car il devrait y avoir des valeurs qui changent comme notamment la position de ma souris. Avec mgba-qt
on peut afficher la mémoire depuis outils > game state view > voir la mémoire
.
L’espace mĂ©moire est petit, de 0 Ă 0xFFFF donc 65535 octets. Et Ă certain endroits il y a des valeurs qui bougent en permanence.
Donc j’ai pensĂ© chercher l’endroit ou sont Ă©crits les dĂ©placement de la souris. J’ai notĂ© mes dĂ©placements, (haut/haut/bas/droite -> UUDR) d’ailleurs un dĂ©placement correspond Ă une salle visitĂ©e et non Ă un pas de souris. Puis je les ai cherchĂ© en mĂ©moire avec un autre utilitaire de mgba-qt
:
On dĂ©couvre qu’Ă l’adresse 0xC808 se trouve l’historique des dĂ©placements, ainsi que deux octets qui gèrent la position de la souris (pas très important mais bon Ă savoir).
Et lorsqu’on continue de jouer, le 33ème dĂ©placement met fin Ă la partie.
Maintenant l’objectif est de savoir quelles sont les conditions de victoire.
Décompilation du jeu
Vu que je ne sais pas lire une ROM Gameboy de tĂŞte, j’ai cherchĂ© si c’Ă©tait faisable sur Ghidra et effectivement il y a une extension pour ça : https://github.com/Gekkio/GhidraBoy.
Il y a le mĂŞme nombre d’octet qui sont lus (0xFFFF, 65535) et Ă des endroits on retrouve du code.
En regardant Ă l’adresse 0xC808, celle oĂą les dĂ©placements sont stockĂ©s on voit qu’un espace de 32 octets est rĂ©servĂ©e.
Et qu’il y a 3 rĂ©fĂ©rences croisĂ©es vers cette addresse:
image cross references
dont une est plutôt intéressante :
FUN_0729
|
|
On voit que la valeur Ă l’indice cVar1
de DAT_c808
est xorĂ©e avec l’a valeur Ă l’indice cVar1+1
de DAT_c808
puis comparĂ©e Ă la valeur Ă l’indice iVar2
de &DAT_c828
.
Si la valeur est diffĂ©rente, la fonction s’arrĂŞte grâce Ă un return 0
, sinon la boucle continue et cVar1
et iVar2
sont incrémentés.
On comprend qu’il faut Ă©viter que la fonction s’arrĂŞte prĂ©maturĂ©ment, mais aussi que nos dĂ©placements sont pris par paires, xorĂ©s et comparĂ©s avec une valeur Ă l’adresse 0xC828.
Par ailleurs, cette valeur n’est pas Ă©crite en dur dans Ghidra mais on a l’espace d’adressage et il est sur 16 octets. Si on veut la valeur cependant il faudra Ă©muler le jeu.
La valeur est la suivante : 191908160700191108161119001E0711
Recomposition du bon chemin
Ainsi la chaine à obtenir est le resultat de 16 opérations de XOR entre différentes directions parmi : D,U,L,R.
Et selon la chaine, il y a 7 valeurs de XOR possible:
- 19
- 08
- 16
- 07
- 00
- 11
- 1e
On va donc commencer par gĂ©nĂ©rer une tables qui fait correspondre les paires de direction avec le rĂ©sultat du XOR qu’elles donnent.
|
|
On obtient ces valeurs :
UU 0x0
UD 0x11
UR 0x7
UL 0x19
DU 0x11
DD 0x0
DR 0x16
DL 0x8
RU 0x7
RD 0x16
RR 0x0
RL 0x1e
LU 0x19
LD 0x8
LR 0x1e
LL 0x0
On peut mĂŞme recomposer Ă la main le chemin satisfaisant.
Mais il faut garder Ă l’esprit ces contraintes :
- Le premier mouvement de la partie est toujours
U
, car c’est le seul mouvement possible au lancement du jeu. - une fois entrĂ©e dans la grotte, tout dĂ©placement de la souris vers le haut ne peut donner suite qu’Ă un dĂ©placement vers le bas (on est bloquĂ© dans la caverne).
Par exemple, pour le début on doit avoir 19 19
donc soit UL
soit LU
. Etant donnĂ© qu’au dĂ©but on va vers le haut pour rentrer dans la grotte, on ne peut commencer que par U
donc UL
.
Puis on a un deuxième 0x19. Soit UL soit LU, mais si on choisit UL, une fois monté avec U
, on sera bloquĂ© dans la grotte et on ne pourra faire qu’un mouvement vers le bas pour en sortir. Donc on lieu d’avoir UL on aurait un UD.
Donc le bon choix est LU. En continuant ainsi on peut commencer Ă construire notre chemin.
Il y a parfois des choix avec plusieurs solutions valides comme RL et LR donnant lieu à une multitude de chemins menant chacun vers la victoire. Ces chemins étaient tout de même valide pour obtenir le flag après avoir demandé au chall maker.
Au final avec ces raisonnements on obtient par exemple ce chemin avec un script : ULLUDLDRRUDDLUDUDLDRUDLUDDLRRUDU
solve.py
|
|
Qui nous permet de gagner :

Flag : AMSI{ULLUDLDRRUDDLUDUDLDRUDLUDDLRRUDU}