vm的stack和got表溢出(附四种vm常见架构)
字数 1443 2025-12-24 12:13:35

虚拟机漏洞利用技术详解:栈与GOT表溢出

一、虚拟机基础架构分析

1.1 四种常见虚拟机架构

架构一:逐步写入指令执行的简单虚拟机

特点:每次读取8字节指令并立即执行

核心数据结构

typedef struct vm_mould1 {
    char op;        // 操作码
    char reg1;      // 寄存器1
    char reg2;      // 寄存器2  
    char reg3;      // 寄存器3
    int32_t num;    // 立即数
} Vm_mould1;

指令解析逻辑

Vm_mould1* ret_vm(long t) {
    char *p = &t;
    Vm_mould1 *vm = malloc(sizeof(Vm_mould1));
    vm->op = p[0];
    vm->reg1 = p[1];
    vm->reg2 = p[2];
    vm->reg3 = p[3];
    vm->num = *(int32_t*)(p+4);
    return vm;
}

指令集

  • 0: reg[reg1] = num (立即数赋值)
  • 1: reg[reg1] = reg[reg2] + reg[reg3] (加法)
  • 2: reg[reg1] = reg[reg2] - reg[reg3] (减法)
  • 3: stack[++sp] = reg[reg1] (压栈)
  • 4: reg[reg1] = stack[sp--] (弹栈)
  • 5: 打印寄存器值
  • 6/7: 移位操作
  • 8: stack[sp] = reg[reg1] (栈赋值)

架构二:一次性写入指令执行的虚拟机

改进点:预先读取所有指令到缓冲区,顺序执行

内存管理

char code[0x1000];  // 指令缓冲区
char *p = code;     // 指令指针

架构三:封装性虚拟机

特点:使用函数指针表实现指令分发

核心结构

typedef struct vm_opcode {
    char opcode;
    void (*handle)(void*);
} vm_opcode;

typedef struct vm_cpu {
    unsigned char *rip;      // 指令指针
    vm_opcode op_list[OPCODE_N];  // 操作码表
} vm_cpu;

指令分发机制

void vm_dispatcher(vm_cpu *cpu) {
    for (int i = 0; i < OPCODE_N; i++) {
        if (*cpu->rip == cpu->op_list[i].opcode) {
            cpu->op_list[i].handle(cpu);
            break;
        }
    }
}

架构四:双层封装虚拟机

逆向难度:极高,采用两级指令解析

  • 第一级:选择处理函数
  • 第二级:实际虚拟机操作
  • 可扩展为多级嵌套增加逆向复杂度

二、栈溢出漏洞利用技术

2.1 栈布局分析

int main() {
    long stack[0x200];      // 栈数组,514个元素
    unsigned int rsp = 0;   // 栈指针
    // ... 其他变量
}

关键漏洞点

case 7:  // 压栈指令
    stack[rsp++] = reg[c->reg1];
    break;
case 8:  // 弹栈指令  
    if(rsp <= 0) exit(0);
    reg[c->reg1] = stack[rsp--];
    break;

2.2 溢出利用原理

  1. 栈帧结构:stack数组位于main函数栈帧内
  2. 溢出方向:向上溢出可覆盖返回地址
  3. 绕过检查:rsp为无符号整数,负数检查失效

2.3 利用步骤

  1. 泄露信息:通过溢出读取栈上敏感数据(canary、libc地址)
  2. 构造ROP链:计算gadget地址
  3. 获取shell:执行system("/bin/sh")

2.4 关键指令构造

def push(idx):
    return str(7 | (idx << 8))

def pop(idx):
    return str(8 | (idx << 8))

def add(rdi, rsi, rdx):
    return str(0 | (rdi << 8) | (rsi << 16) | (rdx << 24))

三、GOT表溢出漏洞利用

3.1 漏洞环境分析

指令格式

def code(opcode, rgs1, rgs2, imm):
    instruction = 0
    instruction |= (opcode & 0xFFFF) << 48
    instruction |= (rgs1 & 0xFF) << 32
    instruction |= (rgs2 & 0xFF) << 16  
    instruction |= imm & 0xFFFF
    return instruction

安全检查绕过

if (BYTE4(realcode) > 0xAu || rgs1 < 0 || 
    (unsigned int)rgs2 > 0xA || index > 256) {
    puts("Invade ptr!");
    exit(0);
}

3.2 GOT表修改技术

  1. 计算偏移:stack数组到GOT表的距离(0x90字节)
  2. 构造指令:通过弹栈操作修改GOT表项
  3. 函数劫持:将sprintf的GOT表项改为system地址

3.3 利用链构造

# 修改GOT表为system地址
payload = flat(
    code(0,0,0,6),        # 压栈立即数
    code(1,0,0,0),        # 多次弹栈调整位置
    # ... 重复弹栈指令
    code(8,2,0,0)         # 最终修改GOT表
)

四、高级利用技巧

4.1 寄存器数值构造

分段构造法

def set_number_off_five(target, mid_reg, wide=5):
    pd = ""
    # 分段提取目标数值的各个字节
    one = target & 0xf
    two = (target >> 4) & 0xf
    # ... 其他分段
    
    # 通过移位和加法组合目标值
    if wide >= 1:
        pd += add(mid_reg, mid_reg, one) + '\n'
    if wide >= 2:
        pd += add(mid_reg+1, mid_reg+1, two) + '\n'
        pd += shl(mid_reg+1, mid_reg+1, 4) + '\n'
        pd += add(mid_reg+1, mid_reg, mid_reg+1) + '\n'
    return pd

4.2 内存布局操控

  1. BSS段利用:通过全局变量reg[]进行数据传递
  2. 栈指针操控:利用无符号整数溢出实现越界访问
  3. 指令流控制:精心构造的指令序列实现任意地址读写

4.3 防御绕过技术

  1. 类型转换绕过:利用有符号/无符号整数差异
  2. 边界检查绕过:通过算术运算产生意外结果
  3. 代码流劫持:修改GOT表或返回地址控制执行流程

五、实战案例详解

5.1 栈溢出实战

漏洞位置:rsp无符号整数导致的向下溢出

case 8:  // 弹栈指令
    if (rsp <= 0) exit(0);  // 错误检查:rsp为无符号,永远>=0
    reg[c->reg1] = stack[rsp--];  // 实际可访问stack[-1]

利用过程

  1. 通过负索引泄露栈上数据
  2. 计算libc基地址和gadget地址
  3. 构造ROP链获取shell

5.2 GOT表修改实战

关键指令

case 1:  // 弹栈到寄存器
    v1 = index--;
    rgs[rgs1] = stack[v1];

利用链

  1. 计算stack到GOT表的偏移
  2. 通过多次弹栈移动"指针"
  3. 将目标地址写入GOT表项
  4. 触发修改后的函数调用

六、防护与检测建议

6.1 漏洞防护

  1. 完善边界检查:使用有符号整数进行索引检查
  2. 内存隔离:将敏感数据与用户可控数据分离
  3. 地址随机化:充分使用ASLR保护

6.2 检测方法

  1. 静态分析:检查所有数组访问的边界条件
  2. 动态测试:构造极端输入测试边界情况
  3. 模糊测试:自动化生成测试用例

本教学文档详细分析了虚拟机环境中栈和GOT表溢出的原理、利用技术和防护措施,为二进制安全研究和CTF竞赛提供了完整的技术参考。

虚拟机漏洞利用技术详解:栈与GOT表溢出 一、虚拟机基础架构分析 1.1 四种常见虚拟机架构 架构一:逐步写入指令执行的简单虚拟机 特点 :每次读取8字节指令并立即执行 核心数据结构 : 指令解析逻辑 : 指令集 : 0: reg[ reg1 ] = num (立即数赋值) 1: reg[ reg1] = reg[ reg2] + reg[ reg3 ] (加法) 2: reg[ reg1] = reg[ reg2] - reg[ reg3 ] (减法) 3: stack[ ++sp] = reg[ reg1 ] (压栈) 4: reg[ reg1] = stack[ sp-- ] (弹栈) 5: 打印寄存器值 6/7: 移位操作 8: stack[ sp] = reg[ reg1 ] (栈赋值) 架构二:一次性写入指令执行的虚拟机 改进点 :预先读取所有指令到缓冲区,顺序执行 内存管理 : 架构三:封装性虚拟机 特点 :使用函数指针表实现指令分发 核心结构 : 指令分发机制 : 架构四:双层封装虚拟机 逆向难度 :极高,采用两级指令解析 第一级:选择处理函数 第二级:实际虚拟机操作 可扩展为多级嵌套增加逆向复杂度 二、栈溢出漏洞利用技术 2.1 栈布局分析 关键漏洞点 : 2.2 溢出利用原理 栈帧结构 :stack数组位于main函数栈帧内 溢出方向 :向上溢出可覆盖返回地址 绕过检查 :rsp为无符号整数,负数检查失效 2.3 利用步骤 泄露信息 :通过溢出读取栈上敏感数据(canary、libc地址) 构造ROP链 :计算gadget地址 获取shell :执行system("/bin/sh") 2.4 关键指令构造 三、GOT表溢出漏洞利用 3.1 漏洞环境分析 指令格式 : 安全检查绕过 : 3.2 GOT表修改技术 计算偏移 :stack数组到GOT表的距离(0x90字节) 构造指令 :通过弹栈操作修改GOT表项 函数劫持 :将sprintf的GOT表项改为system地址 3.3 利用链构造 四、高级利用技巧 4.1 寄存器数值构造 分段构造法 : 4.2 内存布局操控 BSS段利用 :通过全局变量reg[ ]进行数据传递 栈指针操控 :利用无符号整数溢出实现越界访问 指令流控制 :精心构造的指令序列实现任意地址读写 4.3 防御绕过技术 类型转换绕过 :利用有符号/无符号整数差异 边界检查绕过 :通过算术运算产生意外结果 代码流劫持 :修改GOT表或返回地址控制执行流程 五、实战案例详解 5.1 栈溢出实战 漏洞位置 :rsp无符号整数导致的向下溢出 利用过程 : 通过负索引泄露栈上数据 计算libc基地址和gadget地址 构造ROP链获取shell 5.2 GOT表修改实战 关键指令 : 利用链 : 计算stack到GOT表的偏移 通过多次弹栈移动"指针" 将目标地址写入GOT表项 触发修改后的函数调用 六、防护与检测建议 6.1 漏洞防护 完善边界检查 :使用有符号整数进行索引检查 内存隔离 :将敏感数据与用户可控数据分离 地址随机化 :充分使用ASLR保护 6.2 检测方法 静态分析 :检查所有数组访问的边界条件 动态测试 :构造极端输入测试边界情况 模糊测试 :自动化生成测试用例 本教学文档详细分析了虚拟机环境中栈和GOT表溢出的原理、利用技术和防护措施,为二进制安全研究和CTF竞赛提供了完整的技术参考。