ATAST CTF Quals 2012 –
Crack It!

Zu dieser Challenge gibt es keine weitere Aufgabenstellung, da der Name bereits selbsterklärend ist – “Crack It!”. Alles was wir erhalten, ist eine ZIP-Datei mit einem Binary.

rup0rt@lambda:~/ATAST2012$ file crackit
crackit: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, BuildID[sha1]=0x1eb85f9048b1d8dddfbc3898826aaddfe63a55ab, not stripped

… einem Linux-32bit-Programm um genau zu sein. Dieses führen wir, wie gewohnt, erst einmal aus um deren Funktionsweise überblicken zu können.

rup0rt@linux:~/ATAST2012$ ./crackit
Usage : ./crackit <password>

rup0rt@linux:~/ATAST2012$ ./crackit 12345
hahaha wrong password :p

Wie bei der vorherigen Challenge auch wird also ebenso ein Passwort benötigt um die Abfrage des Programmes passieren und somit die Lösung zur Aufgabe (Crackt It!) erhalten zu können. Erneut beginnen wir mit einer Betrachtung des Assembler-Codes durch Verwendung von “objdump”, wobei folgender Abschnitt direkt auffällt:

8048620: c7 44 24 0c 00 00 00  mov DWORD PTR [esp+0xc],0x0
8048628: c7 44 24 08 00 00 00  mov DWORD PTR [esp+0x8],0x0
8048630: c7 44 24 04 00 00 00  mov DWORD PTR [esp+0x4],0x0
8048638: c7 04 24 00 00 00 00  mov DWORD PTR [esp],0x0
804863f: e8 c4 fd ff ff        call 8048408 <ptrace@plt>
8048644: 85 c0                 test eax,eax
8048646: 79 16                 jns 804865e <main+0x6d>

Das Programm ruft die Funktion “ptrace()” auf (Zeile 5), prüft anschließend deren Rückgabewert (Zeile 6) und springt, davon abhängig, weiter in den Quellcode (Zeile 7). Mit “ptrace()” können Prozesse debuggt und gesteuert werden. Dies Funktioniert jedoch nur für einen Prozess. Sollten wir also bei Programmstart selbst einen Debugger verwenden, wird “ptrace()” fehlschlagen und das Programm beendet.

Hierbei handelt es sich hierbei also um eine Anti-Debugging-Methode, die verhindern soll, dass wir mit Programmen wie “strace” oder dem GDB das Programm bei der Ausführung beobachten. Um diesen Schutz zu umgehen verändern wir einfach den bedingten Sprung (Zeile 7) in einen unbedingten Sprung unter Verwendung eines Hex-Editors.

ATAST CTF 2012 - Crackt It! - Debugger protection

Dazu suchen wir in der Datei nach dem Opcodes 0x79 0x16 (JNS 16) (siehe Zeile 7), welche wir bei Position 0x646 im Binary finden. Das JNS (0x79) ersetzen wir nun mit dem Opcode für JMP (0xEB) und speichern die Datei wieder ab. Nun sollte es uns möglich sein, das Programm selbst unter Verwendung eines Debuggers zu starten.

Als Nächstes sehen wir uns den Assembler-Quellcode des Binarys weiter an:

804865e: b8 85 85 04 08        mov eax,0x8048585
8048663: 8b 00                 mov eax,DWORD PTR [eax]
8048665: 25 ff 00 00 00        and eax,0xff
804866a: 3d cc 00 00 00        cmp eax,0xcc
804866f: 75 0c                 jne 804867d <main+0x8c>
8048671: c7 04 24 01 00 00 00  mov DWORD PTR [esp],0x1
8048678: e8 fb fd ff ff        call 8048478 <exit@plt>

804867d: b8 85 85 04 08        mov eax,0x8048585
8048682: 83 c0 22              add eax,0x22
8048685: 8b 00                 mov eax,DWORD PTR [eax]
8048687: 25 ff 00 00 00        and eax,0xff
804868c: 3d cc 00 00 00        cmp eax,0xcc
8048691: 75 0c                 jne 804869f <main+0xae>
8048693: c7 04 24 01 00 00 00  mov DWORD PTR [esp],0x1
804869a: e8 d9 fd ff ff        call 8048478 <exit@plt>

804869f: b8 85 85 04 08        mov eax,0x8048585
80486a4: 83 c0 35              add eax,0x35
80486a7: 8b 00                 mov eax,DWORD PTR [eax]
80486a9: 25 ff 00 00 00        and eax,0xff
80486ae: 3d cc 00 00 00        cmp eax,0xcc
80486b3: 75 0c                 jne 80486c1 <main+0xd0>
80486b5: c7 04 24 01 00 00 00  mov DWORD PTR [esp],0x1
80486bc: e8 b7 fd ff ff        call 8048478 <exit@plt>

80486c1: b8 85 85 04 08        mov eax,0x8048585
80486c6: 83 c0 47              add eax,0x47
80486c9: 8b 00                 mov eax,DWORD PTR [eax]
80486cb: 25 ff 00 00 00        and eax,0xff
80486d0: 3d cc 00 00 00        cmp eax,0xcc
80486d5: 75 0c                 jne 80486e3 <main+0xf2>
80486d7: c7 04 24 01 00 00 00  mov DWORD PTR [esp],0x1
80486de: e8 95 fd ff ff        call 8048478 <exit@plt>

Nach der “ptrace()”-Funktion prüft das Programm an vier Stellen im Quellcode (ausgehend von 0x8048585) ob sich ein 0xcc (INT) an den entsprechenden Positionen befindet. INT (Interrupt) wird von Debuggern in Form von Breakpoints verwendet, um den Programmfluss zu unterbrechen und die Kontrolle so an den Debugger zurück zu geben.

Durch das Prüfen auf 0xcc versucht das Programm also festzustellen ob an bestimmten Schlüsselpositionen Haltepunkte von einem Debugger gesetzt wurden. Hierbei handelt es sich somit um eine weitere Anti-Debugging-Methode. Damit das Programm von uns vollkommen uneingeschränkt untersucht werden kann, werden wir auch diesen Schutzmechanismus entfernen.

Dazu werden wir den gesamten Block mit NOPs (No Operation = 0x90) ersetzen. Wir suchen also nach dem Beginn des Blockes 0xb8 0x85 0x85 (Zeile 1) und Ersetzen alle Zeichen bis zum Ende des Blockes 0xfd 0xff 0xff (Zeile 34) mit 0x90.

ATAST CTF 2012 - Crackt It! - Interrupt Check

Nun, nachdem alle Anti-Debugging-Mechanismen entfernt sind, kann das Programm, wie gewohnt mit dem GDB gestartet werden. Aus dem Programmfluss unterhalb unserer NOPs erkennen wir:

(gdb) disas main
   0x080486e3 <+242>:   mov    eax,DWORD PTR [ebp+0xc]
   0x080486e6 <+245>:   add    eax,0x4
   0x080486e9 <+248>:   mov    eax,DWORD PTR [eax]
   0x080486eb <+250>:   mov    DWORD PTR [esp],eax
   0x080486ee <+253>:   call   0x8048585 <pwd>
   0x080486f3 <+258>:   test   eax,eax
   0x080486f5 <+260>:   jne    0x8048705 <main+276>

Der Rückgabewert der <pwd>-Funktion ist entscheidend dafür, ob das Passwort akzeptiert wird, oder nicht. Die Berechnung und Überprüfung des Passwortes muss also innerhalb dieser Funktion erfolgen.

(gdb) disas pwd
   0x0804859a <+21>:    xor    eax,eax
   0x0804859c <+23>:    mov    eax,DWORD PTR [ebp-0x3c]
   0x0804859f <+26>:    mov    DWORD PTR [esp],eax
   0x080485a2 <+29>:    call   0x8048428 <strlen@plt>
   0x080485a7 <+34>:    cmp    eax,0xa
   0x080485aa <+37>:    jne    0x80485d9 <pwd+84>

   0x080485ac <+39>:    mov    DWORD PTR [esp+0x4],0x14
   0x080485b4 <+47>:    lea    eax,[ebp-0x34]
   0x080485b7 <+50>:    mov    DWORD PTR [esp],eax
   0x080485ba <+53>:    call   0x8048544 <GeneratePassword>

   0x080485bf <+58>:    lea    eax,[ebp-0x34]
   0x080485c2 <+61>:    mov    DWORD PTR [esp+0x4],eax
   0x080485c6 <+65>:    mov    eax,DWORD PTR [ebp-0x3c]
   0x080485c9 <+68>:    mov    DWORD PTR [esp],eax
   0x080485cc <+71>:    call   0x8048468 <strcmp@plt>

Zunächst ermittelt das Programm die Länge des von uns eingegebenen Passwortes mit Hilfe der Funktion “strlen()”. Nur wenn die Länge unseres Passwortes gleich 0xa (10) ist, wird der Programmfluss fortgesetzt. Wir wissen also, dass wir einen zehn Zeichen langen Parameter bei Aufruf übergeben müssen.

Anschließend wird die Funktion “GeneratePassword” aufgerufen. In dieser Funktion muss das eigentliche Passwort generiert werden um es darauf hin, mit der von uns eingegebenen Zeichenkette vergleichen zu können. Wir können nun genau untersuchen, wie das Passwort erstellt wird, oder einfach die Position finden, an der die beiden Strings miteinander verglichen werden – letzteres ist natürlich die schnellere Lösung ;-).

Direkt nach dem Aufruf von “GeneratePassword” wird nämlich die “strcmp()”-Funktion verwendet. Hier muss also der Vergleich des generierten Passwortes mit der von uns eingegebenen Zeichenkette erfolgen. Wir setzen daher einen Haltepunkt mit GDB vor den Aufruf der “strcmp()”-Funktion und starten das Programm mit einem zehn Zeichen langen String.

(gdb) b *0x080485cc
Breakpoint 1 at 0x80485cc

(gdb) r 1234567890
Starting program: /home/creeq/ATAST2012/crackit3 1234567890

Breakpoint 1, 0x080485cc in pwd ()

Das Programm stoppt und wir befinden uns unmittelbar vor dem Aufruf von “strcmp()”. An dieser Stelle müssten also die beiden Zeichenketten, die verglichen werden sollen, bereits oben auf dem Stack liegen. Dies sehen wir uns nun an:

(gdb) x/8x $esp
0xbffff530:     0xbffff7d0      0xbffff554      0xb7fefc16      0xb7fffac0
0xbffff540:     0xb7fe0b58      0x00000001      0x00000001      0xbffff7d0

(gdb) x/s 0xbffff7d0
0xbffff7d0:      "1234567890"

(gdb) x/s 0xbffff554
0xbffff554:      "zkhfspiyrw"

In den zwei Stack-Adressen finden wir zum einen die von uns eingegebene Zeichenkette “1234567890” und zum anderen tatsächlich das vom Programm erwartete Passwort!

Die Lösung lautet somit “zkhfspiyrw“.

Leave a Reply

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