Wie gewohnt ist Weniger auch diesmal Mehr und wir erhalten als Aufgabe (old) nur einen Kommentar, dass es sich um einen “scheinbaren binären Auftrag” handelt. Zusätzlich wird eine Datei zum Download bereit gestellt.
Die erste Betrachtung mit dem Werkzeug “file” um festzustellen, um was für einen Typ von Datei es sich in Wirklichkeit handelt, liefert:
rup0rt@lambda:~/FB2013/old$ file bin.bin
bin.bin: x86 boot sector, code offset 0x6e
Uns liegt also ein bootbares Image vor, das durch Übergabe an einen Emulator, wie QEMU gestartet werden könnte. Da die Datei allerdings nur 512 Bytes groß ist und das simple Starten per Emulator ohnehin nicht die Flagge liefern wird, sehen wir uns direkt etwas genauer an. Zunächst prüfen wir, welche Zeichenketten enthalten sind:
rup0rt@lambda:~/FB2013/old$ strings bin.bin Password: ,jNice, 16bits world is nice :D. Validate using that password :) No :(, try again.
Offensichtlich scheint eine Passwortabfrage implementiert zu sein, die abhängig von der korrekten Eingabe eine positive oder negative Rückmeldung erzeugt. Unser Ziel zur Lösung der Challenge wird es also sein, an genau dieses Passwort zu gelangen.
Um die Überprüfung bzw. Berechnung des Passwortes in Erfahrung zu bringen, bleibt nichts anderes übrig als noch tiefer in das Binary hinein zu sehen – vorzugsweise mit Hilfe einer disassemblierten Darstellung, die mit dem Werkzeug ndisasm erzeugt werden kann.
Nach ein paar Minuten, in denen sich ein Überblick über die Funktionalität verschafft wurde (und vorrangig nach Operationen wie XOR Ausschau gehalten wurde), können wir folgendes Erkennen:
00000099 BEBF7C mov si,0x7cbf 0000009C BFCF7C mov di,0x7ccf 0000009F EB0D jmp short 0xae 000000A1 8B02 mov ax,[bp+si] 000000A3 8B1B mov bx,[bp+di] 000000A5 31D8 xor ax,bx 000000A7 89E9 mov cx,bp 000000A9 D2C8 ror al,cl 000000AB 8802 mov [bp+si],al 000000AD 45 inc bp 000000AE 83FD07 cmp bp,byte +0x7 000000B1 72EE jc 0xa1 000000B3 C3 ret
Das Programm liest zwei Werte aus dem Speicher, der Stelle, an die der Boot-Sektor geladen wurde (Zeilen 1-2). Hierbei handelt es sich um 0x7cbf (die Stelle an die das eingegebene Passwort gespeichert wurde) und 0x7ccf (eine Zeichenkette, aus dem Binary).
Danach wird eine Schleife sieben Mal durchlaufen, wobei das Register “bp” als Zählvariable verwendet wird (Zeilen 3-11). Inerhalb der Schleife passiert für jedes Zeichen des eingegebenen Passwortes folgendes:
Das Zeichen wird per XOR mit der Zeichenkette aus dem Binary verknüpft (Zeile 6). Anschließend wird die Position des Zeichens (0-6) in das Register “cx” gespeichert (Zeile 7) und ein Rechtsschift unter Erhalt aller Bits (ROR) mit der Anzahl der Zeichenposition vorgenommen (Zeile 8). Die ROR-Operation bewirkt im Beispiel für die Zahl 5 (0x00000101) und ein Verschieben um zwei Bit nach rechts, die Zahl 65 (0x01000001).
Anschließend an diese Schleife führt das Programm noch folgenden Code aus:
00000024 BEBF7C mov si,0x7cbf 00000027 BFC77C mov di,0x7cc7 [...] 00000080 8B02 mov ax,[bp+si] 00000082 8B1B mov bx,[bp+di] 00000084 39D8 cmp ax,bx 00000086 750C jnz 0x94 00000088 83C501 add bp,byte +0x1 0000008B 83FD07 cmp bp,byte +0x7
Wieder werden zwei Werte gelesen, 0x7cbf (unser per XOR und ROR veränderte Passwort) sowie 0x7cc7 (eine weitere Speicherstelle aus dem Binary selbst). Erneut wird eine Schleife über sieben Zeichen durchlaufen und unser verändertes Passwort mit dem Wert aus dem Binary verglichen (Zeile 6).
Zusammengefasst tut das Programm also folgendes:
- Einlesen eines vom Benutzer einzugebenen Passwortes
- XOR mit einem statischen 7-Zeichen-String aus dem Binary
- ROR jedes Zeichens entsprechend seiner Position
- Vergleich der Ergebnisses mit einem weiteren 7-Zeichen-String aus dem Binary
Dieses Verhalten sollte sich relativ leicht in ein Skript überführen lassen. Vorher extrahieren wir jedoch die beiden statischen Zeichenketten aus dem Binary, indem wir deren Positionen (0x7ccf und 0x7cc7) direkt aus der Datei kopieren. Dazu müssen wir nur die Werte nur um die Basis 0x7c00 verringern und erhalten die Positionen 0xcf und 0xc7.
Die beiden Zeichenketten lauten also “0xBE 0x21 0x03 0x15 0x1F 0x2C 0x6A” und “0xD8 0xA6 0xCD 0x4E 0x04 0x0A 0x3C”. Alles zusammen ergibt nun folgendes Skript zur Berechnung des einzugebenen Passwortes:
#!/usr/bin/perl -w $one = "\xD8\xA6\xCD\x4E\x04\x0A\x3C"; $two = "\xBE\x21\x03\x15\x1F\x2C\x6A"; sub bin2dec { my $zahl = unpack("N", pack("B32", substr("0" x 32 . $_[0], -32))); return $zahl; } sub dec2bin { my $zahl = unpack("B32", pack("N", $_[0])); $zahl =~ s/^0+(\d+)/$1/; return $zahl; } $key = ""; for ($i=0; $i<length($one); $i++) { $a = ord(substr($one, $i, 1)); $b = ord(substr($two, $i, 1)); for ($p=20; $p<=126; $p++) { $res = ($p ^ $b); # reimplementation of ROR $bin = sprintf("%8d", dec2bin($res)); $bin = substr($bin, 8-$i, $i) . substr($bin, 0, 8-$i); $res = bin2dec($bin); if ($res == $a) { $key .= chr($p); } } } print "KEY: $key\n";
Die Ausführung des Skriptes liefert die Ausgabe:
rup0rt@lambda:~/FB2013/old$ ./old.pl
KEY: fl4g_me
Die Lösung lautet somit “fl4g_me“.