IO_2_1_stdout_利用

Ch33_1n'
6 min readApr 3, 2021

利用場景常見為:能實現任意地址寫、程序沒有打印功能。

目的:代替打印函數泄露信息。

源碼

源碼位置

/usr/include/x86_64-linux-gnu/bits/libio.h

IO FILE結構

從程序運行說起。一個進程啟動之後,內核中會創建PCB進程控制塊,裏面就有一個文件描述符表(一進程一個)。

該表記錄的是該進程 打開的文件描述符fd 和 對應的FILE結構體的地址ptr 。比如我們所熟知的標準輸入/輸出/錯誤,對應的fd分別是0/1/2。這三個文件一般在進程啟動後打開。

ptr指向 file table entry,記錄的是文件讀寫打開模式,包括status flags、file offset、v-node ptr(虛擬文件系統對應的文件節點)。右下角的i-node是磁盤文件系統對應的文件節點。可以理解為PCB建立了一個映射,從ptr出發能找到文件在磁盤文件系統上的位置,進行讀寫等操作。

而 _IO_FILE 就是linux的標準IO庫中用於描述這些文件的結構。該結構在程序執行fopen等函數的時候創建(分配在堆中),open函數返回的fd就指向了這個新創建的 _IO_FILE 結構體。

_IO_FILE結構體的源碼定義就在libio.h文件中:

可以看到結構體中定義了一些狀態位、讀寫buf起止指針、文件描述符等。

_IO_2_1_stdout_結構躰

_IO_2_1_stdout_ 的 _flags 一般結構為:

_IO_MAGIC | _IO_IS_FILEBUF | _IO_CURRENTLY_PUTTING | _IO_LINKED | _IO_NO_READS | _IO_UNBUFFERED | _IO_USER_BUF

對應的值到底是什麽呢,在源碼開頭有定義:(已省去其他宏定義)

也就是:0xfbad2887

在程序無法泄露地址、又能實現任意地址寫的時候,如果能修改這個 _IO_2_1_stdout_ 的一些值(比如 _flags、_IO_write_base、_IO_write_ptr等),讓它指向目的地址,就能在調用puts或printf的時候,泄露出目的地址的值,比如libc地址。

首先是_flags的值,具體設置什麽得參考puts函數的調用過程到底做了什麽檢查,一般為0xfbad18**。

接著_IO_write_base和_IO_write_ptr設置成需要泄露的起止地址就可以了。

調用puts函數

調用puts函數的時候,它是怎麽跟_IO_2_1 stdout_ 聯系起來的呢。這又得去看源碼了。

官網上下載源碼:http://www.gnu.org/software/libc/sources.html

源碼

這裏以glibc-2.27爲例。找到 libio 文件下的 ioputs.c:

可以看到puts函數調用_IO_puts,而_IO_puts又調用了_IO_sputn。

_IO_sputn實際上是一個宏,調用的是 _IO_2_1_stdout_ 的 vtable 中的 __xsputn,也就是 _IO_new_file_xsputn 函數。

這個函數在libio文件下的fileops.c中:

該函數在寫入緩存區的時候調用了 _IO_OVERFLOW 函數,判斷有沒有往緩存區中寫入數據,也就是下面的 _IO_new_file_overflow 函數:

除此之外,
繞過了 _IO_CURRENTLY_PUTTING 的檢查後,調用了 _IO_do_write 函數:

緊接著調用了 new_do_write 函數。可以看到_IO_IS_APPENDING 標誌位需要設置為1(默認為0),否則無法繼續輸出。

到這裏,要是前面的檢查全都繞過了,那就能正常write出我們目標地址的內容了。

思路整理

如果stdout還沒輸出過,_IO_CURRENTLY_PUTTING 的標誌位默認為0,需要設置為1 。
如果已經輸出過就不需要修改了。
下面修改的值都是stdout尚未輸出過的情況。

只改write_base

為了繞過 stdout->_IO_read_end != stdout->_IO_write_base 的檢查,

需要同時修改 _flags 的 _IO_IS_APPENDING 和 _IO_CURRENTLY_PUTTING ,加上 _IO_MAGIC 就是:0xfbad1800(0xfbad2087| 0x1000 | 0x800)。

這時_IO_write_base可修改成目的地址,調用printf或者puts時就能泄露目的地址。

改write_base和read_end

stdout->_IO_read_end stdout->_IO_write_base 的偏移相同時,兩者仍相等,這樣就不需要修改 _IO_IS_APPENDING 了。

_flags 只用修改 _IO_CURRENTLY_PUTTING ,也就是:0xfbad2800(0xfbad2087 | 0x800)。

利用方法

當 unsorted bin 中的 chunk 和 tcache/fastbin 中的 chunk 重合(同一個)時,fd指向 main_arena 地址。
利用uaf等漏洞,覆蓋低位,爆破stdout的地址,分配到stdout結構體處,劫持stdout結構體、泄露地址。

補充

参考链接

--

--