图解利用虚函数过GS保护
字数 2133 2025-08-15 21:31:13

利用虚函数绕过GS保护的技术分析

一、GS保护机制概述

GS保护是微软在Visual Studio中引入的一种栈溢出保护机制,主要特点包括:

  1. 安全Cookie机制

    • 在所有函数调用前,向栈内压入一个随机数(称为canary或security cookie)
    • 这个随机数位于EBP之前
    • 系统在.data内存区域存放security cookie的副本
  2. 安全验证过程

    • 函数返回前调用__security_check_cookie
    • 比较栈中的canary和.data中的副本
    • 如果不一致,说明发生栈溢出,终止程序
  3. 突破思路

    • 在程序检查security cookie之前劫持程序流程
    • 利用虚函数调用时机在安全检查之前的特点

二、虚函数机制分析

  1. 虚函数特点

    • 使用virtual关键字修饰的成员函数
    • 虚函数入口地址统一保存在虚表(vtable)中
  2. 调用过程

    • 通过虚表指针(vptr)找到虚表
    • 从虚表中取出函数入口地址进行调用
  3. 内存布局特点

    • 虚表指针地址和局部变量在内存中相邻
    • 成员变量溢出可能覆盖虚表指针

三、利用虚函数绕过GS的实验环境

  1. 系统环境

    • 操作系统:Windows 7
    • 编译器:Visual Studio 2015
    • 编译选项:
      • 打开GS保护
      • 关闭DEP、ASLR、SafeSEH
      • 修改基址为0x41400000(避免00截断)
  2. 关键编译设置

    • 项目属性→C/C++→代码生成→安全检查→启用安全检查(GS)
    • 链接器→高级→随机基址→否
    • 链接器→高级→数据执行保护(DEP)→否
    • 链接器→高级→映像具有安全异常处理程序→否

四、漏洞代码分析

class Vir {
public:
    void test(char* str) {
        char buf[0x100]; // 局部变量buf
        strcpy(buf, str); // 存在栈溢出漏洞
        printf("buf:%d\n%s\n", strlen(buf), buf);
        this->virfun(); // 调用虚函数
    }
    
    virtual void virfun() {
        printf("I am virtual function\n");
    }
};

int main() {
    Vir v;
    v.test("\x53\x13\x40\x41" // ppt指令序列地址
           "\x90\x90\x90\x90\x90\x90\x90\x90\xaf\x10\x40\x41" // jmpesp地址
           "\x90\x90\x90\x90\x90\x90\x90\x90" // 滑轨
           // ... shellcode和填充数据
           "\x38\x21\x40\x41"); // 原始参数地址
    return 0;
}

五、利用过程详解

1. 计算偏移量

  1. 手动计算

    • 虚表指针地址:0x0018ff34
    • 局部变量buf地址:0x0018fe24
    • 偏移量:0x0018ff34 - 0x0018fe24 = 0x110 (272字节)
  2. 使用Immunity Debugger计算

    !mona pc 300  # 生成300字节测试字符串
    !mona po 0x316A4130  # 计算偏移量
    

2. 攻击思路

  1. 覆盖虚表指针

    • 通过buf溢出覆盖虚表指针
    • 将虚表指针指向原始参数地址(0x41402138)
  2. 跳转流程

    • 虚表指针→原始参数地址(伪虚表)
    • 原始参数前4字节作为虚函数地址(PPT指令序列)
    • 执行PPT指令后跳转到buf地址
    • 再次执行PPT指令后跳转到jmpesp
    • 通过jmpesp跳回shellcode执行

3. 关键跳板地址

  1. PPT指令序列

    • 使用pop pop ret指令序列
    • 搜索方法:!mona seh
    • 选择标准:不涉及ebp/esp操作的指令
    • 示例地址:0x41401353
  2. JMP ESP地址

    • 搜索方法:!mona jmp -r esp
    • 示例地址:0x414010af

4. Payload结构

偏移 内容 说明
0x00 \x53\x13\x40\x41 PPT指令序列地址
0x04 8字节NOP 填充
0x0C \xaf\x10\x40\x41 JMP ESP地址
0x10 8字节NOP 滑轨
0x18 Shellcode 实际执行的代码
... NOP填充 保证覆盖完整
最后4字节 \x38\x21\x40\x41 原始参数地址

六、调试过程关键点

  1. 初始状态

    • 虚表指针(0x0018FF34)被覆盖为原始参数地址(0x41402138)
    • 原始参数前4字节为PPT指令序列地址(0x41401353)
  2. 第一次跳转

    • 执行call virfun时,实际调用0x41401353(PPT)
    • 调用后ESP=0x0018FE1C(返回地址入栈)
  3. PPT执行

    • pop ecx; pop ecx; ret
    • 执行后ESP=0x0018FE24
    • RET跳转到0x0018FE24处的PPT地址
  4. 第二次PPT

    • 再次执行PPT指令
    • 跳转到0x0018FE30处的JMP ESP地址
  5. 最终跳转

    • 执行JMP ESP跳转到0x0018FE34
    • 开始执行NOP滑轨和shellcode

七、替代方案分析

  1. 直接覆盖虚表指针为局部变量地址

    • 地址形式:\x24\xfe\x18\x00
    • 虽然strcpy存在00截断,但高位本来就是00
    • 可以成功覆盖
  2. 简化跳转流程的可能性

    • 部分资料未提及二次PPT和JMP ESP
    • 实际调试发现需要完整跳转链
    • 可能与环境配置或编译器优化有关

八、防御建议

  1. 组合使用安全机制

    • 同时启用GS、DEP、ASLR、SafeSEH
    • 避免单独依赖某一项保护
  2. 代码层面防御

    • 使用安全字符串函数替代strcpy
    • 对输入进行长度检查
  3. 编译器设置

    • 启用所有安全编译选项
    • 使用最新版本的编译器

九、总结

利用虚函数绕过GS保护的关键在于:

  1. 精确计算偏移覆盖虚表指针
  2. 构造包含多级跳转的payload
  3. 利用虚函数调用在安全检查之前的时机
  4. 通过PPT和JMP ESP等跳板完成复杂跳转

这种技术展示了即使有GS保护,通过精心构造的利用链仍然可能实现代码执行,强调了多层防御的必要性。

利用虚函数绕过GS保护的技术分析 一、GS保护机制概述 GS保护是微软在Visual Studio中引入的一种栈溢出保护机制,主要特点包括: 安全Cookie机制 : 在所有函数调用前,向栈内压入一个随机数(称为canary或security cookie) 这个随机数位于EBP之前 系统在.data内存区域存放security cookie的副本 安全验证过程 : 函数返回前调用 __security_check_cookie 比较栈中的canary和.data中的副本 如果不一致,说明发生栈溢出,终止程序 突破思路 : 在程序检查security cookie之前劫持程序流程 利用虚函数调用时机在安全检查之前的特点 二、虚函数机制分析 虚函数特点 : 使用 virtual 关键字修饰的成员函数 虚函数入口地址统一保存在虚表(vtable)中 调用过程 : 通过虚表指针(vptr)找到虚表 从虚表中取出函数入口地址进行调用 内存布局特点 : 虚表指针地址和局部变量在内存中相邻 成员变量溢出可能覆盖虚表指针 三、利用虚函数绕过GS的实验环境 系统环境 : 操作系统:Windows 7 编译器:Visual Studio 2015 编译选项: 打开GS保护 关闭DEP、ASLR、SafeSEH 修改基址为0x41400000(避免00截断) 关键编译设置 : 项目属性→C/C++→代码生成→安全检查→启用安全检查(GS) 链接器→高级→随机基址→否 链接器→高级→数据执行保护(DEP)→否 链接器→高级→映像具有安全异常处理程序→否 四、漏洞代码分析 五、利用过程详解 1. 计算偏移量 手动计算 : 虚表指针地址:0x0018ff34 局部变量buf地址:0x0018fe24 偏移量:0x0018ff34 - 0x0018fe24 = 0x110 (272字节) 使用Immunity Debugger计算 : 2. 攻击思路 覆盖虚表指针 : 通过buf溢出覆盖虚表指针 将虚表指针指向原始参数地址(0x41402138) 跳转流程 : 虚表指针→原始参数地址(伪虚表) 原始参数前4字节作为虚函数地址(PPT指令序列) 执行PPT指令后跳转到buf地址 再次执行PPT指令后跳转到jmpesp 通过jmpesp跳回shellcode执行 3. 关键跳板地址 PPT指令序列 : 使用 pop pop ret 指令序列 搜索方法: !mona seh 选择标准:不涉及ebp/esp操作的指令 示例地址:0x41401353 JMP ESP地址 : 搜索方法: !mona jmp -r esp 示例地址:0x414010af 4. Payload结构 | 偏移 | 内容 | 说明 | |------|------|------| | 0x00 | \x53\x13\x40\x41 | PPT指令序列地址 | | 0x04 | 8字节NOP | 填充 | | 0x0C | \xaf\x10\x40\x41 | JMP ESP地址 | | 0x10 | 8字节NOP | 滑轨 | | 0x18 | Shellcode | 实际执行的代码 | | ... | NOP填充 | 保证覆盖完整 | | 最后4字节 | \x38\x21\x40\x41 | 原始参数地址 | 六、调试过程关键点 初始状态 : 虚表指针(0x0018FF34)被覆盖为原始参数地址(0x41402138) 原始参数前4字节为PPT指令序列地址(0x41401353) 第一次跳转 : 执行 call virfun 时,实际调用0x41401353(PPT) 调用后ESP=0x0018FE1C(返回地址入栈) PPT执行 : pop ecx; pop ecx; ret 执行后ESP=0x0018FE24 RET跳转到0x0018FE24处的PPT地址 第二次PPT : 再次执行PPT指令 跳转到0x0018FE30处的JMP ESP地址 最终跳转 : 执行JMP ESP跳转到0x0018FE34 开始执行NOP滑轨和shellcode 七、替代方案分析 直接覆盖虚表指针为局部变量地址 : 地址形式:\x24\xfe\x18\x00 虽然strcpy存在00截断,但高位本来就是00 可以成功覆盖 简化跳转流程的可能性 : 部分资料未提及二次PPT和JMP ESP 实际调试发现需要完整跳转链 可能与环境配置或编译器优化有关 八、防御建议 组合使用安全机制 : 同时启用GS、DEP、ASLR、SafeSEH 避免单独依赖某一项保护 代码层面防御 : 使用安全字符串函数替代strcpy 对输入进行长度检查 编译器设置 : 启用所有安全编译选项 使用最新版本的编译器 九、总结 利用虚函数绕过GS保护的关键在于: 精确计算偏移覆盖虚表指针 构造包含多级跳转的payload 利用虚函数调用在安全检查之前的时机 通过PPT和JMP ESP等跳板完成复杂跳转 这种技术展示了即使有GS保护,通过精心构造的利用链仍然可能实现代码执行,强调了多层防御的必要性。