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

目录

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) |

相关推荐
iCxhust8 小时前
微机原理实践教程(C语言篇)---A001闪烁灯
c语言·开发语言·汇编·单片机·嵌入式硬件·51单片机·微机原理
浩浩测试一下2 天前
抬栈 恢复上下文 (逆向分析)
汇编·逆向·堆栈·windows核心编程
zhouwy1132 天前
ARM汇编指令集详解
汇编·arm开发
iCxhust2 天前
微机原理实践教程(汇编篇)---A002流水灯
汇编·单片机·嵌入式硬件·51单片机·微机原理
浩浩测试一下2 天前
栈帧 抬栈与平栈 (逆向分析)
汇编·windows api·堆栈·windows编程·windows 开发
陈eaten2 天前
win11下nasm编写汇编及链接方案
汇编·链接·nasm·gcc·golink
iCxhust2 天前
【无标题】8086/8088裸机对于学习微机原理的重要意义
汇编·单片机·嵌入式硬件·嵌入式·微机原理
NPE~2 天前
[App逆向]脱壳实战
android·教程·逆向·android逆向·逆向分析
浩浩测试一下2 天前
PE结构利用 ---> 什么是壳
windows pe·pe壳·windows编译·windows api编写·加密壳·免杀壳