Wie kriege ich mit ROP einen "/bin/sh" Pointer in rdi?
Ich versuche, rücksprungorientierte Programmierung (ROP) zu lernen.
Und zwar habe ich ein Programm mit einem Pufferüberlauf auf dem Stack, und ich möchte das Programm dazu bringen, /bin/sh zu öffnen.
Das geht mit dem execve Syscall, wenn ich die richtigen Instruktionen finden kann, um die Funktionsparameter vorzubereiten. Das ist die Signatur von execve:
int execve(const char *pathname, char *const _Nullable argv[], char *const _Nullable envp[]);
Also muss ich die folgenden Register setzen:
- rax = 0x3b (Syscallnummer von execve)
- rdi = "/bin/sh" Pointer
- rsi = NULL
- rdx = NULL
Die folgenden Instruktionen habe ich bereits gefunden:
pop rax ; ret
pop rdi ; ret
pop rsi ; ret
pop rdx ; ret
syscall
Ich kann also die Instruktionen und Registerwerte mit dem Pufferüberlauf auf den Stack schreiben und so meine Register füllen. Das Problem ist aber, dass ich einen "/bin/sh" Pointer in rdi brauche (also nicht "/bin/sh" im Register, sondern eine Speicheradresse, an der "/bin/sh" steht).
Ich kann natürlich "/bin/sh" in den Puffer auf dem Stack schreiben, aber leider ist die Speicheradresse jedes Mal anders und ich kenne sie vorher nicht.
Ich weiß, dass "/bin/sh" in libc vorkommt, aber auch dort ist die Speicheradresse jedes Mal anders und ich kenne sie vorher nicht.
Wie komme ich also an einen "/bin/sh" Pointer? Gibt es Tricks oder bestimmte Instruktionen, nach denen ich mich umsehen sollte?
2 Antworten
Ich kann natürlich "/bin/sh" in den Puffer auf dem Stack schreiben, aber leider ist die Speicheradresse jedes Mal anders und ich kenne sie vorher nicht.
Welche Adresse ist jedes mal anders?
Du könntest vielleicht '"/bin/sh" auf dem Stack ablegen und kennst die Position zum Basepointer, sodaß Du mit dessen Hilfe eine Adresse konstruieren kannst?
Die Idee wäre halt den Basepointer (liegt ja in RBP) zu nehmen, den bekannten offset zu Deinem string zu verrechnen udn das Ergebnis in rdi zu legen.
Ich denke da in Richtung:
LEA rdi,[rbp+knownoffset]
Danke, ich schau mal nach, ob es solche Instruktionen im Programm gibt.
Statt eines LEA ginge ggf. auch direkte Arithmetik, also ein geeignetes mov udn add o.ä. .
Ob du damit weiterkommst, kann ich Dir natürlich nicht sagen.
Pack doch einfach den String "/bin/sh" in die `.data` Region deiner Software. Dann weißt du immer, wo sich dieser befindet und hast den Pointer dazu.
Hier ein Beispiel: https://www.mourtada.se/calling-printf-from-the-c-standard-library-in-assembly/
Es ist ja eben nicht meine eigene Software, sondern eine Challenge, die auf einem fremden Server läuft. Ich kann das Programm also nicht direkt modifizieren, sondern nur Eingaben senden.
Ich verstehe nicht ganz.
Du kannst direkt in assembly auf den Stack schreiben. Dann hast du doch auch den Stackpointer, der zu dem String zeigt (wenn du ihn dahin schreibst). Die Adresse kannst du dir in ein Register kopieren und an die Funktion geben.
Wenn nicht, müsstest du mehr Informationen zu dem teilen, wie genau dein Code bisher aussieht.
Nein, ich kann kein Assembly auf den Stack schreiben, weil der Stack nicht ausführbar ist. Ich überschreibe durch einen Pufferüberlauf die Rücksprungadresse auf dem Stack, um den Kontrollfluss des Programms zu manipulieren. Deshalb kann ich nur Code, der schon im Programm vorhanden ist, zweckentfremden.
Zum Beispiel gibt es im Programmcode 2 Funktionen, die so enden:
...
0x1234: pop rdi
ret
...
0x5678: pop rsi
ret
Die Adressen habe ich frei erfunden.
Wenn ich die Adresse der "pop rdi" Instruktion auf den Stack schreibe, springt das Programm dort hin, nachdem es mit der aktuellen Funktion fertig ist und liest rdi vom Stack. Die Idee ist jetzt also, solche kleinen Stücke des Programmcodes aneinanderzureihen.
Ich würde dann z.B. das hier als Eingabe senden (natürlich kodiert und Little Endian, aber für die bessere Lesbarkeit lasse ich das hier mal weg):
0x0000000000000000
...
0x0000000000001234
0x0000000000069420
0x0000000000005678
0x0000000000000000
...
Zuerst einige Bytes, sodass 0x1234 an die Stelle auf dem Stack kommt, an der die Rücksprungadresse steht. Dann den Wert, den ich in rdi haben will (ich habe hier 0x69420 als Beispiel verwendet, aber eigentlich will ich einen "/bin/sh" Pointer). 0x5678 ist dann die Rücksprungadresse, die von der "ret" Instruktion nach "pop rdi" verwendet wird, weil ich als nächstes rsi setzen möchte. In rsi schreibe ich in diesem Beispiel 0x0 rein, und so weiter.
Diese Technik heißt rücksprungorientierte Programmierung.
Das funktioniert so weit auch. Ich habe es mit gdb getestet, und die Register haben die richtigen Werte. Aber ich weiß eben nicht, wie ich einen "/bin/sh" Pointer in rdi bekomme.
Die relative Position zum Basepointer kann ich herausfinden. Die sollte sich ja nicht verändern. Aber der Basepointer ist jedes Mal anders.