Ein Reverse-Engineering-Beispiel mit GDB und Iaito/Radare2
2026-05-27T23:56:34+08:00
In den letzten Wochen habe ich Reverse Engineering gelernt und einen Blogbeitrag geschrieben, um meine allererste Erfahrung zu teilen. Inzwischen arbeite ich mich etwas weiter vor, löse eine CTF-Herausforderung mit einer Kombination aus statischer Analyse und dynamischer Analyse, und ich fand, dass sich das hervorragend eignet, um Reverse Engineering noch etwas weiter zu erklären.
Ich hatte zuvor vor, einen Artikel über dynamische Analyse mit GDB zu schreiben. Allerdings stellte ich fest, dass diese Herausforderung ebenfalls ein sehr gutes Beispiel ist, um GDB zu erklären. Um diese Aufgabe zu lösen, werde ich ausserdem noch ein weiteres Werkzeug verwenden, iaito, die grafische Oberfläche von radare2.
Die Herausforderung
cIMG ist gewissermassen ein Bildformat, das dazu dient, ein Bild aus Zeichen in unterschiedlichen Farben im Terminal darzustellen. Es besteht aus:
- der magischen Zahl, nämlich
cIMG; - der Versionsnummer;
- Breite und Höhe;
- den Daten, wobei jedes Pixel R, G, B und das ASCII-Zeichen enthält.
Bei dieser Herausforderung geht es darum, einem ausführbaren Programm eine cIMG-Datei zu übergeben, das die Flag nur dann ausgibt, wenn die cIMG bestimmte Bedingungen erfüllt.
Der Quelltext
Den Quelltext der Herausforderung erhalten Sie hier. Dieser Quelltext scheint unter der BSD-2-Klausel-Lizenz zu stehen.
Kompilieren Sie ihn mit GCC:
```id="ed5rc5" $ gcc -O3 challenge.c -o challenge
Öffnen Sie Python und erstellen Sie eine Beispiel-cIMG-Datei:
```id="lk61ho"
$ 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
Verwenden Sie das Binärprogramm der Herausforderung, um die Beispiel-cIMG-Datei zu öffnen:
```id="rpl7sh" user@learnaarch64asm:~$ ./challenge ./example.cimg PPPPPPPPPP PPPPPPPPPP PPPPPPPPPP PPPPPPPPPP PPPPPPPPPP PPPPPPPPPP PPPPPPPPPP PPPPPPPPPP PPPPPPPPPP PPPPPPPPPP
Diese Datei gibt etwas aus, aber nicht die Flag.
## Statische Analyse
Wir müssen eine cIMG-Datei erstellen, die *die Flag ausgibt*. Lassen Sie uns zunächst eine statische Analyse durchführen.
Öffnen Sie die Datei in iaito. Führen Sie eine `aaa`-Analyse aus und öffnen Sie den Graphen der Funktion `main`. Sie erhalten:

Wir finden das Einlesen des Dateikopfs bei `0x00000cac`, die Prüfung der magischen Zahl bei `0x00000cc4`, die Versionsprüfung bei `0x00000cd0` sowie das Einlesen von Breite und Höhe bei `0x00000ce0`. Danach wird `malloc` verwendet, um Speicher für die Bilddaten zu reservieren, und die Daten werden in diesen Speicher eingelesen.
Die magische Zahl ist `cIMG`, was in Hex `63 49 4d 47` entspricht; die Version ist eine 32-Bit-Little-Endian-Ganzzahl; sowohl Höhe als auch Breite sind 8-Bit-Ganzzahlen.
## Bedingung für die Flag
Gehen wir zum Ende der Funktion. Offensichtlich ist `sym.win` die Funktion, die uns die Flag gibt. Sie wird bei `0x00000e8c` aufgerufen, und dieser Aufruf erfolgt nach einer Prüfung bei `0x00000e58`. Diese Prüfung verlangt, dass `w19` nicht null ist.
## Manipulation des Kontrollflusses
Es ist Zeit, GDB zu starten.
```id="sl4pj9"
$ 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)
Setzen Sie einen Breakpoint auf die Funktion main und starten Sie das Programm:
```id="srt51i" (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 ()
Wir merken uns, dass GDBs Datenadressen einen Versatz von `0xaaaaaaaa0000` haben.
Setzen Sie einen Breakpoint direkt vor der Prüfung der Flag-Bedingung und fahren Sie fort:
```id="upco7m"
(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 ()
Ändern Sie den Inhalt des Registers w19 und fahren Sie fort:
```id="024n2e" (gdb) set $w19 = 1 (gdb) continue
Dann wird das ausführbare Programm uns die Flag geben. Damit ist die Aufgabe beendet, und haben Sie einen schönen Tag :-p
Nur ein Scherz. Wenn wir die Herausforderung tatsächlich so abschliessen würden, wäre der Reiz von RE weg. Gehen wir tiefer hinein.
## Datenprüfung
Gehen Sie zurück zu iaito. Klicken Sie im Graphen auf `w19`. Dadurch wird dieses Register in allen Instruktionen des Graphen hervorgehoben. Scrollen Sie im Graphen zurück, um einen Hinweis zu finden.
Wir können einige Aufrufe von `sym.imp.memcmp` im Graphen sehen.
`memcmp` ist eine Standardfunktion der C-Bibliothek, die in `<string.h>` deklariert ist und zwei Speicherblöcke byteweise vergleicht:
```id="5yb0vi"
int memcmp(const void *ptr1, const void *ptr2, size_t n);
ptr1 und ptr2 zeigen jeweils auf die beiden Blöcke, und n gibt die Anzahl der zu vergleichenden Bytes an. Wenn die beiden Blöcke identisch sind, gibt die Funktion 0 zurück; andernfalls wird eine von null verschiedene Zahl zurückgegeben.
Es gibt vier memcmp-Aufrufe. Auf jeden Aufruf folgt cmp w0, 0. Ausser beim ersten Aufruf gibt es ausserdem nach cmp noch cset w0, eq und and w19, w19, w0. Daraus können wir schliessen, dass w19 gelöscht wird, wenn memcmp einen Unterschied findet, da w0 dann null sein wird. Deshalb vermuten wir, dass wir die vier Paare von Speicherblöcken gleich machen müssen.
x0 und x1 sind die beiden zu vergleichenden Adressen. x2 enthält die Grösse; mov x2, 0x18 sagt uns, dass die Grösse 24 Bytes beträgt.
Öffnen Sie GDB erneut und untersuchen Sie den Speicher vor dem ersten memcmp:
```id="bzfvyb" (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
Zufällige Bytes. Ich kann nicht herausfinden, was sie bedeuten.
Gehen Sie zurück zu iaito. Der erste Speicherblock wird aus `x22` geladen, und `x22` wird aus der Adresse `var_48h` geladen. `afv` zeigt uns, dass sie `0x48` von `sp` entfernt liegt:
```id="y67rzq"
[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
Starten Sie GDB neu und überspringen Sie den Funktionsprolog, nachdem main aufgerufen wurde:
```id="0se5x1" (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 ()
Prüfen Sie den Wert des Stackpointers und setzen Sie einen Watchpoint:
```id="xkqjjg"
(gdb) info registers sp
sp 0xfffffffff290 0xfffffffff290
(gdb) watch *(long long *)0xfffffffff2d8
Hardware watchpoint 2: *(long long *)0xfffffffff2d8
Fahren Sie fort:
```id="qle3tg" (gdb) continue Continuing.
Hardware watchpoint 2: (long long )0xfffffffff2d8
Old value = 281474840286680 New value = 0 0x0000aaaaaaaa0c5c in main ()
Lokalisieren Sie diese Adresse in iaito. Wir können sehen, dass bei `0x00000c58` (der vorherigen Instruktion zu `0x00000c5c`) `var_48h` durch ein `str` auf null gesetzt wird.
Fahren Sie fort, um herauszufinden, wo die Variable gesetzt wird:
```id="uzul15"
(gdb) continue
Continuing.
Hardware watchpoint 2: *(long long *)0xfffffffff2d8
Old value = 0
New value = 187649984565920
0x0000aaaaaaaa1358 in initialize_framebuffer ()
Wir sehen eine neue Funktion, die wir bisher noch nicht untersucht haben: initialize_framebuffer.
Gehen Sie in iaito zu dieser Funktion:

Wir können sehen, dass eine von malloc reservierte Speicheradresse durch die Instruktion bei 0x00001354 in der Variable gespeichert wird. Sie wird jedoch als [x22, 0x10] dargestellt. Prüfen wir den Inhalt von x22:
```id="2rkm7e" (gdb) info registers $x22 x22 0xfffffffff2c8 281474976707272
`0xfffffffff2c8 + 0x10 = 0xfffffffff2d8`. Genau die Adresse von `var_48h`.
Ausserdem ist die an `malloc` übergebene Grösse interessant. Sie wird berechnet, indem die Werte an den Offsets 6 und 7 miteinander multipliziert werden, dann das Ergebnis mit `w1` multipliziert wird, welches `0x18` (24) ist, und dann `x0` addiert wird, welches 1 ist.
Lassen Sie uns diese Werte an den Offsets ebenfalls untersuchen:
```id="kot0zb"
(gdb) x/1xb 0xfffffffff2ce
0xfffffffff2ce: 0x0a
(gdb) x/1xb 0xfffffffff2cf
0xfffffffff2cf: 0x0a
Beide sind 10. Sie scheinen die von uns angegebene Bildbreite und Bildhöhe zu sein. Prüfen wir das mit Hardware-Watchpoints. Da AArch64 eine Ausrichtung verlangt, setzen wir den Hardware-Watchpoint bei 0xfffffffff2cc:
```id="man8re" (gdb) watch (unsigned int )0xfffffffff2cc Hardware watchpoint 1: (unsigned int )0xfffffffff2cc (gdb) run Starting program: /home/user/challenge ./example.cimg
Überspringen Sie die Auslöser der GNU C Library:
```id="3kxuft"
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.
Hier sehen wir, dass der Speicher bei 0x00000c54 auf null gesetzt wird:
```id="7lsdni" Hardware watchpoint 1: (unsigned int )0xfffffffff2cc
Old value = 65535 New value = 0 0x0000aaaaaaaa0c58 in main ()
Wir sehen ein `stp xzr, xzr, [buf]`. Aus dem obigen `afv` wissen wir, dass `buf` `0x38` vom Stackpointer entfernt liegt.
Bei `0x00000cb8` finden wir ein `ldr` aus `buf`, gefolgt von einer Prüfung der magischen Zahl.
Bei `0x00000c9c` finden wir etwas Interessantes:
```id="dephcs"
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)
Damit haben wir bestätigt, dass die beiden Werte an diesen Offsets Breite und Höhe sind.
Gehen Sie in iaito und GDB zurück zur Funktion initialize_framebuffer. Setzen Sie einen Watchpoint und untersuchen Sie den Speicher:
```id="751rxy" (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
Vergleichen Sie das mit den obigen Daten:
```id="qjv2ky"
(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 ist das erste Byte, das sich unterscheidet. Wir können dafür einen Watchpoint setzen:
```id="086nsv" (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 ()
Eine neue Funktion, die es zu untersuchen gilt. Öffnen Sie sie in iaito.

Wir können sehen, dass die Datenänderung bei `0x00001288` in einer `stp`-Instruktion stattfindet. Die `str`-Instruktion bei `0x0000128c` ist ebenfalls interessant; untersuchen wir hier den Speicher:
```id="cuc9oa"
(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
Der 8-Byte-Block ab 0xaaaaaaac12b0 wurde durch die str-Instruktion geändert. Das Register erzählt die Geschichte:
```id="w9u9cf" (gdb) info registers x2 x2 0xaaaaaaac12a0 187649984565920
Wir müssen uns um `x2` nicht zu sehr kümmern. Konzentrieren wir uns auf `x4`, `x5` und `x1`. Bei `0x00001268` und `0x00001270` sehen wir ein sehr wichtiges Register, `x25`. Es enthält die Adresse des Speichers, der die Daten enthält, die in den durch `x2` adressierten Speicher geschrieben werden. Untersuchen wir diesen Speicher:
```id="lpbj45"
(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
Bei 0x00001238 wird die Adresse in x25 nach x0 kopiert. Danach werden die Datenstücke in die folgenden Register kopiert. Dann erfolgt ein Aufruf von snprintf. snprintf schreibt formatierten Output in ein Zeichenfeld:
```id="onjzdn" int snprintf(char str, size_t size, const char format, ...);
Es schreibt also eine formatierte Zeichenkette in den Speicher bei `x25`. Starten wir GDB neu und untersuchen den von `x2` adressierten Speicher:
```id="5694u3"
(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
Das ist die Formatzeichenkette. Wenn Sie mit der von x/s zurückgegebenen Zeichenkette nicht zufrieden sind, können Sie Python verwenden, um die Zeichenkette Byte für Byte auszugeben:
```id="9hlb7o"
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'
Diese Formatzeichenkette hat vier Platzhalter. Die ersten drei sind `%03d`, die eine Ganzzahl als Dezimalzahl mit mindestens drei Ziffern formatieren (zum Beispiel wird aus 7 `007`, und aus 15 wird `015`). Der letzte ist `%c`, der nur ein einzelnes Zeichen akzeptiert.
Nun haben wir das folgende Byte-Muster:
```id="7599l4"
0x1b 0x5b 0x33 0x38 0x3b 0x32 0x3b [N1]
[N1] [N1] 0x3b [N2] [N2] [N2] 0x3b [N3]
[N3] [N3] 0x6d [CH] 0x1b 0x5b 0x30 0x6d
Wir können nun untersuchen, was die Platzhalter füllt:
```id="a6lcl7" (gdb) info registers x3 x4 x5 x6 x3 0x50 80 x4 0x50 80 x5 0x50 80 x6 0x50 80
Alle sind `0x50`, was offenbar die Daten sind, die wir in der Beispiel-cIMG verwenden. Erstellen wir die cIMG nun erneut, um das zu verdeutlichen:
```id="bntxni"
>>> 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
```id="jigwea" (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 ```
Damit entsprechen N1, N2 und N3 im obigen Hex-Muster R, G und B, und CH ist einfach das Zeichen, das wir einsetzen wollen.
Nun können wir zu GDB zurückkehren, vor jedem memcmp einen Breakpoint setzen, mit x/24xb $x1 die erwarteten Framebuffer ermitteln und daraus die erwarteten cIMG-Daten rekonstruieren. Anschliessend füllen wir die Daten mit einem Hex-Editor ein.
Grössenprüfung
Nach dem Einfügen der erwarteten Daten gibt das Programm jedoch immer noch nicht die Flag aus. Nachdem wir mit GDB den Speicher untersucht haben, sehen wir, dass die memcmp-Aufrufe erwartungsgemäss Null zurückgeben. Es muss also noch andere Bedingungen geben, die nicht erfüllt sind.
Gehen Sie in iaito zurück und scrollen Sie vom ersten memcmp aus rückwärts. Wir sehen ein cmp bei 0x00000d60, das var_40h mit 4 vergleicht. Bei 0x00001348 in initialize_framebuffer finden wir ein str, das das Produkt aus Breite und Höhe speichert.
Das cmp bei 0x00000d60 bestimmt dann den Wert von w0. Wenn var_40h gleich 4 ist, wird w0 zu 1; andernfalls wird es 0.
Bei 0x00000d78 wird w0 mit 0 verglichen, was das Verhalten der Instruktion ccmp bei 0x00000d84 beeinflusst. Der Inhalt von [x22, 0x13] wird in das Register w0 geladen, und [x1, 0x13] wird in w2 geladen. Aus memcmp können wir leicht erkennen, dass x22 die tatsächliche Adresse des Framebuffers ist und x1 die erwartete Adresse des Framebuffers. Da wir die Daten bereits so angepasst haben, dass sie mit den erwarteten Daten übereinstimmen, und der Offset 19 ein ASCII-Zeichen ist, sollten w0 und w2 gleich und ungleich null sein.
Wir können daraus schliessen, dass, wenn var_40h gleich 4 ist, w2 mit w0 verglichen wird, wodurch w19 auf 1 gesetzt wird. Andernfalls wird w2 mit 0 verglichen, wodurch w19 auf null gesetzt wird.
Nach dem ersten memcmp wird w19 mit 0 verglichen, da w0 bei 0x00000e7c gleich null ist. Dann setzt cset w19 auf 1, wenn ungleich, andernfalls auf 0.
Ändern Sie nun die cIMG mit einem Hex-Editor und setzen Sie Höhe und Breite auf Werte, deren Produkt 4 ergibt. Beispielsweise können wir 0202 verwenden. Nun sollte das ausführbare Programm die Flag ausgeben.
Fazit
Das obige Verfahren umfasst:
- statische Analyse mit radare2 und iaito;
-
dynamische Analyse mit GDB, einschliesslich:
-
Manipulation des Kontrollflusses;
- Speicherinspektion;
- Ganzzahlinspektion.
Das ist ein sehr typisches Vorgehen beim Reverse Engineering.
Übrigens gibt es möglicherweise auch andere Ansätze, um dieses ausführbare Programm zu rekonstruieren. Doch unabhängig davon, welcher Ansatz verwendet wird, umfasst er normalerweise sowohl statische Analyse als auch dynamische Analyse.
















































