Ich habe das folgende Montageprogramm aus dem Binärbombenlabor. Ziel ist es, das Schlüsselwort zu ermitteln, das zum Ausführen der Binärdatei erforderlich ist, ohne die explode_bomb
Funktion auszulösen . Ich habe meine Analyse der Baugruppe für dieses Programm kommentiert, aber ich habe Probleme, alles zusammenzusetzen.
Ich glaube, ich habe alle Informationen, die ich brauche, aber ich kann die zugrunde liegende Logik immer noch nicht erkennen und stecke daher fest. Ich würde mich über jede Hilfe sehr freuen!
Das folgende ist das zerlegte Programm selbst:
0x08048c3c <+0>: push %edi
0x08048c3d <+1>: push %esi
0x08048c3e <+2>: sub $0x14,%esp
0x08048c41 <+5>: movl $0x804a388,(%esp)
0x08048c48 <+12>: call 0x80490ab <string_length>
0x08048c4d <+17>: add $0x1,%eax
0x08048c50 <+20>: mov %eax,(%esp)
0x08048c53 <+23>: call 0x8048800 <malloc@plt>
0x08048c58 <+28>: mov $0x804a388,%esi
0x08048c5d <+33>: mov $0x13,%ecx
0x08048c62 <+38>: mov %eax,%edi
0x08048c64 <+40>: rep movsl %ds:(%esi),%es:(%edi)
0x08048c66 <+42>: movzwl (%esi),%edx
0x08048c69 <+45>: mov %dx,(%edi)
0x08048c6c <+48>: movzbl 0x11(%eax),%edx
0x08048c70 <+52>: mov %dl,0x10(%eax)
0x08048c73 <+55>: mov %eax,0x4(%esp)
0x08048c77 <+59>: mov 0x20(%esp),%eax
0x08048c7b <+63>: mov %eax,(%esp)
0x08048c7e <+66>: call 0x80490ca <strings_not_equal>
0x08048c83 <+71>: test %eax,%eax
0x08048c85 <+73>: je 0x8048c8c <phase_3+80>
0x08048c87 <+75>: call 0x8049363 <explode_bomb>
0x08048c8c <+80>: add $0x14,%esp
0x08048c8f <+83>: pop %esi
0x08048c90 <+84>: pop %edi
0x08048c91 <+85>: ret
Der folgende Block enthält meine Analyse
5 <phase_3>
6 0x08048c3c <+0>: push %edi // push value in edi to stack
7 0x08048c3d <+1>: push %esi // push value of esi to stack
8 0x08048c3e <+2>: sub $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)
9
10 0x08048c41 <+5>: movl $0x804a388,(%esp) // put 0x804a388 into loc esp points to
11
12 0x08048c48 <+12>: call 0x80490ab <string_length> // check string length, store in eax
13 0x08048c4d <+17>: add $0x1,%eax // increment val in eax by 0x1 (str len + 1)
14 // at this point, eax = str_len + 1 = 77 + 1 = 78
15
16 0x08048c50 <+20>: mov %eax,(%esp) // get val in eax and put in loc on stack
17 //**** at this point, 0x804a388 should have a value of 78? ****
18
19 0x08048c53 <+23>: call 0x8048800 <malloc@plt> // malloc --> base ptr in eax
20
21 0x08048c58 <+28>: mov $0x804a388,%esi // 0x804a388 in esi
22 0x08048c5d <+33>: mov $0x13,%ecx // put 0x13 in ecx (counter register)
23 0x08048c62 <+38>: mov %eax,%edi // put val in eax into edi
24 0x08048c64 <+40>: rep movsl %ds:(%esi),%es:(%edi) // repeat 0x13 (19) times
25 // **** populate malloced memory with first 19 (edit: 76) chars of string at 0x804a388 (this string is 77 characters long)? ****
26
27 0x08048c66 <+42>: movzwl (%esi),%edx // put val in loc esi points to into edx
***** // at this point, edx should contain the string at 0x804a388?
28
29 0x08048c69 <+45>: mov %dx,(%edi) // put val in dx to loc edi points to
***** // not sure what effect this has or what is in edi at this point
30 0x08048c6c <+48>: movzbl 0x11(%eax),%edx // edx = [eax + 0x11]
31 0x08048c70 <+52>: mov %dl,0x10(%eax) // [eax + 0x10] = dl
32 0x08048c73 <+55>: mov %eax,0x4(%esp) // [esp + 0x4] = eax
33 0x08048c77 <+59>: mov 0x20(%esp),%eax // eax = [esp + 0x20]
34 0x08048c7b <+63>: mov %eax,(%esp) // put val in eax into loc esp points to
***** // not sure what effect these movs have
35
36 // edi --> first arg
37 // esi --> second arg
38 // compare value in esi to edi
39 0x08048c7e <+66>: call 0x80490ca <strings_not_equal> // store result in eax
40 0x08048c83 <+71>: test %eax,%eax
41 0x08048c85 <+73>: je 0x8048c8c <phase_3+80>
42 0x08048c87 <+75>: call 0x8049363 <explode_bomb>
43 0x08048c8c <+80>: add $0x14,%esp
44 0x08048c8f <+83>: pop %esi
45 0x08048c90 <+84>: pop %edi
46 0x08048c91 <+85>: ret
Aktualisieren:
Wenn ich die Register überprüfe, bevor strings_not_equal aufgerufen wird, erhalte ich Folgendes:
eax 0x804d8aa 134535338
ecx 0x0 0
edx 0x76 118
ebx 0xffffd354 -11436
esp 0xffffd280 0xffffd280
ebp 0xffffd2b8 0xffffd2b8
esi 0x804a3d4 134521812
edi 0x804f744 134543172
eip 0x8048c7b 0x8048c7b <phase_3+63>
eflags 0x282 [ SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
und ich erhalte den folgenden zerlegten Pseudocode mit Hopper:
Ich habe sogar versucht, sowohl die in eax gefundene Nummer als auch die zuvor als Schlüsselwort angegebene Zeichenfolge zu verwenden, aber keine davon hat funktioniert.
rep movsl
kopiert 32-Bit-Langwörter von Adresse%esi
zu Adresse%edi
und erhöht beide um jeweils 4, was einer Anzahl von gleich entsprichtecx
. Betrachten Sie es alsmemcpy(edi, esi, ecx*4)
. Siehe felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq (movsd
in Intel-Notation).0x80490ab
eine Länge von 77 hat. Und vielen Dank für den Link!Antworten:
Die Funktion erstellt eine geänderte Kopie eines Strings aus dem statischen Speicher in einen malloced Puffer.
Das sieht komisch aus. Die
malloc
Größe ist abhängig vonstrlen
+1, aber diememcpy
Größe ist eine Kompilierungszeitkonstante? Ihre Dekompilierung zeigt anscheinend, dass die Adresse ein String-Literal war, also scheint das in Ordnung zu sein.Wahrscheinlich ist diese verpasste Optimierung auf eine benutzerdefinierte
string_length()
Funktion zurückzuführen, die möglicherweise nur in einer anderen definiert wurde.c
(und die Bombe wurde ohne Optimierung der Verbindungszeit für dateiübergreifendes Inlining kompiliert). Essize_t len = string_length("some string literal");
handelt sich also nicht um eine Kompilierungszeitkonstante, und der Compiler hat einen Aufruf an sie gesendet, anstatt die bekannte konstante Länge der Zeichenfolge verwenden zu können.Aber wahrscheinlich haben sie
strcpy
in der Quelle verwendet und der Compiler hat das alsrep movs
. Da es anscheinend aus einem String-Literal kopiert wird, ist die Länge eine Konstante für die Kompilierungszeit und kann den Teil der Arbeit, derstrcpy
normalerweise zu erledigen ist, optimieren . Normalerweise , wenn Sie bereits über die Länge berechnet haben ist es besser , zu verwenden ,memcpy
anstatt,strcpy
berechnen sie im Fluge wieder, aber in diesem Fall ist es half tatsächlich der Compiler macht besseren Code für den Teil , als wenn sie den Rückgabewert übergeben haben ,string_length
um einmemcpy
, wieder weilstring_length
nicht inline und weg optimieren konnte.Solche Kommentare sind überflüssig. die Anweisung selbst sagt das schon. Dadurch werden zwei aufruferhaltene Register gespeichert, damit die Funktion sie intern verwenden und später wiederherstellen kann.
Ihr Kommentar zu
sub
ist besser; Ja, wachsen Sie den Stapel ist die übergeordnete semantische Bedeutung hier. Diese Funktion reserviert etwas Platz für Einheimische (und für Funktionsargumente, mit denenmov
anstelle vonpush
ed gespeichert werden soll ).Die
rep movsd
Kopien 0x13 * 4 Bytes erhöhen ESI und EDI, um über das Ende des kopierten Bereichs hinaus zu zeigen. Eine anderemovsd
Anweisung würde also weitere 4 Bytes kopieren, die an die vorherige Kopie angrenzen.Der Code kopiert tatsächlich eine weitere 2, verwendet jedoch anstelle der Verwendung
movsw
einemovzw
Wortladung und einenmov
Speicher. Dadurch werden insgesamt 78 Bytes kopiert.Auf einigen (aber nicht allen) CPUs wäre es effizient gewesen, nur
rep movsb
oderrep movsw
mit angemessener Anzahl zu verwenden, aber das hat der Compiler in diesem Fall nicht gewählt.movzx
aka AT & Tmovz
ist ein guter Weg, um enge Lasten ohne Teilregistrierungsstrafen zu erledigen. Das ist der Grund, warum Compiler dies tun, damit sie ein vollständiges Register schreiben können, obwohl sie nur die niedrigen 8 oder 16 Bits dieser Registrierung mit einer Speicheranweisung lesen werden.Nach dieser Kopie eines String-Literal in buf haben wir ein Byte laden / speichern, mit dem ein Zeichen kopiert wird
buf
. Denken Sie daran, dass EAX an dieser Stelle immer noch aufbuf
denmalloc
Rückgabewert zeigt. Es wird also eine modifizierte Kopie des String-Literal erstellt.Wenn die Quelle die konstante Ausbreitung nicht besiegt hätte, hätte der Compiler bei einer ausreichend hohen Optimierungsstufe möglicherweise nur die letzte Zeichenfolge
.rodata
dort abgelegt, wo Sie sie finden könnten, und diese Bombenphase trivialisiert. : P.Anschließend werden Zeiger als Stapelargumente für den Zeichenfolgenvergleich gespeichert.
Wie man "schummelt": Betrachten Sie das Laufzeitergebnis mit GDB
In einigen Bombenlabors können Sie die Bombe nur online auf einem Testserver ausführen, auf dem Explosionen aufgezeichnet werden. Sie konnten es nicht unter GDB ausführen, sondern nur statische Demontage (wie
objdump -drwC -Mintel
) verwenden. So konnte der Testserver aufzeichnen, wie viele fehlgeschlagene Versuche Sie hatten. zB wie CS 3330 bei cs.virginia.edu , das ich bei Google gefunden habe, wo volle Gutschrift weniger als 20 Explosionen erfordert.Die Verwendung von GDB zur Untersuchung von Speicher / Registern auf halbem Weg durch eine Funktion macht dies erheblich einfacher als nur die Arbeit mit statischen Analysen. Diese Funktion wird trivialisiert, wenn die einzelne Eingabe nur ganz am Ende überprüft wird. zB schau dir nur an, an welches andere Argument weitergegeben wird
strings_not_equal
. (Vor allem, wenn Sie GDBsjump
oderset $pc = ...
Befehle verwenden, um die Bombenexplosionsprüfungen zu überspringen.)Setzen Sie einen Haltepunkt oder Einzelschritt auf kurz vor dem Aufruf von
strings_not_equal
. Verwenden Siep (char*)$eax
diese Option, um EAX als zu behandelnchar*
und Ihnen die (0-terminierte) C-Zeichenfolge ab dieser Adresse anzuzeigen. Zu diesem Zeitpunkt enthält EAX die Adresse des Puffers, wie Sie vom Speicher bis zum Stapel sehen können.Kopieren Sie das String-Ergebnis und fügen Sie es ein.
Andere Phasen mit mehreren numerischen Eingaben sind normalerweise nicht so einfach mit einem Debugger zu erstellen und erfordern zumindest einige mathematische Berechnungen. Phasen mit verknüpften Listen, bei denen Sie eine Folge von Zahlen in der richtigen Reihenfolge für die Listenüberquerung benötigen, werden jedoch ebenfalls trivial, wenn Sie wissen, wie Sie mit einem Debugger Register festlegen, damit Vergleiche erfolgreich sind, wenn Sie zu ihnen gelangen.
quelle
rep movsl
kopiert 32-Bit-Langwörter von Adresse%esi
zu Adresse%edi
und erhöht beide um jeweils 4, was einer Anzahl von gleich entspricht%ecx
. Betrachten Sie es alsmemcpy(edi, esi, ecx*4)
.Siehe https://felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq (es ist movsd in Intel-Notation).
Das kopiert also
19*4=76
Bytes.quelle