这题是在glibc2.29下的利用,所以记录下解题学到的一些新颖的思路
程序分析
保护全开,有seccomp:
1 | ruan@ruan:/mnt/hgfs/shared/hitcon2019/one_punch$ seccomp-tools dump ./one_punch |
retire函数里的UAF:
1 | void retire() |
debut函数会先把我们的输入读入到栈上,然后才复制到申请的堆块中,所以可以利用这来进行rop
1 | unsigned __int64 __fastcall debut(__int64 a1, __int64 a2) |
一个后门选项:
1 | __int64 __fastcall sub_15BB(__int64 a1, __int64 a2) |
题目的debut函数用的是calloc函数,意味着进入了tcache的堆块是不会在被取出来了,但是后门函数里用的是malloc,所以我们的目标就是要使得*(_BYTE *)(heap_base + 0x20) > 6,已达到利用后门的效果
思路1
很自然的想到要是能用unsortedbin attack就好了,但是这在libc2.29下是行不通的:
1 | while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) |
我后来谷歌了下wp,找到了一篇wp,里面用的方法有点类似于unsortedbin attack,不得不佩服大佬的思路,orz
文章里提到的方法是,当从smallbin里申请一个堆块的时候,会把剩下的smallbin也链入相对应大小的tcache,前提是相应大小的tcache没满,相对应的源码为:
1 | if (in_smallbin_range (nb)) |
此处是没有对smallbin进行check的:
1 | if (tc_victim != 0) |
所以我们可以伪造tc_victim->bk,然后到了bck->fd=bin这一句,就可以向一个地址写入一个libc的值了,类似于unsortedbin attack,要注意的话就是相对应大小的tcache bin为6个,这样的话tcache_put后,就会退出循环,把chunk返回,不会造成段错误
这里有个大问题,就是程序申请的堆块大小范围在0x7f~0x400之间,所以在tcache没满的情况下,free后都会进入tcache,那要怎么让一个大小的堆块,比如0x100大小的堆块,相对应的tcache bin有6块,smallbin有两块,文章里又提到了用last_remainder:
1 | if (in_smallbin_range (nb) && |
比如我们把unsortedbin切成了0x100的大小,如果在calloc一个比这个大的chunk,那这个unsortedbin就会被放到smallbin,相对应的源码为:
1 | /* place chunk in bin */ |
这样的话一切条件都有了,真正的one_punch
还有一点要注意的是,我们用这个方法把heap+0x30的地方改写了,这样的话其实tcache会 corrupt 掉:
1 | pwndbg> bins |
所以我们要在攻击前先申请一个0x217大小的堆块,然后释放掉,在攻击
exp为:
1 | from pwn import * |
思路2
这思路是看ex师傅发在安全客上的wp学到的,tql!
我们知道后门函数要满足*(_BYTE *)(heap_base + 0x20) > 6这个条件,而*(_BYTE *)(heap_base + 0x20)这个位置对应的是tcache 0x220大小堆块的tcache->count,所以如果填满了的话即使进入了后门函数也不会申请到我们想要的地址,所以要控制tcache->entries,这样的话就可以达到任意地址malloc的目的了
原文思路很清晰:
- 用 UAF 构造 chunk overlap
- 用 tcache->counts 来伪造 size, 用 tcache->entries 伪造 fake_chunk 的 fd 和 bk,提前布置好 堆布局,以便绕过 unlink 检查。
- unlink 控制 tcache->entries,劫持hook控制程序流
在unlink前,堆布局是这样的:
unlink后我们就可以控制到 tcache_perthread_struct ->entries,就可以劫持到hook来控制程序执行流了
这里换劫持__free_hook来orw,用了个超好用的gadget:0x000000000012be97: mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax; ,学到了
exp照着wp里来的:
1 | from pwn import * |
思路3
思路3是Balsn战队的wp里用到的largebin_attack,首先我觉得一个难点是这题申请的堆块最大为0x410,怎么把大小比0x410还大的unsortedbin放入largebin是第一个要解决的问题,所以从源码入手:
1 | /* |
这是判断是否要把last remainder进行切割的代码,如果条件不满足的话就会进入下面的代码:
1 | /* remove from unsorted list */ |
所以wp里的堆布局为:
这样当我们malloc(0x200)的堆块时,就会不满足bck == unsorted_chunks (av)和if (size == nb)从而把这个chunk(0x5601e80414c0)置入largebin中,第二次循环的时候,发现unsorted bin的size刚刚好,直接就取出返回
1 | largebins |
这样的话就解决了这个问题,剩下的就是largebin_attack了,代码为:
1 | if (in_smallbin_range (size)) |
所以我们伪造好fwd->bk_nextsize,随后的victim->bk_nextsize->fd_nextsize = victim;就会在fwd->bk_nextsize+0x20的位置写入一个victim,如果我们让这个堆地址写入到heap_base+0x20的位置就能使用后门函数了
把unsortedbin放入largebin之前,(这里插入是不满足victim == av->last_remainder这个条件
放入后:
后续利用和思路1一样,劫持__malloc_hook为add rsp,xx; ret进行rop
参考链接
https://medium.com/@ktecv2000/hitcon-ctf-2019-quals-one-punch-man-pwn-292pts-3e94eb3fd312
https://balsn.tw/ctf_writeup/20191012-hitconctfquals/#one-punch-man