
Vom Patch zur RCE: handgefertigter Exploit für CVE-2010-4221 (ProFTPD TELNET IAC Stack-Overflow), mit der vollständigen, fehlergetriebenen Reise dokumentiert
Ein vollständig reproduzierbares Labor und ein handgeschriebener Raw-Socket-Exploit für
CVE-2010-4221 — den Stack-Pufferüberlauf vor der Authentifizierung in
ProFTPDs pr_netio_telnet_gets() — aufgebaut als Lernübung in
Schwachstellenforschung und Exploit-Entwicklung.
Jeder Fehlschlag ist dokumentiert. Der Happy Path ist eine Lüge; die Umwege sind die Lektion.
Dieses Repository ist ein pädagogisches Artefakt. Es existiert, damit Menschen, die sich keinen Mentor oder kein Training leisten können, lernen können, wie ein Memory-Corruption-Exploit tatsächlich entsteht: von einem Patch, über Fehlschläge, bis zu einem funktionierenden Proof of Concept innerhalb eines Labors, das dir gehört.
Red-Team-Arbeit — echte, professionelle offensive Sicherheit — ist durch ein einziges Wort definiert: Autorisierung. Alles, was ein Profi tut, geschieht innerhalb einer schriftlichen Vereinbarung: einem unterzeichneten Rules-of-Engagement-Dokument, das den Umfang, die Ziele, die erlaubten Techniken, das Zeitfenster und die Personen benennt, die es genehmigt haben. Ohne dieses Papier sind exakt dieselben Tastendrücke kein Beruf — sie sind in praktisch jeder Jurisdiktion der Erde ein Verbrechen.
Hier ist also der Vertrag für dieses Repo, nicht verhandelbar:
Das Handwerk ist es wert, gelernt zu werden. Das Handwerk ist nur etwas wert mit der Disziplin, die damit einhergeht.
ProFTPD spricht TELNET-Escape-Sequenzen auf dem FTP-Kontrollkanal. In
TELNET ist 0xFF (IAC, „Interpret As Command") das Escape-Byte; ein
literales 0xFF wird als 0xFF 0xFF gesendet.
pr_netio_telnet_gets() kopiert Client-Bytes in einen Stack-Puffer
(pr_cmd_read's char buf[PR_DEFAULT_CMD_BUFSZ+1], 4104 Bytes mit
glibcs MAXPATHLEN=4096), wobei der verbleibende Platz in buflen verfolgt wird — einer
size_t, unsigned.
Der verwundbare Pfad (1.3.3a, netio.c):
case TELNET_IAC:
switch (cp) {
...
default:
*bp++ = TELNET_IAC; // write #1
buflen--; // decrement #1
telnet_mode = 0;
break;
}
break;
...
*bp++ = cp; // write #2
buflen--; // decrement #2 <-- no check in between
Zwei Schreibvorgänge, zwei Dekremente, keine Nullprüfung dazwischen. Wenn buflen
genau 1 ist, dekrementiert das Paar es auf 0, dann unterläuft es auf
SIZE_MAX (18 Trillionen). Die Schleife glaubt nun, der Puffer sei unendlich
und schreibt weiterhin angreiferkontrollierte Bytes den Stack hinauf — über gespeicherte
Register, gespeichertes RBP und die Rücksprungadresse.
Vor der Authentifizierung. Die Funktion läuft, bevor USER/PASS jemals verarbeitet werden.
Der Fix (Commit
3cc69b8388,
„Bug#3521 - Telnet IAC processing stack overflow", veröffentlicht in 1.3.3c)
umfasst zwölf Zeilen. Die gesamte Sicherheitsgrenze ist:
if (buflen == 0) {
break;
}
Siehe patch.diff. Den Patch zu lesen sagt dir, wo die
Wunde war — das ist die Fähigkeit.
Das Dockerfile kompiliert ProFTPD 1.3.3a aus dem historischen Debian- Snapshot-Quellcode, absichtlich unsicher (so sah 2010 aus):
-fno-stack-protector — kein Canary-z execstack — ausführbarer Stack (kein NX)-no-pie — feste Binäradressengdb, das ASLR standardmäßig deaktiviert → deterministischer Stackdocker build -t proftpd-133a .
docker rm -f lab133 2>/dev/null
docker run -d --name lab133 --cap-add SYS_PTRACE \
--security-opt seccomp=unconfined -p 127.0.0.1:2122:21 \
proftpd-133a sh -c 'gdb -batch -ex "set follow-fork-mode child" \
-ex "run" -ex "continue" --args /usr/local/sbin/proftpd -n -d1 \
> /tmp/gdb.txt 2>&1; sleep 600'
python3 exploit.py
Erwartete Ausgabe:
[S] 220 ProFTPD 1.3.3a Server (lab-iac) ...
[S] THE SERVER SAID: b'PWNED!!PWNED!!'
(Der Shellcode schreibt auf die FDs 0, 1 und 2, weil wir nicht davon abhängig sein wollten zu wissen, welcher den Kontrollkanal trägt — zwei von ihnen antworten.)
"SITE " + NOP-Sled + Shellcode + [\xff\xff-Flut] + Padding + [ret] + "\n"
^^^^^^^^^^^^^^^^^^^^ ^^^^
Shellcode lebt IM Befehls- der Overflow-Schwanz liefert
puffer — die Region, die nur EINE Adresse
niemand anfasst
buf[4102] berührt
(Abschneidungs-NUL). Alles unterhalb der lebenden lokalen Variablen des Frames ist ruhig.buflen zum Unterlauf (siehe „Die Reise"
für das Paritätsproblem).pr_cmd_read nach dem Parsen return 0 erreicht,
landet die CPU im Sled und gleitet in den Shellcode.Diese invertierte Architektur — Payload zuerst, Flut zweitens, Adresse zuletzt —
wurde gegen das kanonische Metasploit-Modul validiert
(proftp_telnet_iac), das dasselbe Layout verwendet. Deren Ziele hatten NX,
also brauchten sie eine ROP-Kette mit einem „vierfachen Deref" des res-Zeigers;
unser Labor hat einen ausführbaren Stack, also genügt ein einzelner direkter Return.
Der finale Exploit umfasst 60 Zeilen. Was er kostete:
Blinde \xff-Flut → nichts. Der Server beendete die Sitzung höflich.
Grundursache: buflen startet bei 4102 (GERADE) und jedes IAC-Paar
dekrementiert um 2 — es landet sauber auf 0, nie auf 1. Der Unterlauf
benötigt eine gebrochene Parität. Lektion: das Lesen der Zustandsmaschine schlägt das Sprühen.
Falsche Puffergröße. Erster kalibrierter Versuch nahm einen 1024-Byte-Puffer
an. Der echte ist MAXPATHLEN+8 = 4104 auf Linux/glibc. Die Flut
stoppte 3 KB vor dem Ziel. Lektion: miss das Ziel, nimm das Ziel nicht an.
Erster SIGSEGV. Zyklisches De-Bruijn-Muster (Aa0Aa1...) platzierte den
Ret-Slot bei buf+4152, zweifach kreuzvalidiert (Frame-Mathematik + Muster-
Offset). Lektion: das zyklische Muster ist ein Maßband, kein Exploit.
RIP-Kontrolle. Das Setzen des Slots auf 0x4141414141414141 ließ die
ret-Instruktion selbst abstürzen — x86-64 verweigert nicht-kanonische Adressen, und
der Fehler landet auf ret, wobei unser Wert im Backtrace wartet.
Lektion: ein Absturz bei ret mit deinem Wert im Frame = Kontrolle.
Shellcode oberhalb des Ret-Slots → überschrieben. 8 Bytes durch einen
Heap-Zeiger überschrieben (0x4d7838 — später als die cmd_rec-Pool-
Allokation identifiziert). Die Stack-Frames oberhalb des Slots gehören zu Funktionen, die
zwischen Landung und Hijack noch arbeiten. Lektion: der Overflow ist nicht der
letzte Schreibvorgang; das Programm lebt weiter auf dem Stack, den du gerade vandalisiert hast.
„Tote Zone" unterhalb des Slots → ebenfalls beschrieben. pr_cmd_read's eigene
lokale Variablen (cmd, buflen, cp) leben genau dort und werden während des Parsens
weiterhin gespeichert.
Hardware-Watchpoint-Forensik. watch *(long*)ADDR in gdb verwandelte
das Rätsel in eine Kamera: jeder Schreibvorgang auf die überschriebene Adresse, mit
Backtrace, in Reihenfolge. Lektion: wenn die Frage „wer hat diesen Speicher geschrieben?" ist,
ist die Antwort einen gdb-Befehl entfernt.
Lies die Referenz, dann verstehe sie. Das kanonische Modul bestätigte die invertierte Architektur. Einen anderen Exploit nach dem Aufbau deines eigenen mentalen Modells zu lesen ist Studium; davor ist es Kopieren.
size_t wird nie negativ — es wird gigantisch. Integer-Unterlauf
in einem Zähler für verbleibenden Platz ist ein Stack-Overflow mit Extraschritten.\x0a
(beendet das Lesen) und überlebt \xff (TELNET-Escape) konstruktionsbedingt.argv[0] (Build-Tree-Binary vs. installierte Binary) verschob den gesamten
Stack um 0x40 und machte einen perfekten Exploit stillschweigend ungültig.modules/exploits/linux/ftp/proftp_telnet_iac.rb
(rapid7/metasploit-framework)Erstellt von diegslva, öffentlich lernend — von „nie einen Exploit geschrieben" zu Pre-Auth-RCE mit handgerolltem Shellcode, an einem dokumentierten Tag. Wenn dieses Repo dir etwas beigebracht hat, gib es weiter.