Hack 虚拟内存系列(五):栈,寄存器和汇编
字数 973 2025-08-20 18:17:42
栈、寄存器和汇编代码深入解析
栈的基本概念
栈是位于内存高端并向下增长的内存区域,主要用于存储局部变量和函数调用信息。在x86-64架构中,栈通过两个特殊寄存器进行管理:
- RBP (基址指针寄存器):指向当前栈帧的底部
- RSP (栈指针寄存器):指向当前栈帧的顶部
栈帧的创建与销毁
栈帧创建过程
-
保存前一个栈帧的基址:
push rbp将当前rbp值压入栈中,保存前一个函数的栈帧基址
-
设置新的栈帧基址:
mov rbp, rsp将rsp的值赋给rbp,建立新的栈帧
-
分配局部变量空间:
sub rsp, 0x10通过减少rsp的值来为局部变量分配空间
栈帧销毁过程
使用leave指令完成:
- 将rsp设置为rbp(释放局部变量空间)
- 从栈中弹出值到rbp(恢复前一个栈帧)
局部变量的存储与访问
局部变量存储在栈帧中,通过rbp的偏移量来访问:
int a = 972;
对应的汇编代码:
mov DWORD PTR [rbp-0x4], 0x3cc ; 0x3cc = 972
变量在栈中的顺序可能与源代码中声明的顺序不同,由编译器决定。
函数调用机制
调用函数时
- 使用
call指令:- 将返回地址(下一条指令的地址)压入栈
- 跳转到被调用函数
从函数返回时
使用ret指令:
- 从栈中弹出返回地址
- 跳转到该地址继续执行
栈帧布局分析
一个典型的栈帧包含:
- 局部变量(位于rbp下方)
- 保存的rbp值(位于rbp指向的位置)
- 返回地址(位于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函数而非原本的调用者。
关键知识点总结
- 栈增长方向:向低地址增长
- 寄存器作用:
- 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
理解栈的工作原理对于逆向工程、漏洞分析和程序优化都至关重要。通过掌握这些基础知识,可以更深入地理解程序在内存中的行为。