
glibc 2.43 पर House of Apple 2 FSOP तकनीक का इंटरैक्टिव GDB वॉकथ्रू, जिसमें vtable बायपास, स्टैक पिवोटिंग और ROP को कवर करने वाला पुनरुत्पादनीय सैंडबॉक्स शामिल है।
यह रिपॉज़िटरी pwn.college के File Struct Exploitation मॉड्यूल से प्रेरित एक स्व-निहित playground है। यह House of Apple 2 का कोई नया रूपांतर प्रस्तुत नहीं करता, यह केवल मेरी जिज्ञासा का उत्तर देता है कि यह तकनीक glibc के हालिया संस्करणों पर कैसे टिकती है और क्या यह एक व्यवहार्य exploitation पथ बनी हुई है। यह दस्तावेज़ एक इंटरैक्टिव GDB walkthrough प्रदान करता है जिसे पाठक sandbox के साथ-साथ follow कर सकते हैं ताकि primitive की अधिक सहज समझ विकसित कर सकें। सभी प्रयोग glibc 2.43 का उपयोग करते हैं, जैसा कि लेखन के समय Ubuntu 26.04 और Fedora 44 द्वारा packaged था।
File Stream Oriented Programming (FSOP)। यह glibc file stream structures में हेरफेर करके control flow को hijack करने के बारे में है। ऐसा करने का एक तरीका _IO_FILE_plus के vtable dispatch mechanism को corrupt करना है। आधुनिक glibc इस vtable को validate करता है, इसलिए इसे किसी मनमाने address से बदलने का स्पष्ट तरीका काम नहीं करता।
House of Apple 2, जिसे मूल रूप से Roderick ने प्रस्तुत किया था, इस प्रतिबंध को एक वैध _IO_FILE_plus vtable का उपयोग करके wide-character stream machinery तक पहुँचकर bypass करता है, जहाँ एक secondary vtable बिना range validation के सीधे dispatch होता है। यह एक arbitrary call primitive प्रदान करता है जिसे हम stack pivot और ROP chain में escalate कर सकते हैं।
यह खोज मानती है कि हम एक FILE structure को overwrite कर सकते हैं और हमारे पास heap leak और libc leak दोनों हैं। लक्ष्य binary यह पहले से प्रदान करता है।
Sandbox Ubuntu 26.04 LTS चलाता है, जो हमें इस तकनीक की खोज के लिए एक आधुनिक वातावरण देता है।
इमेज में GDB, pwndbg, pwntools, ropper और tmux शामिल हैं। इसमें एक लक्ष्य binary भी है जिसमें fopen, fread, fwrite, और fclose जैसे file stream operations को invoke करने के लिए एक इंटरैक्टिव मेनू है। यह हमें debugging और विचारों के परीक्षण के दौरान streams में हेरफेर करने का एक सुविधाजनक तरीका देता है।
Sandbox को build और run करें:``` ./build.sh ./run.sh
## अन्वेषण
आइए `_IO_FILE` और `_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;
}
व्यावहारिक रूप से, _IO_FILE_plus एक _IO_FILE है जिसमें एक vtable पॉइंटर होता है। यह तुरंत दिलचस्प लगता है: यदि हम इस पॉइंटर को नियंत्रित कर सकें, तो हम एक अप्रत्यक्ष कॉल को पुनर्निर्देशित कर सकते हैं और नियंत्रण प्रवाह को हाईजैक कर सकते हैं।
vtable का निरीक्षण करने के लिए, आइए fopen द्वारा लौटाए गए एक FILE पॉइंटर की जाँच करें।```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>
}
The pointer `_IO_file_jumps` टेबल को target करता है।

यह 21 function pointers का एक सेट है। File stream operations execution path के आधार पर अलग-अलग entries के माध्यम से dispatch होते हैं।
### `fwrite` path का अनुसरण करना
इस exploration के लिए मैं `fwrite` path पर ध्यान केंद्रित करूँगा। प्रत्येक function पर breakpoints लगाने और `fwrite` को call करने के बाद, पहला breakpoint जो हम hit करते हैं वह `_IO_file_xsputn` है।

यह call `fwrite+216` पर होता है। यह [glibc source](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/iofwrite.c#L44) से मेल खाता है: `_IO_sputn` एक macro है जो vtable के माध्यम से dispatch होता है, और इस stream के लिए `_IO_file_xsputn` में resolve होता है।```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]
पहले प्रयास के लिए, आइए vtable पॉइंटर को desired_func - 0x38 से ओवरराइट करें और 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.

ब्रेकपॉइंट तक पहुँचने से पहले ही निष्पादन रद्द हो जाता है। त्रुटि बताती है कि glibc अप्रत्यक्ष कॉल करने से पहले vtable पॉइंटर को मान्य करता है। आइए बैकट्रेस का निरीक्षण करें और देखें कि यह कहाँ होता है।
`fwrite` `_IO_vtable_check` तक पहुँचता है जो नकली vtable पॉइंटर को अस्वीकार कर रहा है।

कार्यान्वयन में विदेशी vtables को स्वीकार करने की एक व्यवस्था है, लेकिन यह हमारे नियंत्रण में नहीं है। संबंधित कोड [`vtables.c`](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/vtables.c#L504) में उपलब्ध है।
### vtable सत्यापन को समझना