2025全国大学生软件系统安全赛——VM题解
字数 1990 2025-08-22 12:22:54

2025全国大学生软件系统安全赛VM题解教学文档

1. 题目概述

这是一道虚拟机(VM)相关的PWN题目,具有以下特点:

  • 保护全开(包括ASLR、NX等)
  • 代码量大,逆向分析是主要难点
  • 使用自定义的VM架构
  • 通过vmdata和vmcode文件提供初始数据

2. 初始分析

2.1 程序启动流程

  1. 初始化函数(sub_30C0):

    • 设置三个关键指针:
      • qword_6120 → 0x64617461000
      • qword_6128 → 0x7063000
      • qword_6160 → 0x73746163000
  2. 读取外部数据(sub_3107):

    • 读取vmdata和vmcode文件
    • 将文件内容(跳过前4字节长度字段)分别写入0x64617461000和0x7063000

2.2 主要数据结构

  • VM内存布局:
    • 0x64617461000: 数据区域
    • 0x7063000: 代码区域
    • 0x73746163000: 可能为栈区域

3. VM指令解析

3.1 指令解码流程(sub_2F6F)

  1. 指令提取(sub_132D):

    • 从vmcode读取1字节
    • vmcode指针后移1字节
    • context[0] = 读取的字节
    • context[1] = 字节 & 3 (用于分支选择)
  2. 分支选择:
    根据context[1]的值(0-3)跳转到不同处理函数

3.2 各分支功能

分支3 (value & 3 == 3)

  • 处理函数(sub_2A49):
    • 根据vmcode >> 2选择操作
    • 主要操作形式: *(vmcode_6120 + 8 * (*(context + 4) + 2LL))
    • 关键操作(case 3): 直接赋值操作

分支2 (value & 3 == 2)

  • 处理函数(sub_23A7):
    • 数组元素间的赋值操作

分支1 (value & 3 == 1)

  • 处理函数(sub_2142):
    • 对数组的赋值操作

分支0 (value & 3 == 0)

  • 处理函数(sub_19AD):
    • 包含堆操作和内存读写功能
    • 漏洞点所在

4. 漏洞分析

4.1 关键函数(sub_19AD)

  1. 安全检查:

    • sub_12E8: 确保qword_6120[3] ≤ 0x30000
  2. 堆操作:

    • 可以申请16个无限制大小的堆块
    • free后未清空指针,存在UAF漏洞
  3. 内存操作:

    • read_from_memory: 从0x64617461000的偏移地址读取数据到堆中
    • write_to_memory: 将堆中的数据写入0x64617461000的偏移地址

4.2 可利用点

  1. 控制能力:

    • context可控
    • qword_6120数组下标≥2可控
  2. 利用链:

    • 通过choice3控制数组元素
    • 通过choice0进行堆操作和内存读写

5. 利用思路

5.1 阶段一: 泄露libc和heap地址

  1. 设置size=0x500,申请堆块0
  2. 修改size,申请堆块1
  3. 释放堆块0,使其进入largebin
  4. 申请比堆块0大的堆块2
  5. 利用write_to_memory读取堆块0中的libc和heap地址
  6. 通过write操作泄露地址

5.2 阶段二: 泄露栈地址

  1. 设置size=0x90,申请两个0x90堆块并释放
  2. 修改fd指向environ
  3. 重新申请,获取包含environ的堆块
  4. 读取environ中的栈地址
  5. 通过write操作泄露栈地址

5.3 阶段三: ROP攻击

  1. 设置size=0x80,申请两个0x80堆块并释放
  2. 修改fd指向返回地址(选择sub_132D函数中的返回地址)
  3. 写入ROP链
  4. 触发函数返回执行ROP

6. 完整利用代码分析

6.1 关键payload构造

# 设置size=0x500
code = p32(0x0500000f).ljust(10, b'\x00')  # choice3修改qword_6120[2+0]为0x500
code += p32(0x030000cc)  # 申请chunk 0

6.2 地址泄露

# 泄漏libc地址
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x21b110
# 泄漏heap地址
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x2b0

6.3 ROP链构造

# ROP链
payload = p64(stack_addr)*4 + p64(rdi) + p64(ret) + p64(rdi) + p64(bin_sh) + p64(system)

7. 关键技巧总结

  1. 逆向分析技巧:

    • 优先分析可能包含漏洞的分支(如choice0)
    • 不必完全理解所有指令功能,重点寻找可利用的读写原语
  2. 利用技巧:

    • 利用largebin泄露libc地址
    • 利用environ泄露栈地址
    • 精心选择返回地址位置确保ROP链执行
  3. VM PWN通用方法:

    • 理解VM的内存布局和指令集
    • 寻找内存读写和堆操作原语
    • 利用VM自身的功能构造利用链

8. 防御建议

  1. 对题目设计者:

    • 在free后清空指针
    • 限制堆块大小
    • 增加内存访问的边界检查
  2. 对参赛者(防御角度):

    • 注意UAF漏洞的检测
    • 检查所有内存读写操作的边界
    • 确保敏感数据(如指针)不被泄露

9. 扩展思考

  1. 其他可能的利用方式:

    • 利用VM的指令集实现任意代码执行
    • 篡改VM的关键数据结构实现控制流劫持
  2. 类似题目训练建议:

    • 研究其他VM PWN题目(如Defcon Quals中的VM题目)
    • 练习自定义指令集的逆向分析
    • 熟悉各种虚拟架构的内存管理方式
2025全国大学生软件系统安全赛VM题解教学文档 1. 题目概述 这是一道虚拟机(VM)相关的PWN题目,具有以下特点: 保护全开(包括ASLR、NX等) 代码量大,逆向分析是主要难点 使用自定义的VM架构 通过vmdata和vmcode文件提供初始数据 2. 初始分析 2.1 程序启动流程 初始化函数(sub_ 30C0) : 设置三个关键指针: qword_ 6120 → 0x64617461000 qword_ 6128 → 0x7063000 qword_ 6160 → 0x73746163000 读取外部数据(sub_ 3107) : 读取vmdata和vmcode文件 将文件内容(跳过前4字节长度字段)分别写入0x64617461000和0x7063000 2.2 主要数据结构 VM内存布局 : 0x64617461000: 数据区域 0x7063000: 代码区域 0x73746163000: 可能为栈区域 3. VM指令解析 3.1 指令解码流程(sub_ 2F6F) 指令提取(sub_ 132D) : 从vmcode读取1字节 vmcode指针后移1字节 context[ 0 ] = 读取的字节 context[ 1 ] = 字节 & 3 (用于分支选择) 分支选择 : 根据context[ 1 ]的值(0-3)跳转到不同处理函数 3.2 各分支功能 分支3 (value & 3 == 3) 处理函数(sub_ 2A49) : 根据vmcode >> 2选择操作 主要操作形式: *(vmcode_6120 + 8 * (*(context + 4) + 2LL)) 关键操作(case 3): 直接赋值操作 分支2 (value & 3 == 2) 处理函数(sub_ 23A7) : 数组元素间的赋值操作 分支1 (value & 3 == 1) 处理函数(sub_ 2142) : 对数组的赋值操作 分支0 (value & 3 == 0) 处理函数(sub_ 19AD) : 包含堆操作和内存读写功能 漏洞点所在 4. 漏洞分析 4.1 关键函数(sub_ 19AD) 安全检查 : sub_12E8 : 确保qword_ 6120[ 3 ] ≤ 0x30000 堆操作 : 可以申请16个无限制大小的堆块 free后未清空指针,存在UAF漏洞 内存操作 : read_from_memory : 从0x64617461000的偏移地址读取数据到堆中 write_to_memory : 将堆中的数据写入0x64617461000的偏移地址 4.2 可利用点 控制能力 : context可控 qword_ 6120数组下标≥2可控 利用链 : 通过choice3控制数组元素 通过choice0进行堆操作和内存读写 5. 利用思路 5.1 阶段一: 泄露libc和heap地址 设置size=0x500,申请堆块0 修改size,申请堆块1 释放堆块0,使其进入largebin 申请比堆块0大的堆块2 利用write_ to_ memory读取堆块0中的libc和heap地址 通过write操作泄露地址 5.2 阶段二: 泄露栈地址 设置size=0x90,申请两个0x90堆块并释放 修改fd指向environ 重新申请,获取包含environ的堆块 读取environ中的栈地址 通过write操作泄露栈地址 5.3 阶段三: ROP攻击 设置size=0x80,申请两个0x80堆块并释放 修改fd指向返回地址(选择sub_ 132D函数中的返回地址) 写入ROP链 触发函数返回执行ROP 6. 完整利用代码分析 6.1 关键payload构造 6.2 地址泄露 6.3 ROP链构造 7. 关键技巧总结 逆向分析技巧 : 优先分析可能包含漏洞的分支(如choice0) 不必完全理解所有指令功能,重点寻找可利用的读写原语 利用技巧 : 利用largebin泄露libc地址 利用environ泄露栈地址 精心选择返回地址位置确保ROP链执行 VM PWN通用方法 : 理解VM的内存布局和指令集 寻找内存读写和堆操作原语 利用VM自身的功能构造利用链 8. 防御建议 对题目设计者 : 在free后清空指针 限制堆块大小 增加内存访问的边界检查 对参赛者(防御角度) : 注意UAF漏洞的检测 检查所有内存读写操作的边界 确保敏感数据(如指针)不被泄露 9. 扩展思考 其他可能的利用方式 : 利用VM的指令集实现任意代码执行 篡改VM的关键数据结构实现控制流劫持 类似题目训练建议 : 研究其他VM PWN题目(如Defcon Quals中的VM题目) 练习自定义指令集的逆向分析 熟悉各种虚拟架构的内存管理方式