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

目录

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

相关推荐
txg6661 天前
编译无关的漏洞检测:基于 Transformer 的 LLVM-IR 与汇编鲁棒建模
汇编·深度学习·安全·transformer
浩浩测试一下2 天前
汇编 16位32位64位通用寄存器(逆向分析)
汇编·windows·stm32·单片机·嵌入式硬件·逆向·二进制
浩浩测试一下2 天前
汇编常用的(JCC 串 判断)指令 通用寄存器 标志寄存器 段寄存器(逆向分析)
汇编·通用寄存器·逆向二进制·标志寄存器·段寄存器·串 jcc 常用指令
Lang-12102 天前
Frida + Android Hook 完整指南
android·逆向·hook·frida
浩浩测试一下3 天前
汇编 标志位寄存器 (逆向分析 )
c语言·汇编·逆向·windows编程·标志寄存器
浩浩测试一下3 天前
汇编 数组与串指令(逆向分析)
汇编·逆向·二进制·免杀·串指令·汇编数组
程序0073 天前
.NET玩转爬虫 遇到反爬 jsl cookie
逆向
浩浩测试一下3 天前
汇编 内联汇编与混合编程 (逆向分析)
汇编·混合编程·windows编程·内联汇编·二进制逆向·c语言混合汇编
浩浩测试一下4 天前
汇编 结构体与宏
汇编··免杀·结构体·windows编程·逆向二进制
RSCompany4 天前
Frida 17 以后 Python API 跑旧版 JS 报 Java is not defined ?一行 import 直接恢复 Frida 16 体验
开发语言·python·逆向·hook·frida·android逆向·frida17