-
-
[原创]看雪CTF.TSRC 2018 团队赛 第十四题
-
发表于: 2018-12-28 17:33 6136
-
程序功能很简单,循环5次,通过sub_AF0函数获取输入的长度,然后分配相应大小的堆保存输入的word,最后输出。
使用shecksec查看有以下保护:
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
获取word的输入没有长度检查,可以造成堆溢出。word的输出会造成格式化字符串漏洞。
1、通过格式化字符串漏洞泄漏出保存在栈中返回到__libc_start_main中的地址,从而可以计算出libc的基址。
2、利用堆溢出修改
top chunk
的大小,当不满足 malloc 的分配需求时,会通过sysmalloc 来向系统申请更多的空间 。对于堆来说有 mmap 和 brk 两种分配方式,我们需要让堆以 brk 的形式拓展,申请的大小不能超过默认的阈值也就是128k ,原有的top chunk就会被置于unsorted bin中 。top chunk的大小也会有合法性检测,检查如下:
assert((old_top == initial_top(av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse(old_top) && ((unsigned long)old_end & pagemask) == 0));
所以伪造的大小必须要对齐到内存页, 大于 MINSIZE(0x10),
小于之后申请的堆块 且size 的 prev inuse 位必须为 1。
3、 利用FSOP (File Stream Oriented Programming)原理以及house of orange原理控制程序流程。
FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中。FILE结构的定义如下:
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 };
进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用libc中的全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。需要注意的是 stdin、stdout、stderr 这三个文件流位于 libc的数据段中。但是事实上_IO_FILE 结构位于另一种结构_IO_FILE_plus中, _IO_FILE_plus的定义如下:
struct _IO_FILE_plus { _IO_FILE file; IO_jump_t *vtable; }
其中包含了一个重要的指针 vtable,它指向了一系列函数指针,
标准 IO 函数中会调用这些函数指针 。
gdb-peda$ p _IO_file_jumps $1 = { __dummy = 0x0, __dummy2 = 0x0, __finish = 0x7ffff7a8ed20 <_IO_new_file_finish>, __overflow = 0x7ffff7a8f700 <_IO_new_file_overflow>, __underflow = 0x7ffff7a8f4b0 <_IO_new_file_underflow>, __uflow = 0x7ffff7a90560 <__GI__IO_default_uflow>, __pbackfail = 0x7ffff7a91700 <__GI__IO_default_pbackfail>, __xsputn = 0x7ffff7a8e5a0 <_IO_new_file_xsputn>, __xsgetn = 0x7ffff7a8e2b0 <__GI__IO_file_xsgetn>, __seekoff = 0x7ffff7a8d8e0 <_IO_new_file_seekoff>, __seekpos = 0x7ffff7a90ad0 <_IO_default_seekpos>, __setbuf = 0x7ffff7a8d850 <_IO_new_file_setbuf>, __sync = 0x7ffff7a8d780 <_IO_new_file_sync>, __doallocate = 0x7ffff7a829b0 <__GI__IO_file_doallocate>, __read = 0x7ffff7a8e580 <__GI__IO_file_read>, __write = 0x7ffff7a8df70 <_IO_new_file_write>, __seek = 0x7ffff7a8dd70 <__GI__IO_file_seek>, __close = 0x7ffff7a8d840 <__GI__IO_file_close>, __stat = 0x7ffff7a8df60 <__GI__IO_file_stat>, __showmanyc = 0x7ffff7a91860 <_IO_default_showmanyc>, __imbue = 0x7ffff7a91870 <_IO_default_imbue> }
因此直接改写 vtable 中的函数指针或者是覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针就可以劫持程序流程 。该程序可以覆盖unsorted bin空闲块,修改bk指向_IO_list_all-0x10,同时布置fake file struct,然后分配堆块,触发unsorted bin attack 修改_IO_list_all指向main_arena+88,因为_chain域在_IO_list_all + 0x68的位置 ,也就是 main_arena + 88 + 0x68-->small bin中大小为0x60的位置,所以需要修改其大小为0x60 ,之后修改过的unsorted bin 会被放入 small bin [4]中,这样就可以伪造一个FILE结构,继续遍历unsorted bin会触发异常,调用malloc_printerr。调用栈如下:
malloc_printerr _libc_message(error msg) abort _IO_flush_all_lockp -> JUMP_FILE(_IO_OVERFLOW)
_IO_flush_all_lockp函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的
_IO_OVERFLOW
。控制
_IO_OVERFLOW 函数便就可以拿到shell。 libc2.24版本的_IO_flush_all_lockp定义如下:
int _IO_flush_all_lockp (int do_lock) { int result = 0; struct _IO_FILE *fp; int last_stamp; #ifdef _IO_MTSAFE_IO __libc_cleanup_region_start (do_lock, flush_cleanup, NULL); if (do_lock) _IO_lock_lock (list_all_lock); #endif last_stamp = _IO_list_all_stamp; fp = (_IO_FILE *) _IO_list_all; while (fp != NULL) { run_fp = fp; if (do_lock) _IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)//合法性检查 #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) #endif ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; if (do_lock) _IO_funlockfile (fp); run_fp = NULL; if (last_stamp != _IO_list_all_stamp) { /* Something was added to the list. Start all over again. */ fp = (_IO_FILE *) _IO_list_all; last_stamp = _IO_list_all_stamp; } else fp = fp->_chain; } #ifdef _IO_MTSAFE_IO if (do_lock) _IO_lock_unlock (list_all_lock); __libc_cleanup_region_end (0); #endif return result; }
所以伪造的结构体要满足(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)或者 (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) )。在 libc2.23 版本中可以直接修改伪造的vtable, 使得_IO_OVERFLOW=system_addr 。kkhaike大佬说该程序无法泄漏出堆顶地址 ,所以采用绕过libc2.24检查机制的方法来获取shell。libc2.24版本多了一个vtable合理性的检查机制,检查如下:
struct _IO_FILE_plus { _IO_FILE file; IO_jump_t *vtable; }
其中包含了一个重要的指针 vtable,它指向了一系列函数指针,
标准 IO 函数中会调用这些函数指针 。
gdb-peda$ p _IO_file_jumps $1 = { __dummy = 0x0, __dummy2 = 0x0, __finish = 0x7ffff7a8ed20 <_IO_new_file_finish>, __overflow = 0x7ffff7a8f700 <_IO_new_file_overflow>, __underflow = 0x7ffff7a8f4b0 <_IO_new_file_underflow>, __uflow = 0x7ffff7a90560 <__GI__IO_default_uflow>, __pbackfail = 0x7ffff7a91700 <__GI__IO_default_pbackfail>, __xsputn = 0x7ffff7a8e5a0 <_IO_new_file_xsputn>, __xsgetn = 0x7ffff7a8e2b0 <__GI__IO_file_xsgetn>, __seekoff = 0x7ffff7a8d8e0 <_IO_new_file_seekoff>, __seekpos = 0x7ffff7a90ad0 <_IO_default_seekpos>, __setbuf = 0x7ffff7a8d850 <_IO_new_file_setbuf>, __sync = 0x7ffff7a8d780 <_IO_new_file_sync>, __doallocate = 0x7ffff7a829b0 <__GI__IO_file_doallocate>, __read = 0x7ffff7a8e580 <__GI__IO_file_read>, __write = 0x7ffff7a8df70 <_IO_new_file_write>, __seek = 0x7ffff7a8dd70 <__GI__IO_file_seek>, __close = 0x7ffff7a8d840 <__GI__IO_file_close>, __stat = 0x7ffff7a8df60 <__GI__IO_file_stat>, __showmanyc = 0x7ffff7a91860 <_IO_default_showmanyc>, __imbue = 0x7ffff7a91870 <_IO_default_imbue> }
因此直接改写 vtable 中的函数指针或者是覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针就可以劫持程序流程 。该程序可以覆盖unsorted bin空闲块,修改bk指向_IO_list_all-0x10,同时布置fake file struct,然后分配堆块,触发unsorted bin attack 修改_IO_list_all指向main_arena+88,因为_chain域在_IO_list_all + 0x68的位置 ,也就是 main_arena + 88 + 0x68-->small bin中大小为0x60的位置,所以需要修改其大小为0x60 ,之后修改过的unsorted bin 会被放入 small bin [4]中,这样就可以伪造一个FILE结构,继续遍历unsorted bin会触发异常,调用malloc_printerr。调用栈如下:
malloc_printerr _libc_message(error msg) abort _IO_flush_all_lockp -> JUMP_FILE(_IO_OVERFLOW)
_IO_flush_all_lockp函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的
_IO_OVERFLOW
。控制
_IO_OVERFLOW 函数便就可以拿到shell。 libc2.24版本的_IO_flush_all_lockp定义如下:
gdb-peda$ p _IO_file_jumps $1 = { __dummy = 0x0, __dummy2 = 0x0, __finish = 0x7ffff7a8ed20 <_IO_new_file_finish>, __overflow = 0x7ffff7a8f700 <_IO_new_file_overflow>, __underflow = 0x7ffff7a8f4b0 <_IO_new_file_underflow>, __uflow = 0x7ffff7a90560 <__GI__IO_default_uflow>, __pbackfail = 0x7ffff7a91700 <__GI__IO_default_pbackfail>, __xsputn = 0x7ffff7a8e5a0 <_IO_new_file_xsputn>, __xsgetn = 0x7ffff7a8e2b0 <__GI__IO_file_xsgetn>, __seekoff = 0x7ffff7a8d8e0 <_IO_new_file_seekoff>, __seekpos = 0x7ffff7a90ad0 <_IO_default_seekpos>, __setbuf = 0x7ffff7a8d850 <_IO_new_file_setbuf>, __sync = 0x7ffff7a8d780 <_IO_new_file_sync>, __doallocate = 0x7ffff7a829b0 <__GI__IO_file_doallocate>, __read = 0x7ffff7a8e580 <__GI__IO_file_read>, __write = 0x7ffff7a8df70 <_IO_new_file_write>, __seek = 0x7ffff7a8dd70 <__GI__IO_file_seek>, __close = 0x7ffff7a8d840 <__GI__IO_file_close>, __stat = 0x7ffff7a8df60 <__GI__IO_file_stat>, __showmanyc = 0x7ffff7a91860 <_IO_default_showmanyc>, __imbue = 0x7ffff7a91870 <_IO_default_imbue> }
因此直接改写 vtable 中的函数指针或者是覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针就可以劫持程序流程 。该程序可以覆盖unsorted bin空闲块,修改bk指向_IO_list_all-0x10,同时布置fake file struct,然后分配堆块,触发unsorted bin attack 修改_IO_list_all指向main_arena+88,因为_chain域在_IO_list_all + 0x68的位置 ,也就是 main_arena + 88 + 0x68-->small bin中大小为0x60的位置,所以需要修改其大小为0x60 ,之后修改过的unsorted bin 会被放入 small bin [4]中,这样就可以伪造一个FILE结构,继续遍历unsorted bin会触发异常,调用malloc_printerr。调用栈如下:
malloc_printerr _libc_message(error msg) abort _IO_flush_all_lockp -> JUMP_FILE(_IO_OVERFLOW)
_IO_flush_all_lockp函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的
_IO_OVERFLOW
。控制
_IO_OVERFLOW 函数便就可以拿到shell。 libc2.24版本的_IO_flush_all_lockp定义如下:
malloc_printerr _libc_message(error msg) abort _IO_flush_all_lockp -> JUMP_FILE(_IO_OVERFLOW)
_IO_flush_all_lockp函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的
_IO_OVERFLOW
。控制
_IO_OVERFLOW 函数便就可以拿到shell。 libc2.24版本的_IO_flush_all_lockp定义如下:
int _IO_flush_all_lockp (int do_lock) { int result = 0; struct _IO_FILE *fp; int last_stamp; #ifdef _IO_MTSAFE_IO __libc_cleanup_region_start (do_lock, flush_cleanup, NULL); if (do_lock) _IO_lock_lock (list_all_lock); #endif last_stamp = _IO_list_all_stamp; fp = (_IO_FILE *) _IO_list_all; while (fp != NULL) { run_fp = fp; if (do_lock) _IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)//合法性检查 #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) #endif ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; if (do_lock) _IO_funlockfile (fp); run_fp = NULL; if (last_stamp != _IO_list_all_stamp) { /* Something was added to the list. Start all over again. */ fp = (_IO_FILE *) _IO_list_all; last_stamp = _IO_list_all_stamp; } else fp = fp->_chain; } #ifdef _IO_MTSAFE_IO if (do_lock) _IO_lock_unlock (list_all_lock); __libc_cleanup_region_end (0); #endif return result; }
所以伪造的结构体要满足(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)或者 (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) )。在 libc2.23 版本中可以直接修改伪造的vtable, 使得_IO_OVERFLOW=system_addr 。kkhaike大佬说该程序无法泄漏出堆顶地址 ,所以采用绕过libc2.24检查机制的方法来获取shell。libc2.24版本多了一个vtable合理性的检查机制,检查如下:
int _IO_flush_all_lockp (int do_lock) { int result = 0; struct _IO_FILE *fp; int last_stamp; #ifdef _IO_MTSAFE_IO __libc_cleanup_region_start (do_lock, flush_cleanup, NULL); if (do_lock) _IO_lock_lock (list_all_lock); #endif last_stamp = _IO_list_all_stamp; fp = (_IO_FILE *) _IO_list_all; while (fp != NULL) { run_fp = fp; if (do_lock) _IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)//合法性检查 #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) #endif ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; if (do_lock) _IO_funlockfile (fp); run_fp = NULL; if (last_stamp != _IO_list_all_stamp) { /* Something was added to the list. Start all over again. */ fp = (_IO_FILE *) _IO_list_all; last_stamp = _IO_list_all_stamp; } else fp = fp->_chain; } #ifdef _IO_MTSAFE_IO if (do_lock) _IO_lock_unlock (list_all_lock); __libc_cleanup_region_end (0); #endif return result; }
所以伪造的结构体要满足(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)或者 (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) )。在 libc2.23 版本中可以直接修改伪造的vtable, 使得_IO_OVERFLOW=system_addr 。kkhaike大佬说该程序无法泄漏出堆顶地址 ,所以采用绕过libc2.24检查机制的方法来获取shell。libc2.24版本多了一个vtable合理性的检查机制,检查如下:
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; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __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; }
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; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __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; }
可以使用__IO_str_jumps和__IO_wstr_jumps进行绕过, 使用__IO_str_jumps 更为简单,如何定位
__IO_str_jumps
参考这篇文章。
__IO_str_jumps
定义如下:
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2018-12-28 17:38
被会飞的鱼油编辑
,原因:
赞赏记录
参与人
雪币
留言
时间
一笑人间万事
为你点赞~
2022-7-27 01:50
心游尘世外
为你点赞~
2022-7-26 23:46
飘零丶
为你点赞~
2022-7-17 03:23
qux
为你点赞~
2018-12-31 12:10
深天深天
为你点赞~
2018-12-29 16:34
Editor
为你点赞~
2018-12-29 14:55
赞赏
他的文章
- vmp3.5模拟x86分支指令je、jne、jge和jl的分析 19623
- [原创]vmp trace的优化处理 18464
- [原创]基于LSTM的二进制代码相似性检测 21436
- [原创]混合布尔算术运算的混淆及反混淆 20141
- [原创]利用机器学习分析vmp的思路 18671
看原图
赞赏
雪币:
留言: