Balsn CTF 2019 pwn PlainText -- glibc-2.29 off by one pypass
TOC
真心不错的题目,这题引出了 glibc-2.29 off by one 的全新绕过方法,比赛时仅有RPISEC
战队做出来了,赛后我询问了该战队思路,并对其完成复现。
源程序下载:PlainNote.zip 。
致谢
首先,非常感谢RPISEC
战队的Jack Dates
所提供的思路,没有这个思路恐怕我还在思考怎么绕过prevsize check
。
漏洞
明显的 off off one
,难点在于环境是glibc-2.29
,由于其增加了新的检查,原先的方法都将失效。
void __cdecl add() |
主要失效原因是:glibc 在 unlink 的关键点都加上了 prevsize check
,而我们根本无法直接修改正常chunk的size,导致想要 unlink 变得几乎不可能。
if (__glibc_unlikely (chunksize(p) != prevsize)) |
思路
正如 Jack Dates
所提供的思路,我们不需要绞尽脑汁的去思考如何绕过 prevsize check
,我们只需要利用 large bin 的残留指针再结合堆的恰当布局,则能构造出一个fake chunk
,后面我将其称作fake_chunk_B
。
主要原理就是利用残余在 large bin
上的 fd_nextsize / bk_nextsize 指针。首先,我们拿回 large bin
,后面我将其称作chunk_A
,而 fake_chunk_B
就是 chunk_A
+ 0x10,在chunk_A
的 bk 位置上写好size,fd先不管,然后部分覆盖chunk_A
的 fd_nextsize 到一个我们可以控制其 bk 的 chunk上(比如从 small bin 或者 unsorted bin 中拿出的chunk,如果其bin中有多个chunk的话,那么拿出来的chunk的bk上必定残留了heap指针,我们可以通过部分覆盖使其指向 fake chunk,以便绕过unlink 检查),由于 chunk_A
的 bk_nextsize 我们并没对其修改,所以其指向的是 chunk_A
本身,为了绕过 unlink 检查( p->fd->bk == p && p->bk->fd == p),我们需要将这个该 fake_chunk_B
的 bk 指向其本身,也就是 chunk_A
的 fd 指向chunk_A
+ 0x10 并且不能修改已经保存好的其他数据,原本我们可以利用 tcache 的链表特性来完成这一操作,奈何 glibc-2.29的tcache会对 bk 也进行修改,那么则会直接改掉 fake_chunk_B
的 szie,导致unlink失败,但是我们任然可以利用 fastbin 的链表特性来完成这一操作,在chunk_A
上写好heap地址后,在进行部分覆盖使其指向chunk_A
+ 0x10,则这样就能绕过 glibc-2.29 的检查。
由于最后一个字节总是有’\0’填充,所以我们需要爆破0x..........00..
(点为任意十六进制)这样的heap地址。综上所诉该攻击方式的概率是 1/16
。
沙箱绕过
__int64 init() |
由于沙箱是白名单的形式,我们只能利用特定的系统的调用的来拿flag,而且printf
和puts
这类的函数都不能使用,还有setcontext
函数也并不能正常使用,因为其中使用了sys_rt_sigprocmask
。
setcontext
函数汇编如下:
.text:0000000000055E00 setcontext ; weak |
原本在 glibc-2.27 的话,参数直接是rdi
,而不会像这里这样转换到rdx
,导致不可以直接利用。
通过仔细观察gadgets,找到了一条非常好用的 gadget:mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax;
我们可以利用该 gadget 修改 rdx 的值,然后在配合 setcontext 进行 SROP 劫持rsp
到heap上,然后在进行ROP将flag读出即可。
脚本
#!/usr/bin/python2 |