前言
众所周知,glibc
高版本逐渐移除了__malloc_hook/__free_hook/__realloc_hook
等等一众 hook
全局变量,ctf
中 pwn
题对 hook
钩子的利用将逐渐成为过去式。而想要在高版本利用成功,基本上就离不开对 IO_FILE
结构体的伪造与 IO
流的攻击。之前很多师傅都提出了一些优秀的攻击方法,比如 house of pig、house of kiwi 和 house of emma等。
其中,house of pig
除了需要劫持 IO_FILE
结构体,还需要劫持 tcache_perthread_struct
结构体或者能控制任意地址分配;house of kiwi
则至少需要修改三个地方的值:_IO_helper_jumps + 0xA0
和_IO_helper_jumps + 0xA8
,另外还要劫持_IO_file_jumps + 0x60
处的_IO_file_sync
指针;而 house of emma
则至少需要修改两个地方的值,一个是 tls
结构体的 point_guard
(或者想办法泄露出来),另外需要伪造一个 IO_FILE
或替换 vtable
为 xxx_cookie_jumps
的地址。
总的来看,如果想使用上述方法成功地攻击 IO
,至少需要两次写或者一次写和一次任意地址读。而在只给一次任意地址写(如一次 largebin attack
)的情景下是很难利用成功的。
largebin attack
是高版本中为数不多的可以任意地址写一个堆地址的方法,并常常和上述三种方法结合起来利用。本文将给出一种新的利用方法,在仅使用一次 largebin attack
并限制读写次数的条件下进行 FSOP
利用。顺便说一下,house of banana 也只需要一次 largebin attack
,但是其攻击的是 rtld_global
结构体,而不是 IO
流。
上述方法利用成功的前提均是已经泄露出 libc
地址和 heap
地址。本文的方法也不例外。
利用条件
使用 house of apple
的条件为: 1、程序从 main
函数返回或能调用 exit
函数 2、能泄露出 heap
地址和 libc
地址 3、 能使用一次 largebin attack
(一次即可)
利用原理
原理解释均基于 amd64
程序。
当程序从 main
函数返回或者执行 exit
函数的时候,均会调用 fcloseall
函数,该调用链为:
-
exit
-
_IO_cleanup
-
_IO_OVERFLOW
-
_IO_flush_all_lockp
-
fcloseall
最后会遍历_IO_list_all
存放的每一个 IO_FILE
结构体,如果满足条件的话,会调用每个结构体中 vtable->_overflow
函数指针指向的函数。
使用 largebin attack
可以劫持_IO_list_all
变量,将其替换为伪造的 IO_FILE
结构体,而在此时,我们其实仍可以继续利用某些 IO
流函数去修改其他地方的值。要想修改其他地方的值,就离不开_IO_FILE
的一个成员_wide_data
的利用。
|
|
amd64
程序下,struct _IO_wide_data *_wide_data
在_IO_FILE
中的偏移为 0xa0
:
|
|
我们在伪造_IO_FILE
结构体的时候,伪造_wide_data
变量,然后通过某些函数,比如_IO_wstrn_overflow
就可以将已知地址空间上的某些值修改为一个已知值。
|
|
分析一下这个函数,首先将 fp
强转为_IO_wstrnfile *
指针,然后判断 fp->_wide_data->_IO_buf_base != snf->overflow_buf
是否成立(一般肯定是成立的),如果成立则会对 fp->_wide_data
的_IO_write_base
、_IO_read_base
、_IO_read_ptr
和_IO_read_end
赋值为 snf->overflow_buf
或者与该地址一定范围内偏移的值;最后对 fp->_wide_data
的_IO_write_ptr
和_IO_write_end
赋值。
也就是说,只要控制了 fp->_wide_data
,就可以控制从 fp->_wide_data
开始一定范围内的内存的值,也就等同于任意地址写已知地址。
这里有时候需要绕过_IO_wsetb
函数里面的 free
:
|
|
_IO_wstrnfile
涉及到的结构体如下:
|
|
其中,overflow_buf
相对于_IO_FILE
结构体的偏移为 0xf0
,在 vtable
后面。
而 struct _IO_wide_data
结构体如下:
|
|
换而言之,假如此时在堆上伪造一个_IO_FILE
结构体并已知其地址为 A
,将 A + 0xd8
替换为_IO_wstrn_jumps
地址,A + 0xc0
设置为 B
,并设置其他成员以便能调用到_IO_OVERFLOW
。exit
函数则会一路调用到_IO_wstrn_overflow
函数,并将 B
至 B + 0x38
的地址区域的内容都替换为 A + 0xf0
或者 A + 0x1f0
。
简单写一个 demo
程序进行验证:
|
|
输出结果如下:
|
|
从输出中可以看到,已经成功修改了 sdterr->_wide_data
所指向的地址空间的内存。
利用思路
从上面的分析可以,在只给了 1
次 largebin attack
的前提下,能利用_IO_wstrn_overflow
函数将任意地址空间上的值修改为一个已知地址,并且这个已知地址通常为堆地址。那么,当我们伪造两个甚至多个_IO_FILE
结构体,并将这些结构体通过 chain
字段串联起来就能进行组合利用。基于此,我总结了 house of apple
下至少四种利用思路。
思路一:修改 tcache
线程变量
该思路需要借助 house of pig
的思想,利用_IO_str_overflow
中的 malloc
进行任意地址分配,memcpy
进行任意地址覆盖。其代码片段如下:
|
|
利用步骤如下:
-
伪造至少两个
_IO_FILE
结构体 -
第一个
_IO_FILE
结构体执行_IO_OVERFLOW
的时候,利用_IO_wstrn_overflow
函数修改tcache
全局变量为已知值,也就控制了tcache bin
的分配 -
第二个
_IO_FILE
结构体执行_IO_OVERFLOW
的时候,利用_IO_str_overflow
中的malloc
函数任意地址分配,并使用memcpy
使得能够任意地址写任意值 -
利用两次任意地址写任意值修改
pointer_guard
和IO_accept_foreign_vtables
的值绕过_IO_vtable_check
函数的检测(或者利用一次任意地址写任意值修改libc.got
里面的函数地址,很多IO
流函数调用strlen/strcpy/memcpy/memset
等都会调到libc.got
里面的函数) -
利用一个
_IO_FILE
,随意伪造vtable
劫持程序控制流即可
因为可以已经任意地址写任意值了,所以这可以控制的变量和结构体非常多,也非常地灵活,需要结合具体的题目进行利用,比如题目中_IO_xxx_jumps
映射的地址空间可写的话直接修改其函数指针即可。
思路二:修改 mp_
结构体
该思路与上述思路差不多,不过对 tcachebin
分配的劫持是通过修改 mp_.tcache_bins
这个变量。打这个结构体的好处是在攻击远程时不需要爆破地址,因为线程全局变量、tls
结构体的地址本地和远程并不一定是一样的,有时需要爆破。
利用步骤如下:
-
伪造至少两个
_IO_FILE
结构体 -
第一个
_IO_FILE
结构体执行_IO_OVERFLOW
的时候,利用_IO_wstrn_overflow
函数修改mp_.tcache_bins
为很大的值,使得很大的chunk
也通过tcachebin
去管理 -
接下来的过程与上面的思路是一样的
思路三:修改 pointer_guard
线程变量之 house of emma
该思路其实就是 house of apple + house of emma
。
利用步骤如下:
-
伪造两个
_IO_FILE
结构体 -
第一个
_IO_FILE
结构体执行_IO_OVERFLOW
的时候,利用_IO_wstrn_overflow
函数修改tls
结构体pointer_guard
的值为已知值 -
第二个
_IO_FILE
结构体用来做house of emma
利用即可控制程序执行流
思路四:修改 global_max_fast
全局变量
这个思路也很灵活,修改掉这个变量后,直接释放超大的 chunk
,去覆盖掉 point_guard
或者 tcache
变量。我称之为 house of apple + house of corrision
。
利用过程与前面也基本是大同小异,就不在此详述了。
其实也有其他的思路,比如还可以劫持 main_arena
,不过这个结构体利用起来会更复杂,所需要的空间将更大。而在上述思路的利用过程中,可以选择错位构造_IO_FILE
结构体,只需要保证关键字段满足要求即可,这样可以更加节省空间。
例题分析
这里以某次市赛的题为例,题目为 pwn_oneday
,附件下载链接在这里。
这个题目禁止了 execve
系统调用,能分配的 chunk
的大小基本是固定的,并且只允许 1
次读和 1
次写,最多只能分配 0x10
次,使用的 glibc
版本为 2.34
。
题目分析
initial
首先是初始化,开启了沙盒:
main
main
函数必须选一个 key
,大小在 6-10
。也就是说,分配的 chunk
都会属于 largebin
范围。
add
限制了只能分配 key+0x10
、key+0x20
、2 * key + 0x10
大小的 chunk
dele
存在 UAF
,没有清空指针。
read
只给 1
次机会读。
write
只给一次机会写,并只泄露出 0x10
个字节的数据。
利用过程
这道题的限制还是很多的,当然,给的漏洞也很明显。但是程序里面没有使用与 IO
有关的函数,全部使用原始的 read/write
去完成读写操作,并且使用 glibc-2.34
版本,这个版本里面去掉了很多的 hook
变量。
很明显,需要使用一次读泄露出 libc
地址和 heap
地址,然后用一次写做一次 largebin attack
。
如果用 largebin attack
去劫持_rtld_global
的 link_map
成员,那么还需要一次写去绕过 for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
,否则这里会造成死循环;如果打 l_addr
成员,会发现能分配的堆的空间不足,l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
的值为 0x201d70
,而就算每次分配 0xaa0 * 2 + 0x10
,再分配 16
次也没这么大。至于劫持别的成员,受限于沙盒,也很难完成利用。
由于限制了读写次数为 1
次,就很难再泄露出 pointer_guard
的值,也很难再覆盖 pointer_guard
的值,所以与 pointer_guard
有关的利用也基本行不通。
因此,选择使用 house of apple
劫持_IO_FILE->_wide_data
成员进行利用。
在利用之前,还有一些准备工作需要做。我们需要进行合理的堆风水布局,使得能够在修改一个 largebin chunk A
的 bk_nextsize
的同时伪造一个 chunk B
,并需要让 A
和 B
在同一个 bins
数组中,然后释放 B
并进行 largebin attack
,这样就能保证既完成任意地址写堆地址,也能控制写的堆地址所属的 chunk
的内容。
对三种大小 chunk
的 size
进行分析,并设 x = key + 0x10
,y = key + 0x20
, z = key * 2 + 0x10
。那么有:
|
|
题目中还存在 UAF
,于是就可以的构造出如下布局:
堆风水步骤为:
-
释放
chunk 1
,并将其置于largebin
中 -
利用一次写的机会,修改
chunk 2
,此时修改了chunk1
的bk_nextsize
,并伪造一个chunk 3
-
释放
chunk 3
,在其入链的过程中触发largebin attack
,即可任意地址写一个堆地址
经过计算,这里选择 key
为 0xa
,此时 chunk 1
的大小为 0xab0
,伪造的 chunk 3
的大小为 0xa80
。
基于上面对 house of apple
的分析,首先使用思路三修改 pointer_guard
,然后进行 house of emma
利用。由于 pointer_guard
是 fs:[0x30]
,而 canary
是 fs:[0x28]
,所以直接找 canary
,然后利用 pwndbg
的 search
命令搜索即可,如下所示:
此时的利用步骤如下:
-
利用一次
write
的机会泄露出libc
地址和heap
地址 -
利用堆风水,构造
1
次largebin attack
,替换_IO_list_all
为堆地址 -
利用
house of apple
,修改掉pointer_guard
的值 -
利用
house of emma
并结合几个gadgets
控制rsp
-
用
rop
链输出flag
其 exp
如下:
|
|
调试截图如下:
修改掉 pointer_guard
:
然后使用_IO_cookie_read
控制程序执行流:
成功劫持 rsp
:
接下来使用思路一,修改 tcache
变量,对于该变量的寻找同样可以使用 search
命令:
此时的步骤如下:
-
使用
house of apple
修改tcache
变量为可控堆地址 -
使用
_IO_str_overflow
完成任意地址写任意值,由于_IO_str_jumps
区域是可写的,所以我选择覆盖这里 -
仍然利用一些
gadgets
劫持rsp
,然后rop
泄露出flag
exp
如下:
|
|
调试截图:
修改掉 tache
变量:
然后分配到_IO_str_jumps
:
后面的过程就一样了:
最后成功输出 flag
:
总结
之前的一些 IO
流攻击方法对_wide_data
的关注甚少,本文提出一种新的方法,劫持了_wide_data
成员并在仅进行 1
次 largebin attack
的条件下成功进行 FSOP
利用。且该方法可通杀所有版本的 glibc
。
可以看到,house of apple
是对现有一些 IO
流攻击方法的补充,能在一次劫持 IO
流的过程中做到任意地址写已知值,进而构造出其他方法攻击成功的条件。
来源:https://xz.aliyun.com/ 感谢【 roderick】
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论