堆栈中的 参数与局部变量 (逆向分析)

目录

C代码与汇编映射

汇编流程

栈帧结构与内存布局

[参数 局部变量 esp ebp的关联说明](#参数 局部变量 esp ebp的关联说明)

[func 函数执行期间](#func 函数执行期间)

[EBP 的核心作用:栈帧基址锚点](#EBP 的核心作用:栈帧基址锚点)

[参数访问:EBP + 正偏移(在 EBP 上方)](#参数访问:EBP + 正偏移(在 EBP 上方))

[局部变量访问:EBP - 负偏移(在 EBP 下方)](#局部变量访问:EBP - 负偏移(在 EBP 下方))

[ESP 的动态变化](#ESP 的动态变化)


C代码与汇编映射

cpp 复制代码
int func(int a, int b) {
    int x = a + 1;    // 局部变量x
    int y = b * 2;    // 局部变量y
    return x + y;
}

int main() {
    int result = func(20, 42);
    return result;
}
  • 参数压栈顺序:

    • 从右向左(先压42,再压20),确保参数a在低地址,b在高地址;
  • 栈帧建立:

    • 通过push ebp与mov ebp, esp构建当前函数的栈帧基址;
  • 局部变量分配:

    • sub esp, 8在EBP下方预留8字节空间(x与y各4字节)。
汇编流程
cpp 复制代码
; === main 函数(调用者) ===
main:
    push ebp           ; 保存main的旧EBP(上下文保护)
    mov  ebp, esp      ; 建立main的栈帧
    sub  esp, 16       ; 为局部变量分配16字节空间(含对齐)

    ; 压栈参数(从右向左):
    push 42            ; 参数2: b = 42(先压入,位于高地址)
    push 20            ; 参数1: a = 20(后压入,位于低地址)
    call func          ; 压入返回地址(EIP),跳转到func
    add  esp, 8        ; 清理参数(cdecl约定由调用者清理)
    
    mov  DWORD PTR [ebp-4], eax  ; 保存返回值到result
    mov  eax, 0        ; 返回0
    leave              ; 等价于: mov esp, ebp; pop ebp
    ret

; === func 函数(被调用者) ===
func:
    push ebp           ; 保存调用者(main)的EBP(关键!形成EBP链)
    mov  ebp, esp      ; 设置当前栈帧基址(此时EBP指向旧EBP位置)
    sub  esp, 8        ; 分配8字节局部变量空间(x和y各4字节)

    ; --- 栈帧结构关键点 ---
    ; [ebp]       = 保存的旧EBP(main的EBP)
    ; [ebp+4]     = 返回地址(main中call后的下一条指令)
    ; [ebp+8]     = 第1个参数 a = 20
    ; [ebp+12]    = 第2个参数 b = 42
    ; [ebp-4]     = 局部变量 x
    ; [ebp-8]     = 局部变量 y

    ; 访问参数并计算局部变量:
    mov  eax, DWORD PTR [ebp+8]   ; eax = a (20)
    add  eax, 1                   ; eax = a+1 (21)
    mov  DWORD PTR [ebp-4], eax   ; x = 21

    mov  eax, DWORD PTR [ebp+12]  ; eax = b (42)
    add  eax, eax                 ; eax = b*2 (84)
    mov  DWORD PTR [ebp-8], eax   ; y = 84

    ; 返回值计算(通过EAX传递):
    mov  eax, DWORD PTR [ebp-4]   ; eax = x
    add  eax, DWORD PTR [ebp-8]   ; eax = x+y (105)

    ; --- 上下文恢复关键步骤 ---
    mov  esp, ebp      ; 1. ESP回到当前帧基址(清除局部变量)
    pop  ebp           ; 2. 恢复调用者EBP(从栈中弹出旧EBP)
    ret                ; 3. 弹出返回地址到EIP(自动ESP+=4)
栈帧结构与内存布局
  • 当func函数执行时,堆栈呈现清晰的层次结构(高地址→低地址)
cpp 复制代码
高地址(内存高位)
+---------------------+ ← 初始 ESP (main 调用前)
| main 的局部变量      |  (16字节,main 中 sub esp,16 分配)
| ... (result 等)      |
+---------------------+ ← main 的 EBP (0x0FFC)
| 保存的旧 EBP (main)  |  ← [main_ebp] = 0x2000 (main 的调用者帧基址)
+---------------------+
| 参数 b = 42         |  ← [ebp + 12] (main 中 push 42)
+---------------------+
| 参数 a = 20         |  ← [ebp + 8]  (main 中 push 20)
+---------------------+
| 返回地址 (EIP)      |  ← [ebp + 4]  (call func 压入)
+---------------------+ ← func 的 EBP (0x0FD0) ← EBP 寄存器当前值
| 保存的旧 EBP (main) |  ← [ebp]      (func 中 push ebp)
+---------------------+
| 局部变量 y = 84     |  ← [ebp - 4]  (func 中分配)
+---------------------+
| 局部变量 x = 21     |  ← [ebp - 8]  (func 中分配)
+---------------------+ ← func 的 ESP (0x0FC8) ← ESP 寄存器当前值
|                      |
|       空闲栈空间     |
|                      |
低地址(内存低位)
参数 局部变量 esp ebp的关联说明
  1. EBP 是栈帧基址指针 :在函数内部 固定不变 ,作为访问参数/局部变量的 唯一基准

  2. ESP 是栈顶指针 : 动态变化 ,随压栈/出栈操作实时移动

  3. 参数在 EBP 上方 (正偏移):因为调用者先压参数,再压返回地址

  4. 局部变量在 EBP 下方 (负偏移):被调用者通过 sub esp, N 分配

func 函数执行期间

EBP 的核心作用:栈帧基址锚点
  • EBP 寄存器值 = 0x0FD0(指向当前函数的栈帧基址)

  • EBP 指向的位置 :保存了调用者(main)的 EBP 值 (0x0FFC)

  • 为什么固定? mov ebp, esp 执行后,EBP 不再随局部变量分配而改变,始终指向:

cpp 复制代码
[ebp] = 保存的旧 EBP (main 的帧基址)
参数访问:EBP + 正偏移(在 EBP 上方)
cpp 复制代码
偏移量	计算方式	物理位置	值	说明
4	[ebp + 4]	0x0FD4	EIP	返回地址(call 指令压入)
8	[ebp + 8]	0x0FD8	20	第1个参数 a(main 后压)
12	[ebp + 12]	0x0FDC	42	第2个参数 b(main 先压)
  • 第 N 个参数 = EBP + 4(N+1) * (因为 [ebp+4] 是返回地址,参数从 [ebp+8] 开始)
局部变量访问:EBP - 负偏移(在 EBP 下方)
cpp 复制代码
偏移量	计算方式	物理位置	值	说明
-4	[ebp - 4]	0x0FCC	21	局部变量 x(先分配)
-8	[ebp - 8]	0x0FC8	84	局部变量 y(后分配)
  • 第 K 个局部变量 = EBP - 4\*K (按声明顺序从高地址向低地址分配)
ESP 的动态变化

|--------------------|-----------|--------|--------------------|
| 指令 | ESP 变化 | ESP 值 | 说明 |
| push ebp (func 开始) | ESP -= 4 | 0x0FD0 | 压入旧 EBP(main 的帧基址) |
| mov ebp, esp | 无变化 | 0x0FD0 | EBP 锚定当前帧基址 |
| sub esp, 8 | ESP -= 8 | 0x0FC8 | 分配局部变量空间(x,y 各4字节) |
| mov esp, ebp (返回时) | ESP = EBP | 0x0FD0 | 丢弃局部变量(ESP 回到帧基址) |
| pop ebp | ESP += 4 | 0x0FD4 | 恢复调用者 EBP(加载到寄存器) |
| ret | ESP += 4 | 0x0FD8 | 弹出返回地址(跳转回 main) |

相关推荐
水饺编程4 小时前
第2章:Linux 基础,切换用户
linux·c语言·汇编
水饺编程1 天前
第2章:Linux 基础,在 root 中创建用户账号
linux·c语言·汇编·1024程序员节
一个风轻云淡1 天前
从 int $0x80 到 iret:用 gdb 把 getpid 走一遍
linux·c语言·开发语言·汇编
感谢地心引力1 天前
Gemini 4 Argon 使用 REA 分析 Rstudio,实现简体中文支持
ai·逆向·r·汉化·rstudio·rea
水饺编程2 天前
第2章:Linux 基础,切换到超级用户
linux·c语言·汇编
虎鲸不是鱼2 天前
AI 自动化逆向(九)AI 会记住错的公共常量
人工智能·自动化·大模型·逆向·ai逆向
虎鲸不是鱼2 天前
AI 自动化逆向(十二)AI 有分类冲动:非要找二元判据
人工智能·分类·自动化·逆向·ai逆向
七夜zippoe2 天前
启元 Q1 接口地图:从 aimdk_msgs 逆向出 25 个 Service/Topic 的完整清单
ai·机器人·逆向·启元·接口地图
水饺编程3 天前
第2章:Linux 基础,重启命令
linux·c语言·汇编
en.en..4 天前
volatile 完整深度教程(C 代码 + 带注释 ARM 汇编 + 硬件中断原理 + 分步运行时序)
c语言·汇编·arm开发