64位c语言调用32位glibc,glibc fclose源代码阅读及伪造_IO_FILE利用fclose实现任意地址执行...
簡(jiǎn)介
最近學(xué)習(xí)了一下_IO_FILE的利用,剛好在pwnable.tw上碰到一道相關(guān)的題目。拿來(lái)做了一下,遇到了一些困難,不過(guò)順利解決了,順便讀了一波相關(guān)源碼,對(duì)_IO_FILE有了更深的理解。
文章分為三部分,分別是利用原理、實(shí)例和源碼閱讀。源碼部分比較無(wú)聊所以我把它放在了最后。
原理
原理
我們使用fopen打開(kāi)一個(gè)文件會(huì)在堆上分配一塊內(nèi)存區(qū)域用來(lái)存儲(chǔ)FILE結(jié)構(gòu)體,存儲(chǔ)的結(jié)構(gòu)體包含兩個(gè)部分,前一部分為_(kāi)IO_FILE結(jié)構(gòu)體file,后一部分是一個(gè)指向struct IO_jump_t的指針vtable, 這個(gè)結(jié)構(gòu)體種存儲(chǔ)著一系列與文件IO相關(guān)的函數(shù)指針。
在我們調(diào)用fclose關(guān)閉一個(gè)文件時(shí),我們最終會(huì)調(diào)用到vtable中存儲(chǔ)的函數(shù)指針。如果我們能夠?qū)table中的指針替換為我們自己想要跳轉(zhuǎn)到的地址就可以劫持程序流程。
利用前提
本文僅考慮libc版本 <= 2.23的情況。因?yàn)榇笥诘扔?.24的libc會(huì)對(duì)vtable的位置做判斷,無(wú)法令其指向自己構(gòu)造的區(qū)域
可以控制vtable指針或者fp指針指向的位置
有一塊已知地址的可控內(nèi)存區(qū)域,大小需要視情況而定
利用方式1:直接覆蓋vtable指針
這個(gè)沒(méi)什么好說(shuō)的,將vtable指針指向可控內(nèi)存,將__finish(off=2*SIZE_T)構(gòu)造為要執(zhí)行的地址即可
利用方式2:覆蓋fp指針
有的時(shí)候我們無(wú)法直接控制FILE結(jié)構(gòu)體的vtable指針,但是我們可以控制文件指針。因此我們需要偽造整個(gè)FILE結(jié)構(gòu)體,然后控制vtable指針指向我們自己構(gòu)造的函數(shù)列表,在__finish(off=2*SIZE_T)位置布置好我們想要調(diào)用的地址,最后調(diào)用fclose。
這種方式的 關(guān)鍵 在于要偽造一個(gè)合適的FILE結(jié)構(gòu)體使得在fclose的過(guò)程中不會(huì)觸發(fā)異常造成程序異常終止。為了避免這種情況,一種最簡(jiǎn)單的方式就是將FILE結(jié)構(gòu)體的_flags變量的_IO_IS_FILEBUF標(biāo)志位置0。例如置為0xffffdfff。這樣做的主要原因是為了繞過(guò)一些操作。
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
相關(guān)代碼如上。
可以看到當(dāng)_IO_IS_FILEBUF位為0時(shí),函數(shù)不會(huì)執(zhí)行_IO_un_link和_IO_file_close_it函數(shù),而直接執(zhí)行_IO_FINISH函數(shù)。在_IO_FINISH函數(shù)中會(huì)直接調(diào)用vtable中的__finish函數(shù)。其中_IO_IS_FILEBUF被定義為0x2000。
#define _IO_IS_FILEBUF 0x2000
利用實(shí)例
題目簡(jiǎn)介
測(cè)試用的題目來(lái)源于pwnable.tw,題目名為seethefile。為FILE結(jié)構(gòu)體利用的一道比較經(jīng)典的題目。在這里只談一下解題思路,不給出exp。
逆向分析概要
openfile打開(kāi)一個(gè)文件,文件名由用戶(hù)輸入,但是當(dāng)文件名含flag時(shí)會(huì)退出程序
readfile將文件中的內(nèi)容讀入一個(gè)全局字符數(shù)組中
writefile將全局字符數(shù)組中的內(nèi)容輸出到屏幕上
closefile關(guān)閉文件
當(dāng)輸入為5時(shí)程序要求輸入一個(gè)名字,然后關(guān)閉存儲(chǔ)文件指針fp,退出程序
main.png
以上步驟的文件指針都存放在一個(gè)全局變量中,bss對(duì)應(yīng)的結(jié)構(gòu)如下
bss.png
漏洞分析
由逆向結(jié)果可知。在讀取數(shù)字時(shí)存在棧溢出,但是程序開(kāi)啟了棧保護(hù),所以不可利用;
當(dāng)輸入命令5,讀取用戶(hù)指令時(shí)存在一個(gè)bss段的溢出,利用這個(gè)溢出我們可以覆蓋fp的指針指向我們想要的位置,同時(shí)可以偽造FILE結(jié)構(gòu)體。利用fclose來(lái)實(shí)現(xiàn)攻擊。
漏洞利用
leak libc
題目給出了libc的文件,為了執(zhí)行l(wèi)ibc中的system命令,還需要獲取libc加載的基址。我們可以通過(guò)打開(kāi)/proc/self/mmap這個(gè)虛擬文件來(lái)獲取當(dāng)前進(jìn)程的地址空間情況。獲得到libc的加載基址后就可以計(jì)算出libc中system的偏移。接下來(lái)我們就可以利用_IO_FILE結(jié)構(gòu)體進(jìn)行攻擊。
構(gòu)造FILE
構(gòu)造FILE結(jié)構(gòu)體只需要關(guān)注兩個(gè)變量,第一個(gè)為FILE結(jié)構(gòu)體的_flags字段,只需要_flags & 0x2000為0就會(huì)直接調(diào)用_IO_FINSH(fp),_IO_FINISH(fp)相當(dāng)于調(diào)用fp->vtabl->__finish(fp)。
將fp指向一塊內(nèi)存P,P偏移0的前4字節(jié)設(shè)置為0xffffdfff,P偏移4位置放上要執(zhí)行的字符串指令(字符串以';'開(kāi)頭即可),P偏移sizeof(_IO_FILE)大小位置(vtable)覆蓋為內(nèi)存區(qū)域Q,Q偏移2*4字節(jié)處(vtable->__finish)覆蓋為system函數(shù)地址即可。
glibc fclose源碼學(xué)習(xí)
glibc的版本為2.23.90,復(fù)制粘貼比較多,主要是為了方便查閱。以下內(nèi)容對(duì)大部分的fclose函數(shù)進(jìn)行了層層解剖,很多部分與漏洞利用無(wú)太大關(guān)系,按需取用。
_IO_FILE與_IO_FILE_plus結(jié)構(gòu)體
在閱讀fclose前先來(lái)了解一些有關(guān)于FILE結(jié)構(gòu)體的知識(shí)。
在C語(yǔ)言中,成功調(diào)用fopen函數(shù)后會(huì)在堆上分配一塊空間用于存放_(tái)IO_FILE_plus結(jié)構(gòu)體,并且返回結(jié)構(gòu)體的首地址。閱讀源碼可以發(fā)現(xiàn)_IO_FILE_plus結(jié)構(gòu)體只是在_IO_FILE結(jié)構(gòu)體后添加了一個(gè)虛表指針 vtable。
/* _IO_FILE_plus結(jié)構(gòu)體 */
/* in libio/libioP.h */
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
虛表指針指向了如下的一個(gè)結(jié)構(gòu)體。JUMP_FIELD是一個(gè)接收兩個(gè)參數(shù)的宏,前一個(gè)參數(shù)為類(lèi)型名,后一個(gè)為變量名。結(jié)構(gòu)體的前兩個(gè)變量實(shí)際上不會(huì)被使用到,所以默認(rèn)為0,其余的變量存儲(chǔ)著不同的函數(shù)指針,在使用FILE結(jié)構(gòu)體進(jìn)行IO操作的過(guò)程中會(huì)通過(guò)這些函數(shù)指針調(diào)用到對(duì)應(yīng)的函數(shù)。
/*_IO_jump_t虛表結(jié)構(gòu)體*/
/* in libio/libioP.h */
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
_IO_FILE結(jié)構(gòu)體的定義如下。__flags FILE結(jié)構(gòu)體的一些狀態(tài);_markers為指向markers結(jié)構(gòu)體的指針變量,為一個(gè)單向鏈表結(jié)構(gòu),存放流的位置;_chain變量為一個(gè)鏈表的指針,進(jìn)程中創(chuàng)建的FILE結(jié)構(gòu)體會(huì)通過(guò)這個(gè)變量連成一個(gè)單向鏈表;
另一點(diǎn)需要注意的是在新版本中,_IO_FILE_complete結(jié)構(gòu)體被刪除,其中的字段被添加到_IO_FILE結(jié)構(gòu)體中
/*_IO_FILE結(jié)構(gòu)體*/
/* libio/libio.h */
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
# else
void *__pad1;
void *__pad2;
void *__pad3;
void *__pad4;
# endif
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};
fclose代碼
為了精簡(jiǎn)我刪掉了一部分關(guān)系不大的代碼,下面的函數(shù)是新版本的fclose代碼,IO_old_fclose主要代碼與新版本類(lèi)似
/* libio/iofclose.c */
int
_IO_new_fclose (_IO_FILE *fp)
{
int status;
/*這里本來(lái)有個(gè)對(duì)版本進(jìn)行檢測(cè)的代碼,根據(jù)FILE結(jié)構(gòu)中_vtable_offset變量是否為0來(lái)判斷,不為0則執(zhí)行_IO_old_fclose*/
/* First unlink the stream. */
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
if (fp->_mode > 0)
{
#if _LIBC
/* This stream has a wide orientation. This means we have to free
the conversion functions. */
struct _IO_codecvt *cc = fp->_codecvt;
__libc_lock_lock (__gconv_lock);
__gconv_release_step (cc->__cd_in.__cd.__steps);
__gconv_release_step (cc->__cd_out.__cd.__steps);
__libc_lock_unlock (__gconv_lock);
#endif
}
else
{
if (_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_IO_file_flags = 0;
free(fp);
}
return status;
}
該函數(shù)的流程可以粗略地進(jìn)行如下表示:
fclose flowchart.png
流程圖中有幾個(gè)關(guān)鍵函數(shù),至于加解鎖什么的我忽略了:
_IO_un_link
_IO_file_close_it
_IO_FINISH
_IO_free_backup_area
free
在檢查vtable_offset==0之后函數(shù)對(duì)fp->_flags的_IO_IS_FILEBUF位進(jìn)行檢查,_IO_IS_FILEBUF定義如下
#define _IO_IS_FILEBUF 0x2000
若該位不為0則調(diào)用_IO_un_link(fp)將fp指向的FILE結(jié)構(gòu)體從_IO_list_all的單向鏈表中取下,并調(diào)用_IO_file_close_it(fp)關(guān)閉fp。
然后將調(diào)用_IO_FINISH(fp),相當(dāng)于執(zhí)行((struct IO_FILE_plus *)fp->vtable)->__finish(fp)。
_IO_un_link
/* in libio/genops.c */
void
_IO_un_link (struct _IO_FILE_plus *fp)
{
if (fp->file._flags & _IO_LINKED)
{
struct _IO_FILE **f;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (_IO_FILE *) fp;
_IO_flockfile ((_IO_FILE *) fp);
#endif
if (_IO_list_all == NULL)
;
else if (fp == _IO_list_all)
{
_IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
++_IO_list_all_stamp;
}
else
for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)
if (*f == (_IO_FILE *) fp)
{
*f = fp->file._chain;
++_IO_list_all_stamp;
break;
}
fp->file._flags &= ~_IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((_IO_FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
_IO_un_link首先判斷fp的標(biāo)志位中的_IO_LINKED是否置位,若置位進(jìn)行下一步操作,最后將其清零
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
若_IO_list_all != fp則_IO_un_link函數(shù)將從_IO_list_all開(kāi)始遍歷鏈表,尋找fp指針,找到后將其前一個(gè)節(jié)點(diǎn)指針指向后一個(gè)節(jié)點(diǎn)指針即指向fp->file._chain;若_IO_list_all==fp則將全局變量_IO_list_all的值更改為IO_list_all->file._chain。
_IO_file_close_it
/* in libio/fileops.c */
/* 在新版本中 _IO_file_close_it被定義為_(kāi)IO_new_file_close_it */
int
_IO_new_file_close_it (_IO_FILE *fp)
{
int write_status;
if (!_IO_file_is_open (fp))
return EOF;
if ((fp->_flags & _IO_NO_WRITES) == 0
&& (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
write_status = _IO_do_flush (fp);
else
write_status = 0;
_IO_unsave_markers (fp);
int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
? _IO_SYSCLOSE (fp) : 0);
/* Free buffer. */
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
if (fp->_mode > 0)
{
if (_IO_have_wbackup (fp))
_IO_free_wbackup_area (fp);
_IO_wsetb (fp, NULL, NULL, 0);
_IO_wsetg (fp, NULL, NULL, NULL);
_IO_wsetp (fp, NULL, NULL);
}
#endif
_IO_setb (fp, NULL, NULL, 0);
_IO_setg (fp, NULL, NULL, NULL);
_IO_setp (fp, NULL, NULL);
_IO_un_link ((struct _IO_FILE_plus *) fp);
fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;
return close_status ? close_status : write_status;
}
_IO_new_file_close_it首先根據(jù)fp->_fileno是否為0判斷文件是否打開(kāi)
#define _IO_file_is_open(__fp) ((__fp)->_fileno != -1)
若文件未打開(kāi),則直接返回EOF。否則函數(shù)將繼續(xù)執(zhí)行
if ((fp->_flags & _IO_NO_WRITES) == 0
&& (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
write_status = _IO_do_flush (fp);
以上代碼將fp中未輸出的部分輸出,_IO_do_flush(fp)定義如下
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
# define _IO_do_flush(_f) \
((_f)->_mode <= 0 \
? _IO_do_write(_f, (_f)->_IO_write_base, \
(_f)->_IO_write_ptr-(_f)->_IO_write_base) \
: _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base, \
((_f)->_wide_data->_IO_write_ptr \
- (_f)->_wide_data->_IO_write_base)))
#else
# define _IO_do_flush(_f) \
_IO_do_write(_f, (_f)->_IO_write_base, \
(_f)->_IO_write_ptr-(_f)->_IO_write_base)
#endif
不做過(guò)多解釋。
然后fclose將調(diào)用_IO_unsave_markers(fp)將保存的markers清除,在這個(gè)版本的libc代碼中,這個(gè)函數(shù)有一部分功能還沒(méi)完成,用(#define TODO圍著),唯一值得注意的是函數(shù)最后
if (_IO_have_backup (fp))
_IO_free_backup_area (fp);
void
_IO_free_backup_area (_IO_FILE *fp)
{
if (_IO_in_backup (fp))
_IO_switch_to_main_get_area (fp); /* Just in case. */
free (fp->_IO_save_base);
fp->_IO_save_base = NULL;
fp->_IO_save_end = NULL;
fp->_IO_backup_base = NULL;
}
如果fp->_IO_save_base不為空,它將被free。
之后在_IO_new_file_close_it中執(zhí)行了
int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
? _IO_SYSCLOSE (fp) : 0);
當(dāng)fp->_flags2的_IO_FLAGS2_NOCLOSE沒(méi)有被置位時(shí),會(huì)調(diào)用_IO_SYSCLOSE(fp),相當(dāng)于調(diào)用_IO_FILE_plus結(jié)構(gòu)體中的vtable中的__close函數(shù)。這一次調(diào)用_IO_un_link好像并沒(méi)有實(shí)際作用?
最后又調(diào)用了_IO_un_link(fp)并設(shè)置了一些flags
_IO_un_link ((struct _IO_FILE_plus *) fp);
fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;
_IO_have_backup
上面已經(jīng)提到了,略略略
free(fp)
用戶(hù)打開(kāi)的FILE結(jié)構(gòu)體是分配在堆上的,在fclose中最終會(huì)被free釋放。
至此glibc的fclose源代碼分析完畢。
總結(jié)
以上是生活随笔為你收集整理的64位c语言调用32位glibc,glibc fclose源代码阅读及伪造_IO_FILE利用fclose实现任意地址执行...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mitmproxy抓包 | Python
- 下一篇: 线段树 离散化