前言:
最近在学习的过程中,遇到一个很有趣的东西,就是IO_FILE
和Largebin Unsortbin attack
的结合利用,这个技巧能延伸出来很多种利用方式。
正文:
就拿最近的*CTF上的heap_master
来举例。
因为本文主讲利用技巧,所以具体程序分析这里就略过了。程序在mmap
区域上对堆进行增删改,所以要想构造利用,就得在mmap
区域上构造chunk。以下均在libc-2.23环境下进行。
漏洞点:
有一个类似于UAF的漏洞点。
利用初探:
程序没有show函数,那么便很容易想到用修改stdout
的方式来泄漏,那么该怎么去修改呢,从UAF角度分析,可以利用UAF来达到Unsortbin attack
和Largebin attack
。
利用思考:
Unsortbin attack
只能任意地址写一个libc地址的值,该如何把这一次任意写利用最大化呢,那么就是修改global_max_fast
。这样我们就可以得到glibc上的任意地址写堆地址,因为很大的chunk都变成了fastbin,因此越界了规定内的fastbin_index
,导致可以在任意写堆地址。
用图来表示就是:
所以可以任意写堆地址。
我们可以覆盖stdout
,使得stdout
指向我们的mmap空间,并且我们事先在mmap空间构造好_IO_2_1_stdout
,导致在打印出程序菜单之前先泄漏了地址。结果为这样:
如图0x57e5c100
开始就是我们事先构造好的_IO_2_1_stdout
,有的人或许会想问,0x7f
那些地址怎么来的?很简单,事先构造0x91
的chunk,free后又add,即可得到libc上的地址,再把低位双字节改成_IO_2_1_stdout
上的内容,就有1/16
的概率能够撞到。
泄漏出来:
泄漏出地址了,下一步便是劫持程序流了。
这里我们可以利用2.24版本后的IO_FILE
利用,先劫持_IO_list_all
,再接着构造_IO_list_all
,触发_IO_flush_all_lockp
。
覆盖就很容易了,跟前面所覆盖的stdout
一样,而构造过程需要根据后续调用来构造了。我们需要触发_IO_str_jumps
上的overflow
。通过以下代码来劫持:
1 | int |
劫持程序流:
1 | new_buf |
我们所需要bypass的几个条件:
1 | 1. fp->_flags & _IO_NO_WRITES为假 |
这里我们已经可以控制rip和rdi了,我构造如下:
1 | _IO_FILE = ( p64(0) + |
但是单单控制了rip和rdi还不够,我们还需要把栈空间给转移到mmap上来。
观察上面可以看到,我们先把程序流劫持到这里来:
1 | 0x00007f20066f4b75 <+53>: mov rsp,QWORD PTR [rdi+0xa0] |
从第一条语句我们就可以转移栈空间,因为rdi我们可控。中间的rcx可以用__morecore
维持平衡。
最后栈会成功转移到我们的mmap区域来,所以事先在mmap区域构造好ROP即可劫持整个程序流。
利用延伸:
延伸点1:
可以有别的劫持流吗?当然可以。
我们还可以不劫持_IO_list_all
,换个方式,劫持_dl_open_hook
。
_dl_open_hook
是怎么个说法呢?它跟__free_hook
类似,但是又不一样,区别就在于当它不为NULL时,执行的是**_dl_open_hook
,而__free_hook
是执行*__free_hook
。触发条件是当malloc或free出错时。
当执行到**_dl_open_hook
时,rax存的就是*_dl_open_hook
,即堆地址。所以我找到了这么一处gadgets
:
1 | => 0x00007fd2f8d9a98a <+170>: mov rdi,rax |
这样,我们也控制了rdi
,往后可以构造劫持到上面所说的转移栈空间的那处gadgets
。后面的流程也一样了。
延伸点2:
那么largebin attack
呢?
largebin attack
实际上也是任意地址修改为堆地址,发生的链表修改操作为:
1 | fwd->bk_nextsize->fd_nextsize = victim; |
通过调试可知这里的任意修改为第二条,每次largebin attack
可任意修改一次为堆地址。实质上跟unsortbin attack
没有太大的区别,只是修改方式不一样。
但是这里可以换一种方式泄漏libc地址。
可以去修改_IO_2_1_stdout
的_flag
为堆地址。因为flag
满足一定的条件时,就可以泄漏:
1 | if fp->flag & 0xa00 == 1 and fp->flag & 0x1000 == 1 then it will leak something when f->write_base != f->write_ptr |
这里也是有一定概率的。除了修改完_flag
之后,还需要覆盖write_base
的最低一个字节为\x00
,这时候可以错位覆盖:
两处地方修改完之后的情况:
即可泄漏出地址。
往后的劫持程序流跟上面所说的一样,既可以劫持_dl_open_hook
也可以劫持_IO_list_all
。
延伸点3:
还可以如何劫持程序流程?可以劫持__free_hook
。
大致流程就是用largebin attack
泄漏出地址后(跟上面延伸2一致),再用largebin attack
修改global_max_fast
。这样就可以来利用fastbin_index_overflow
了。
覆盖__free_hook
为堆地址之后,修改该堆地址所对应的chunk
的fd
指针为system
地址。这样当把他add取出之后,__free_hook
地址就变为了system
的地址:
delete
之后即可触发。
当然了,__malloc_hook
、__relloc_hook
等等也是一样的。
利用总结:
题目还是很新颖的,从普通堆空间转化到了mmap区域上的堆空间来。可以大胆的去想思路,上面的有些思路仔细想的话其实还是很巧妙的,不管是从找gadgets
和整个劫持程序流程的构造来说都很巧妙,能够把几种思路都去试着练习一下还是能够收获到很多东西的,思路上、或者是构造利用上。而且上面的几种方式交叉组合一下利用,还能有着多种方式。
Reference:
- https://balsn.tw/ctf_writeup/20190427-*ctf/#heap-master
- https://xz.aliyun.com/t/2411
- https://xz.aliyun.com/t/5006#toc-15
- https://github.com/sixstars/starctf2019/blob/master/pwn-heap_master/hack.py
EXP:
1. Unsortbin attack + _IO_list_all
1 | from pwn import * |
2. Unsortbin attack + _dl_open_hook
1 | from pwn import * |
3. Largebin attack + _dl_open_hook
1 | from pwn import * |
4. Largebin attack + __free_hook
1 | from pwn import * |