Un exemple de rétro-ingénierie avec GDB et Iaito/Radare2
2026-05-27T23:56:34+08:00
Au cours des dernières semaines, j’ai appris la rétro-ingénierie, et j’ai rédigé un billet de blog pour partager ma toute première expérience. Ces derniers temps, je vais un peu plus loin, en résolvant un défi de CTF à l’aide d’une combinaison d’analyse statique et d’analyse dynamique, et j’ai trouvé que c’était un excellent exemple pour expliquer un peu plus en profondeur la rétro-ingénierie.
J’avais auparavant prévu d’écrire un article sur l’analyse dynamique avec GDB. Cependant, j’ai trouvé que ce défi était aussi un très bon exemple pour expliquer GDB. Et pour résoudre ce défi, j’utiliserai en plus un autre outil, iaito, qui est l’interface graphique de radare2.
Le défi
cIMG est en quelque sorte un format d’image destiné à représenter une image constituée de caractères de différentes couleurs dans le terminal. Il est composé de :
- le nombre magique, qui est
cIMG; - le numéro de version ;
- la largeur et la hauteur ;
- les données, chaque pixel contenant R, G, B et le caractère ASCII.
Ce défi consiste à fournir un cIMG à un exécutable qui vous donnera le drapeau uniquement si le cIMG satisfait certaines conditions.
Le code source
Obtenez ici le source du défi. Ce code source semble être sous licence BSD 2-Clause.
Compilez-le avec GCC :
$ gcc -O3 challenge.c -o challenge
Ouvrez Python et créez un exemple de fichier cIMG :
$ python3
Python 3.13.5 (Jun 25 2025, 18:55:22) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> with open("example.cimg", "wb") as file:
... file.write(b"cIMG\x02\0\x0a\x0a")
... file.write(b"\x50"*(10*10*4))
...
8
400
Utilisez le binaire du défi pour ouvrir l’exemple de fichier cIMG :
user@learnaarch64asm:~$ ./challenge ./example.cimg
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
Ce fichier affiche quelque chose, mais ne fournit pas le drapeau.
Analyse statique
Nous devons fabriquer un fichier cIMG qui donne le drapeau. Commençons par une analyse statique.
Ouvrez le fichier dans iaito. Lancez une analyse aaa et ouvrez le graphe de la fonction main. Vous obtiendrez :

Nous pouvons voir la lecture de l’en-tête du fichier à 0x00000cac, la vérification du nombre magique à 0x00000cc4, la vérification de la version à 0x00000cd0, ainsi que la lecture de la largeur et de la hauteur à 0x00000ce0. Ensuite, malloc est utilisé pour allouer un bloc de mémoire pour les données de l’image, puis les données sont lues dans cette mémoire.
Le nombre magique est cIMG, ce qui correspond à 63 49 4d 47 en hexadécimal ; la version est un entier 32 bits en little-endian ; la hauteur et la largeur sont toutes deux des entiers de 8 bits.
Condition pour obtenir le drapeau
Allons à la fin de la fonction. De toute évidence, sym.win est la fonction qui nous donnera le drapeau. Elle est appelée à 0x00000e8c, et cette exécution a lieu après une vérification à 0x00000e58. Cette vérification exige que w19 ne soit pas nul.
Altération du flot de contrôle
Il est temps de lancer GDB.
$ gdb --args ./challenge ./example.cimg
GNU gdb (Debian 16.3-1) 16.3
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+ or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "aarch64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./challenge...
(No debugging symbols found in ./challenge)
Placez un point d’arrêt sur la fonction main et lancez le programme :
(gdb) break *main
Breakpoint 1 at 0xc44
(gdb) run
Starting program: /home/user/challenge ./example.cimg
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".
Breakpoint 1, 0x0000aaaaaaaa0c44 in main ()
Nous remarquons que les adresses de données de GDB ont un décalage de 0xaaaaaaaa0000.
Placez un point d’arrêt juste avant la vérification de la condition du drapeau, puis continuez :
(gdb) break *0x0000aaaaaaaa0e58
Breakpoint 2 at 0xaaaaaaaa0e58
(gdb) continue
Continuing.
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
Breakpoint 2, 0x0000aaaaaaaa0e58 in main ()
Modifiez le contenu du registre w19 et continuez :
(gdb) set $w19 = 1
(gdb) continue
L’exécutable nous donnera alors le drapeau. Le travail est terminé, et passez une bonne journée :-p
Je plaisante. Si l’on concluait réellement le défi ainsi, toute la joie de la RE disparaîtrait. Allons plus loin.
Vérification des données
Retournez dans iaito. Cliquez sur w19 dans le graphe. Cela mettra en surbrillance ce registre dans toutes les instructions du graphe. Faites défiler le graphe en arrière pour trouver un indice.
Nous pouvons voir plusieurs appels à sym.imp.memcmp dans le graphe.
memcmp est une fonction standard de la bibliothèque C, déclarée dans <string.h>, qui compare deux blocs de mémoire octet par octet :
int memcmp(const void *ptr1, const void *ptr2, size_t n);
ptr1 et ptr2 pointent respectivement vers les deux blocs, et n indique le nombre d’octets à comparer. Si les deux blocs sont identiques, la fonction renvoie 0 ; sinon, elle renvoie un nombre non nul.
Il y a quatre appels à memcmp. Chaque appel est suivi de cmp w0, 0. À l’exception du premier appel, il y a aussi un cset w0, eq et un and w19, w19, w0 après cmp. On peut en conclure que si memcmp détecte une différence, w19 sera remis à zéro, puisque w0 vaudra zéro. Nous supposons donc qu’il faut rendre identiques les quatre paires de blocs mémoire.
x0 et x1 sont les deux adresses à comparer. x2 contient la taille ; mov x2, 0x18 nous indique que la taille est de 24 octets.
Ouvrez à nouveau GDB et inspectez la mémoire avant le premier memcmp :
(gdb) break *0x0000aaaaaaaa0e78
Breakpoint 1 at 0xaaaaaaaa0e78
(gdb) run
Starting program: /home/user/challenge ./example.cimg
[Thread debugging using libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1].
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
PPPPPPPPPP
Breakpoint 1, 0x0000aaaaaaaa0e78 in main ()
(gdb) info registers x0 x1
x0 0xaaaaaaac12a0 187649984565920
x1 0xaaaaaaac00c0 187649984561344
(gdb) x/24xb $x0
0xaaaaaaac12a0: 0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b 0x30
0xaaaaaaac12a8: 0x38 0x30 0x3b 0x30 0x38 0x30 0x3b 0x30
0xaaaaaaac12b0: 0x38 0x30 0x6d 0x50 0x1b 0x5b 0x30 0x6d
(gdb) x/24xb $x1
0xaaaaaaac00c0 <desired_output>: 0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b 0x32
0xaaaaaaac00c8 <desired_output+8>: 0x33 0x31 0x3b 0x30 0x31 0x37 0x3b 0x31
0xaaaaaaac00d0 <desired_output+16>: 0x33 0x30 0x6d 0x63 0x1b 0x5b 0x30 0x6d
Des octets aléatoires. Je n’arrive pas à comprendre ce qu’ils signifient.
Retournez dans iaito. Le premier bloc de mémoire est chargé depuis x22, et x22 est chargé depuis l’adresse var_48h. afv nous indique qu’elle se trouve à 0x48 depuis sp :
[0x00000e70]> afv
arg signed int argc @ x0
arg char ** s @ x1
var int64_t var_50h @ sp+0x0
var int64_t var_50h_2 @ sp+0x8
var int64_t var_10h @ sp+0x10
var int64_t var_10h_2 @ sp+0x18
var int64_t var_20h @ sp+0x20
var int64_t var_20h_2 @ sp+0x28
var void * buf @ sp+0x38
var int64_t var_3ch @ sp+0x3c
var int64_t var_3eh @ sp+0x3e
var int64_t var_3fh @ sp+0x3f
var int64_t var_40h @ sp+0x40
var int64_t var_48h @ sp+0x48
Redémarrez GDB et sautez le prologue de la fonction après l’invocation de main :
(gdb) break *main
Breakpoint 1 at 0xaaaaaaaa0c44
(gdb) run
Starting program: /home/user/challenge ./example.cimg
[Thread debugging using libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1].
Breakpoint 1, 0x0000aaaaaaaa0c44 in main ()
(gdb) si
0x0000aaaaaaaa0c48 in main ()
(gdb) si
0x0000aaaaaaaa0c4c in main ()
Vérifiez la valeur du pointeur de pile et définissez un watchpoint :
(gdb) info registers sp
sp 0xfffffffff290 0xfffffffff290
(gdb) watch *(long long *)0xfffffffff2d8
Hardware watchpoint 2: *(long long *)0xfffffffff2d8
Continuez :
(gdb) continue
Continuing.
Hardware watchpoint 2: *(long long *)0xfffffffff2d8
Old value = 281474840286680
New value = 0
0x0000aaaaaaaa0c5c in main ()
Localisez cette adresse dans iaito. Nous pouvons voir qu’à 0x00000c58 (l’instruction précédente à 0x00000c5c), var_48h est remise à zéro par un str.
Poursuivez pour découvrir où la variable est définie :
(gdb) continue
Continuing.
Hardware watchpoint 2: *(long long *)0xfffffffff2d8
Old value = 0
New value = 187649984565920
0x0000aaaaaaaa1358 in initialize_framebuffer ()
Nous voyons apparaître une nouvelle fonction que nous n’avons pas encore explorée : initialize_framebuffer.
Dans iaito, allez à cette fonction :

Nous pouvons voir qu’une adresse mémoire allouée par malloc est stockée dans la variable par l’instruction à 0x00001354. Cependant, elle est représentée par [x22, 0x10]. Vérifions le contenu de x22 :
(gdb) info registers $x22
x22 0xfffffffff2c8 281474976707272
0xfffffffff2c8 + 0x10 = 0xfffffffff2d8. C’est exactement l’adresse de var_48h.
De plus, la taille fournie à malloc est intéressante. Elle est calculée en multipliant les valeurs aux offsets 6 et 7, puis en multipliant le résultat par w1, qui vaut 0x18 (24), puis en ajoutant x0, qui vaut 1.
Vérifions aussi les valeurs de ces offsets :
(gdb) x/1xb 0xfffffffff2ce
0xfffffffff2ce: 0x0a
(gdb) x/1xb 0xfffffffff2cf
0xfffffffff2cf: 0x0a
Toutes deux valent 10. Elles semblent correspondre à la largeur et à la hauteur de l’image que nous avons spécifiées. Vérifions cela en plaçant des watchpoints matériels. Comme AArch64 exige un alignement, plaçons le watchpoint matériel à 0xfffffffff2cc :
(gdb) watch *(unsigned int *)0xfffffffff2cc
Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc
(gdb) run
Starting program: /home/user/challenge ./example.cimg
Ignorez les déclenchements de la GNU C Library :
Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc
Old value = 0
New value = 65535
0x0000fffff7fccd08 in ?? () from /lib/ld-linux-aarch64.so.1
(gdb) continue
Continuing.
Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc
Old value = 65535
New value = 0
0x0000fffff7fd5668 in ?? () from /lib/ld-linux-aarch64.so.1
(gdb) continue
Continuing.
[Thread debugging using libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1"]
Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc
Old value = 0
New value = 65535
0x0000fffff7fcc3a4 in ?? () from /lib/ld-linux-aarch64.so.1
(gdb) continue
Continuing.
Ici, nous voyons que la mémoire est remise à zéro à 0x00000c54 :
Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc
Old value = 65535
New value = 0
0x0000aaaaaaaa0c58 in main ()
Nous voyons un stp xzr, xzr, [buf]. D’après l’afv ci-dessus, nous savons que buf se trouve à 0x38 du pointeur de pile.
À 0x00000cb8, nous pouvons voir un ldr depuis buf, puis une vérification du nombre magique.
À 0x00000c9c, nous pouvons voir quelque chose d’intéressant :
0x00000c9c add x21, sp, 0x38
0x00000ca0 mov x2, 8 ; size_t nbyte
0x00000ca4 mov x1, x21 ; void *buf
0x00000ca8 mov w0, 0
0x00000cac bl sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
; ssize_t read(0, 0x0000000000000000, 0x00000000)
Nous avons donc confirmé que les deux valeurs à ces offsets correspondent à la largeur et à la hauteur.
Retournez à la fonction initialize_framebuffer dans iaito et GDB. Placez un watchpoint et inspectez la mémoire :
(gdb) break *0xaaaaaaaa1354
Breakpoint 1 at 0xaaaaaaaa1354
(gdb) run
Starting program: /home/user/challenge ./example.cimg
[Thread debugging using libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1].
Breakpoint 1, 0x0000aaaaaaaa1354 in initialize_framebuffer ()
(gdb) info registers x0
x0 0xaaaaaaac12a0 187649984565920
(gdb) watch *(char *)0xaaaaaaac12a0
Hardware watchpoint 2: *(char *)0xaaaaaaac12a0
(gdb) continue
Continuing.
Hardware watchpoint 2: *(char *)0xaaaaaaac12a0
Old value = 0 '\000'
New value = 27 '\033'
0x0000aaaaaaaa13bc in initialize_framebuffer ()
(gdb) x/24xb 0xaaaaaaac12a0
0xaaaaaaac12a0: 0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b 0x32
0xaaaaaaac12a8: 0x35 0x35 0x3b 0x32 0x35 0x35 0x3b 0x32
0xaaaaaaac12b0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Comparez avec les données ci-dessus :
(gdb) x/24xb $x0
0xaaaaaaac12a0: 0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b 0x30
0xaaaaaaac12a8: 0x38 0x30 0x3b 0x30 0x38 0x30 0x3b 0x30
0xaaaaaaac12b0: 0x38 0x30 0x6d 0x50 0x1b 0x5b 0x30 0x6d
0xaaaaaaac12a7 est le premier octet qui diffère. Nous pouvons placer un watchpoint dessus :
(gdb) watch *(char *)0xaaaaaaac12a7
Hardware watchpoint 3: *(char *)0xaaaaaaac12a7
(gdb) continue
Continuing.
Hardware watchpoint 3: *(char *)0xaaaaaaac12a7
Old value = 50 '2'
New value = 48 '0'
0x0000aaaaaaaa128c in display ()
Une nouvelle fonction à explorer apparaît. Ouvrez-la dans iaito.

Nous pouvons voir que la modification des données a lieu à 0x00001288, dans une instruction stp. L’instruction str à 0x0000128c est également intéressante ; inspectons la mémoire ici :
(gdb) x/24xb 0xaaaaaaac12a0
0xaaaaaaac12a0: 0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b 0x30
0xaaaaaaac12a8: 0x38 0x30 0x3b 0x30 0x38 0x30 0x3b 0x30
0xaaaaaaac12b0: 0x35 0x35 0x6d 0x20 0x1b 0x5b 0x30 0x6d
(gdb) si
0x0000aaaaaaaa1290 in display ()
(gdb) x/24xb 0xaaaaaaac12a0
0xaaaaaaac12a0: 0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b 0x30
0xaaaaaaac12a8: 0x38 0x30 0x3b 0x30 0x38 0x30 0x3b 0x30
0xaaaaaaac12b0: 0x38 0x30 0x6d 0x50 0x1b 0x5b 0x30 0x6d
Le bloc de 8 octets à partir de 0xaaaaaaac12b0 a été modifié par l’instruction str. Le registre raconte l’histoire :
(gdb) info registers x2
x2 0xaaaaaaac12a0 187649984565920
Nous n’avons pas besoin de nous attarder sur x2. Concentrons-nous sur x4, x5 et x1. À 0x00001268 et 0x00001270, nous voyons un registre très important, x25. Il contient l’adresse de la mémoire qui contient les données écrites dans la mémoire pointée par x2. Inspectons cette mémoire :
(gdb) x/24xb $x25
0xfffffffff270: 0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b 0x30
0xfffffffff278: 0x38 0x30 0x3b 0x30 0x38 0x30 0x3b 0x30
0xfffffffff280: 0x38 0x30 0x6d 0x50 0x1b 0x5b 0x30 0x6d
À 0x00001238, l’adresse contenue dans x25 est copiée dans x0. Ensuite, plusieurs données sont copiées dans les registres suivants. Puis un appel à snprintf est effectué. snprintf écrit une sortie formatée dans un tableau de caractères :
int snprintf(char *str, size_t size, const char *format, ...);
Il écrit donc une chaîne formatée dans la mémoire pointée par x25. Redémarrons GDB et inspectons la mémoire pointée par x2 :
(gdb) break *0xaaaaaaaa1258
Breakpoint 1 at 0xaaaaaaaa1258
(gdb) run
Starting program: /home/user/challenge ./example.cimg
[Thread debugging using libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1].
Breakpoint 1, 0x0000aaaaaaaa1258 in display ()
(gdb) x/s $x2
0xaaaaaaaa1510: "\033[38;2;%03d;%03d;%03dm%c\033[0m"
(gdb) x/29xb $x2
0xaaaaaaaa1510: 0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b 0x25
0xaaaaaaaa1518: 0x30 0x33 0x64 0x3b 0x25 0x30 0x33 0x64
0xaaaaaaaa1520: 0x3b 0x25 0x30 0x33 0x64 0x6d 0x25 0x63
0xaaaaaaaa1528: 0x1b 0x5b 0x30 0x6d 0x00
C’est la chaîne de format. Si vous n’êtes pas à l’aise avec la chaîne renvoyée par x/s, vous pouvez utiliser Python pour afficher la chaîne octet par octet :
>>> print(b"\x1b\x5b\x33\x38\x3b\x32\x3b\x25\x30\x33\x64\x3b\x25\x30\x33\x64\x3\
b\x25\x30\x33\x64\x6d\x25\x63\x1b\x5b\x30\x6d\x00")
b'\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m\x00'
>>> blob = b'\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m\x00'
>>> for i1 in range(len(blob)):
... blob[i1:i1+1]
...
b'\x1b'
b'['
b'3'
b'8'
b';'
b'2'
b';'
b'%'
b'0'
b'3'
b'd'
b';'
b'%'
b'0'
b'3'
b'd'
b';'
b'%'
b'0'
b'3'
b'd'
b'm'
b'%'
b'c'
b'\x1b'
b'['
b'0'
b'm'
b'\x00'
Cette chaîne de format possède quatre espaces réservés. Les trois premiers sont %03d, qui formatent un entier en décimal avec au moins trois chiffres (par exemple, 7 devient 007, et 15 devient 015). Le dernier est %c, qui ne correspond qu’à un seul caractère.
Nous avons maintenant le motif d’octets suivant :
0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b [N1]
[N1] [N1] 0x3b [N2] [N2] [N2] 0x3b [N3]
[N3] [N3] 0x6d [CH] 0x1b 0x5b 0x30 0x6d
Nous pouvons maintenant inspecter ce qui remplit les espaces réservés :
(gdb) info registers x3 x4 x5 x6
x3 0x50 80
x4 0x50 80
x5 0x50 80
x6 0x50 80
Tous valent 0x50, ce qui semble correspondre aux données que nous utilisons dans l’exemple de cIMG. Recréons maintenant le cIMG pour clarifier cela :
>>> with open("example.cimg", "wb") as file:
... file.write(b"cIMG\x02\0\x0a\x0a\x12\x34\x56\x78")
... file.write(b"\x50"*(10*10*4-4))
...
12
396
(gdb) break *0xaaaaaaaa1258
Breakpoint 1 at 0xaaaaaaaa1258
(gdb) run
Starting program: /home/user/challenge ./example.cimg
[Thread debugging using libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1].
Breakpoint 1, 0x0000aaaaaaaa1258 in display ()
(gdb) info registers x3 x4 x5 x6
x3 0x12 18
x4 0x34 52
x5 0x56 86
x6 0x78 120
Ainsi, les N1, N2 et N3 du motif hexadécimal ci-dessus correspondent respectivement à R, G et B, et CH est simplement le caractère que nous cherchons à remplir.
Nous pouvons maintenant revenir à GDB, placer un point d’arrêt avant chaque memcmp, exécuter x/24xb $x1 pour découvrir les framebuffers attendus, puis utiliser ces framebuffers pour retrouver les données cIMG attendues. Il ne reste plus qu’à remplir les données à l’aide d’un éditeur hexadécimal.
Vérification de la taille
Cependant, après avoir rempli les données attendues, l’exécutable ne nous donne toujours pas le drapeau. Après avoir utilisé GDB pour inspecter la mémoire, nous pouvons constater que les memcmp renvoient bien zéro comme prévu. Il doit donc exister d’autres conditions qui ne sont pas satisfaites.
Retournez dans iaito et remontez depuis le premier memcmp. Nous pouvons voir un cmp à 0x00000d60 qui compare var_40h avec 4. À 0x00001348 dans initialize_framebuffer, nous pouvons trouver un str qui stocke le produit de la largeur et de la hauteur.
Le cmp à 0x00000d60 détermine alors la valeur de w0. Si var_40h vaut 4, w0 devient 1 ; sinon, il devient 0.
À 0x00000d78, w0 est comparé à 0, ce qui affecte le comportement de l’instruction ccmp à 0x00000d84. Le contenu de [x22, 0x13] est chargé dans le registre w0, et [x1, 0x13] est chargé dans w2. D’après memcmp, nous pouvons facilement déduire que x22 est l’adresse réelle du framebuffer, et que x1 est l’adresse du framebuffer attendu. Puisque nous avons déjà fait en sorte que les données soient identiques à celles attendues, et que l’offset 19 est un caractère ASCII, w0 et w2 devraient être identiques et non nuls.
Nous pouvons en conclure que si var_40h vaut 4, w2 sera comparé à w0, ce qui fixera w19 à 1. Sinon, w2 sera comparé à 0, ce qui mettra w19 à zéro.
Après le premier memcmp, w19 sera comparé à 0 puisque w0 vaut zéro à 0x00000e7c. Ensuite, cset fixera w19 à 1 si différent, sinon à 0.
Modifiez maintenant le cIMG à l’aide d’un éditeur hexadécimal, en changeant la hauteur et la largeur pour des valeurs dont le produit vaut 4. Par exemple, nous pouvons utiliser 0202. L’exécutable devrait maintenant nous donner le drapeau.
Conclusion
La procédure ci-dessus comprend :
- une analyse statique avec radare2 et iaito ;
-
une analyse dynamique avec GDB comprenant :
-
l’altération du flot de contrôle ;
- l’inspection de la mémoire ;
- l’inspection des entiers.
C’est une procédure très classique en rétro-ingénierie.
Au passage, il existe peut-être d’autres approches pour rétroconcevoir cet exécutable. Cependant, quelle que soit l’approche utilisée, elle implique généralement à la fois une analyse statique et une analyse dynamique.
















































