背景

前两天在学习ret2text时,尤其是在文章:HappyNewYearCTF_5_ret2text 32位中,有提到为什么必须call func才能执行,而将eip直接改为func无法执行。

最初经过分析,误认为是传参,进而导致的栈重建。经过不断细细学习,琢磨,最终发现并非如此。

32位程序

32 位 ret2text / ret2libc 场景中,遇到了一个看似矛盾的现象:

  • 无参函数
    • 直接把 EIP 覆盖为 func 的地址,一切正常
  • 有参函数
    • × 直接跳 func 会异常
    • √ 反而跳到 call func 的地址,函数就“能正常拿到参数”

究其根源,实际上是call做了一步将下一步执行内容地址压栈的处理,即如下操作

call func
    ^
    |
push return_address
jmp func

那在这个操作中,其实影响了栈的布局,原本我们认为的布局应该是下面的样子,也就是call system,然后直接读取参数。非常符合我们的直觉。

High
Address   |                 |  
          +-----------------+  <-- ebp + 8 
          | 0x0804C028      |  4字节 (/bin/sh字符串位置)
          +-----------------+  <-- ebp + 4 
          | 0x0804925A      |  4字节 (system函数的入口地址)
          +-----------------+  
          |      ......     |  <-- 。。。
          +-----------------+ 
          | buf[2]          |  
          | buf[1]          |  <-- 3字节 (原始缓冲区,溢出起点)
          | buf[0]          |  
          +-----------------+
Low       |                 |
Address

但实际场景中,call system其实际操作会进行压栈,也就是压入返回地址。

如果不涉及到call的话,那我们的栈布局应该是下面这样。

High
Address   |                 |  
          +-----------------+  <-- ebp + 12 
          | 0x0804C028      |  4字节 (/bin/sh字符串位置)
          +-----------------+  <-- ebp + 8 
          | 0xDEADBEEF      |  4字节 (返回地址-无效字符串即可)
          +-----------------+  <-- ebp + 4 
          | 0x0804925A      |  4字节 (system函数的入口地址)
          +-----------------+  
          |      ......     |  <-- 。。。
          +-----------------+ 
          | buf[2]          |  
          | buf[1]          |  <-- 3字节 (原始缓冲区,溢出起点)
          | buf[0]          |  
          +-----------------+
Low       |                 |
Address

总结

在进行32位exp编写时,优先还是选择直接进行栈布局,也就是函数地址+返回地址+参数,即:

padding
target_func_addr
fake_ret_addr
[arg1]
[arg2]
[arg3]
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐