Hack 虚拟内存系列(五):栈,寄存器和汇编
字数 973 2025-08-20 18:17:42

栈、寄存器和汇编代码深入解析

栈的基本概念

栈是位于内存高端并向下增长的内存区域,主要用于存储局部变量和函数调用信息。在x86-64架构中,栈通过两个特殊寄存器进行管理:

  • RBP (基址指针寄存器):指向当前栈帧的底部
  • RSP (栈指针寄存器):指向当前栈帧的顶部

栈帧的创建与销毁

栈帧创建过程

  1. 保存前一个栈帧的基址

    push rbp
    

    将当前rbp值压入栈中,保存前一个函数的栈帧基址

  2. 设置新的栈帧基址

    mov rbp, rsp
    

    将rsp的值赋给rbp,建立新的栈帧

  3. 分配局部变量空间

    sub rsp, 0x10
    

    通过减少rsp的值来为局部变量分配空间

栈帧销毁过程

使用leave指令完成:

  1. 将rsp设置为rbp(释放局部变量空间)
  2. 从栈中弹出值到rbp(恢复前一个栈帧)

局部变量的存储与访问

局部变量存储在栈帧中,通过rbp的偏移量来访问:

int a = 972;

对应的汇编代码:

mov DWORD PTR [rbp-0x4], 0x3cc  ; 0x3cc = 972

变量在栈中的顺序可能与源代码中声明的顺序不同,由编译器决定。

函数调用机制

调用函数时

  1. 使用call指令:
    • 将返回地址(下一条指令的地址)压入栈
    • 跳转到被调用函数

从函数返回时

使用ret指令:

  • 从栈中弹出返回地址
  • 跳转到该地址继续执行

栈帧布局分析

一个典型的栈帧包含:

  1. 局部变量(位于rbp下方)
  2. 保存的rbp值(位于rbp指向的位置)
  3. 返回地址(位于rbp + 8的位置)

实战:访问栈帧内容

通过C代码直接访问栈帧内容:

register long rbp asm ("rbp");

// 访问局部变量a(假设位于rbp-0xc)
int a_value = *(int *)(((char *)rbp) - 0xc);

// 获取保存的前一个rbp值
unsigned long prev_rbp = *(unsigned long *)rbp;

// 获取返回地址
unsigned long return_addr = *(unsigned long *)((char *)rbp + 8);

栈攻击实例

通过修改返回地址劫持程序流:

// 假设bye函数地址为0x4005bd
*(unsigned long int *)((char *)rbp + 8) = 0x4005bd;

这将使函数返回到bye函数而非原本的调用者。

关键知识点总结

  1. 栈增长方向:向低地址增长
  2. 寄存器作用
    • RBP:当前栈帧基址
    • RSP:当前栈顶指针
  3. 栈帧生命周期
    • 通过push rbp; mov rbp, rsp; sub rsp, X创建
    • 通过leave指令销毁
  4. 局部变量访问:通过rbp固定偏移量访问
  5. 函数调用机制
    • call指令压入返回地址
    • ret指令弹出返回地址
  6. 栈帧布局
    • 低地址:局部变量
    • rbp位置:保存的前一个rbp
    • rbp+8:返回地址
  7. 安全风险:返回地址可以被修改以改变程序流

环境与工具

  • 系统:Ubuntu 14.04
  • 编译器:gcc 4.8.4
  • 反汇编工具:objdump
  • 架构:x86-64

理解栈的工作原理对于逆向工程、漏洞分析和程序优化都至关重要。通过掌握这些基础知识,可以更深入地理解程序在内存中的行为。

栈、寄存器和汇编代码深入解析 栈的基本概念 栈是位于内存高端并向下增长的内存区域,主要用于存储局部变量和函数调用信息。在x86-64架构中,栈通过两个特殊寄存器进行管理: RBP (基址指针寄存器) :指向当前栈帧的底部 RSP (栈指针寄存器) :指向当前栈帧的顶部 栈帧的创建与销毁 栈帧创建过程 保存前一个栈帧的基址 : 将当前rbp值压入栈中,保存前一个函数的栈帧基址 设置新的栈帧基址 : 将rsp的值赋给rbp,建立新的栈帧 分配局部变量空间 : 通过减少rsp的值来为局部变量分配空间 栈帧销毁过程 使用 leave 指令完成: 将rsp设置为rbp(释放局部变量空间) 从栈中弹出值到rbp(恢复前一个栈帧) 局部变量的存储与访问 局部变量存储在栈帧中,通过rbp的偏移量来访问: 对应的汇编代码: 变量在栈中的顺序可能与源代码中声明的顺序不同,由编译器决定。 函数调用机制 调用函数时 使用 call 指令: 将返回地址(下一条指令的地址)压入栈 跳转到被调用函数 从函数返回时 使用 ret 指令: 从栈中弹出返回地址 跳转到该地址继续执行 栈帧布局分析 一个典型的栈帧包含: 局部变量(位于rbp下方) 保存的rbp值(位于rbp指向的位置) 返回地址(位于rbp + 8的位置) 实战:访问栈帧内容 通过C代码直接访问栈帧内容: 栈攻击实例 通过修改返回地址劫持程序流: 这将使函数返回到bye函数而非原本的调用者。 关键知识点总结 栈增长方向 :向低地址增长 寄存器作用 : RBP:当前栈帧基址 RSP:当前栈顶指针 栈帧生命周期 : 通过push rbp; mov rbp, rsp; sub rsp, X创建 通过leave指令销毁 局部变量访问 :通过rbp固定偏移量访问 函数调用机制 : call指令压入返回地址 ret指令弹出返回地址 栈帧布局 : 低地址:局部变量 rbp位置:保存的前一个rbp rbp+8:返回地址 安全风险 :返回地址可以被修改以改变程序流 环境与工具 系统:Ubuntu 14.04 编译器:gcc 4.8.4 反汇编工具:objdump 架构:x86-64 理解栈的工作原理对于逆向工程、漏洞分析和程序优化都至关重要。通过掌握这些基础知识,可以更深入地理解程序在内存中的行为。