Diese Challenge (hypercomputer 1) erinnert nicht nur vom Namen her an die Supercomputer-Challenge aus dem vorjährigen plaidCTF 2012. Auch die Aufgabenstellung selbst weist bereits auf diese Challenge hin und vermerkt, dass es diesmal einfacher sein soll. Neben der Beschreiben erhalten wir die IP-Adresse eines SSH-Servers von dem wir uns dieses Binary kopieren.
Eine erste Untersuchung zeigt uns…
root@linux64:~/Plaid2013# file hypercomputer
hypercomputer: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x0b7c8d904831417f536c59b59fdecfc738136536, stripped
…, dass es sich um ein 64bit Linux-Binary handelt, das beim normalen, testweisen Ausführen diese Ausgabe liefert:
root@linux64:~/Plaid2013# ./hypercomputer ...Welcome to Hypercomputer!... ...This could take a very long time...
Es ist jedoch nicht so, dass dieser Text sofort erscheint – vielmehr dauert es mehrere Sekunden, was darauf schließen lässt, wie lange die gesamte Berechnung und Ausgabe der Flagge dauern könnte. Im letzten Jahr musste der Assembler-Code per Hand untersucht und optimiert werden, also sehen wir uns auch diesmal das Programm im Debugger an.
root@linux64:~/Plaid2013# gdb ./hypercomputer
GNU gdb (GDB) 7.4.1-debian
Reading symbols from /root/Plaid2013/hypercomputer...(no debugging symbols found)...done.
(gdb) run
Starting program: /root/Plaid2013/hypercomputer
...Welcome to Hypercomputer!...
...This could take a very long time...
^C
Program received signal SIGINT, Interrupt.
0x00007ffff787cbc0 in nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
Durch die Unterbrechung der Programmausführung mit CTRL+C sehen wir, dass die Ausführung in der Funktion nanosleep(), die eine bestimmte Zeit abwartet, angehalten wurde. Dies erinnert schon an das Vorjahr, in dem der Programmablauf ebenfalls durch solche Funktionen künstlich verzögert wurde.
Unser erster Schritt wird es daher sein, alle Aufrufe von nanosleep() aus dem Binary zu entfernen. Dazu suchen wir zunächst die entsprechende Stelle mit dem GDB heraus, von der nanosleep() aufgerufen wurde.
(gdb) backtrace
#0 0x00007ffff787cbc0 in nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x00007ffff78a5c94 in usleep () from /lib/x86_64-linux-gnu/libc.so.6
#2 0x0000000000401613 in ?? ()
nanosleep() wurde also von der Funktion usleep() aufgerufen, die direkt vom Binary gestartet wurde. Für die Funktion usleep() muss daher im Binary ein Eintrag in der Procedure Linking Table (PLT) vorhanden sein, der für den eigentlichen Aufruf von usleep() in der libc-Bibliothek zuständig ist.
root@linux64:~/hypercomputer# objdump -M intel -d hypercomputer
[...]
0000000000400770 <usleep@plt>:
400770: ff 25 fa 68 20 00 jmp QWORD PTR [rip+0x2068fa] # 607070 <usleep@plt+0x206900>
400776: 68 0b 00 00 00 push 0xb
40077b: e9 30 ff ff ff jmp 4006b0 <fminl@plt-0x10>
Anstatt hier den Sprung in die libc zuzulassen, ersetzen wir die Opcodes durch ein RET (0xC3), das direkt – ohne künstliche Verzögerung – in den Programmcode zurück springt. Dazu verwenden wir einen Hex-Editor, hier “bless”:
Das erneute Ausführen des gepatchten Binarys bringt bereits einen merkbaren Geschwindigkeitsschub – die Berechnung der Flagge erscheint jedoch immernoch zu lange, so dass wir die Programm ausführung nochmals abbrechen.
(gdb) run
Starting program: /root/Plaid2013/hypercomputer2
...Welcome to Hypercomputer!...
...This could take a very long time...
^C
Program received signal SIGINT, Interrupt.
0x0000000000400b71 in ?? ()
Diesmal langen wir in keiner externen Funktion, sondern im hypercomputer-Binary selbst! Was hier genau stattfindet sehen wir uns im GDB an:
(gdb) x/5i $rip-5 0x400b6c: mov eax,0x0 => 0x400b71: add rax,0x1 0x400b75: cmp rax,rdx 0x400b78: jne 0x400b71 0x400b7a: sar rdi,1
Zuerst wird das register EAX auf Null gesetzt, bevor die hervorgehobenen Zeilen so oft als Schleife wiederholt werden, bis die Register RAX und RDX gleich sind, wobei jedes mal nur RAX um Eins erhöht wird. Um diese Schleife zu optimieren, genügt es daher, die Zeilen mit der Operation “MOV RAX, RDX” zu ersetzen.
Die entsprechenden Opcodes könnten wir entweder Nachschlagen, selbst generieren (zum Beispiel mit “nasm”) oder aus dem Binary selbst von anderer Stelle entnehmen. Wir erhalten, dass “MOV RAX, RDX” den Opcodes 0x48 0x89 0xd0 entspricht. Wir suchen und ersetzen daher vorsorglich alle Vorkommen dieser Schleife mit Hilfe eines Hex-Editors, durch diese Opcodes.
Insgesamt ist die Schleif drei Mal vorhanden. Nachdem das Binary erneut abgespeichert wurde, führen wir es aus um unsere Manipulationen zu testen und erhalten:
root@linux64:~/Plaid2013# ./hypercomputer3
...Welcome to Hypercomputer!...
...This could take a very long time...
Y0uKn0wH0wT0Sup3rButCanY0uHyp3r
Wir haben alle erforderlichen Teile des Programmes optimieren können und die Berechnung der Flagge daher so beschleunigt, dass sie nun direkt ausgegeben wird.
Die Lösung lautet somit “Y0uKn0wH0wT0Sup3rButCanY0uHyp3r“.