这题是在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