Wie so oft werden wir auch bei dieser Challenge (crack1) einfach nur mit den Worten
“get the password”
begrüßt. Dieses Passwort sollen wir aus einer Binärdatei extrahieren.
Sehen wir uns also als Erstes den Typ des Binarys an:
rup0rt@lambda:~$ file crack1
crack1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, BuildID[sha1]=0x4579db2a2ebe3b4e83948f7a650dc3fced1d812d, not stripped
Es handelt sich um ein einfaches Linux-32bit-Programm.
Wir führen das Programm zunächst einmal aus um uns einen Überblick über dessen Funktionsweise zu verschaffen:
creeq@linux:~/ATAST2012$ ./crack1 Password Required : 12345 Wrong !!!! better luck next time :D
Es wird eine Passwortabfrage durchgeführt, die uns anschließend, bei Eingabe von “12345”, mit “falsch” beantwortet wird. Unser Ziel ist es daher, den Programmcode zu untersuchen um festzustellen, wie genau das Passwort überprüft wird und so an die korrekte Lösung zu gelangen.
Dafür nutzen wir “objdump” um an den Assembler-Code des Binarys zu gelangen. Dabei fallen folgende Inhalte in der <main>-Funktion auf:
804865f: mov eax,DWORD PTR [esp+0x2c] 8048663: mov edx,DWORD PTR [esp+0x2c] 8048667: movzx edx,BYTE PTR [esp+edx*1+0x32] 804866c: xor edx,0x21 804866f: mov BYTE PTR [esp+eax*1+0x68],dl 8048673: mov eax,DWORD PTR [esp+0x2c] 8048677: mov DWORD PTR [esp+0x8],eax 804867b: lea eax,[esp+0x68] 804867f: mov DWORD PTR [esp+0x4],eax 8048683: lea eax,[esp+0x54] 8048687: mov DWORD PTR [esp],eax 804868a: call 80484d0 <strncmp@plt> 804868f: test eax,eax 8048691: jne 804869a <main+0x106> 8048693: add DWORD PTR [esp+0x2c],0x1 8048698: jmp 80486ad <main+0x119>
In Zeile 4 wird eine XOR-Operation auf einen statischen Byte-Wert angewendet. Dies ist für gewöhnlich nur dann der Fall, wenn Daten im Programm versteckt wurden. Anschließend (Zeile 13) wird die “strcmp”-Funktion ausgeführt um den mit XOR erhaltenen Wert, mit den von uns eingegebenen Passwort-Buchstaben zu vergleichen. Zuletzt (Zeile 15) wird geprüft, ob die Zeichen überein stimmen und bei Erfolg der Buchstaben-Zähler (Zeile 17) erhöht.
Zusammengefasst wird also das von uns eingegebene Passwort mit einer im Programm per XOR verstecken Zeichenkette verglichen. An diesen String müssen wir gelangen! Dem Assembler-Code zur Folge befindet sich die gesuchte Zeichenkette an Position “esp+edx*1+0x32” (Zeile 3). Da “edx” der Schleifenzähler ist (und bei Null beginnt) suchen wir nur die Position “esp+0x32“. Die entsprechende Zuordnung finden wir hier ebenfalls im Assembler-Code:
80485b4: mov DWORD PTR [esp+0x32],0x48534e59 80485bc: mov DWORD PTR [esp+0x36],0x52404452 80485c4: mov DWORD PTR [esp+0x3a],0xbdbfef58 80485cc: mov DWORD PTR [esp+0x3e],0xefbdbfef 80485d4: mov DWORD PTR [esp+0x42],0xbfefbdbf 80485dc: mov DWORD PTR [esp+0x46],0xbfc256bd 80485e4: mov DWORD PTR [esp+0x4a],0xbdbfef39 80485ec: mov DWORD PTR [esp+0x4e],0xbdbfef24 80485f4: mov WORD PTR [esp+0x52],0x78
Die Ersteller der Challenge haben hier Daten statisch in das Binary codiert, was zu Beginn der Betrachtung schon auffällig war. Dem Anschein nach befinden sich hier jedoch mehr Daten als nur das Passwort. Daher sollte vorher geprüft werden, wie lang das Passwort ist, das heißt, wann die XOR-Schleife also regulär verlassen wird. Diese Information finden wir, wenn wir dem Sprung von Speicheradresse 8048698 (Zeile 18) folgen.
80486ad: mov ebx,DWORD PTR [esp+0x2c] 80486b1: lea eax,[esp+0x54] 80486b5: mov DWORD PTR [esp],eax 80486b8: call 8048490 <strlen@plt> 80486bd: cmp ebx,eax 80486bf: jb 804865f <main+0xcb> 80486c1: cmp DWORD PTR [esp+0x2c],0xa 80486c6: jne 80486d4 <main+0x140>
Zunächst wird in Zeile 4, erneut mittels “strlen” geprüft, ob die Länge unserer Zeichenkette mit der Anzahl per XOR durchlaufener Zeichen überein stimmt. Wenn weitere Zeichen per XOR zu bearbeiten sind, dann wird an den Beginn der XOR-Schleife zurück gesprungen (Zeile 6). Das Interessante ist hier jedoch Zeile 8, in der die Zählvariable (esp+0x2c) mit 0xa (10) verglichen wird. Bei Misserfolg wird das Programm beendet! Das bedeutet, dass das gesuchte Passwort 10 Zeichen (inklusive Zeilenumbruch bzw. bereits inkrementierter Zählvariable) lang sein muss.
Wir entnehmen also 9 Zeichen in Network-Byte-Order aus dem Assembler-Code und erstellen ein kleines C-Programm, dass die XOR-Operationen darauf vornimmt:
#include <stdio.h> #include <string.h> char crypt[9] = "\x59\x4e\x53\x48\x52\x44\x40\x52\x58"; int i; int main() { for (i=0;i<strlen(crypt);i++) { printf("%c", crypt[i] ^ 0x21); } printf("\n"); }
Nach Kompilierung und Ausführung erhalten wir folgendes Ergebnis:
rup0rt@lambda:~/ATAST2012$ gcc chall2.c -o chall2
rup0rt@lambda:~/ATAST2012$ ./chall2
xoriseasy
Die Lösung der Challenge lautet somit “xoriseasy“.