栈帧为何可存储指令?栈中指令执行机制及攻击场景疑问
栈帧中存储的指令执行机制疑问
问题简述
给出的汇编指令如下:
0: 48 c7 c7 ee 4f 37 45 mov $0x45374fee, %rdi 7: 68 60 18 40 00 pushq $0x401860 c: c3 retq
疑问:上述存储在栈帧中的3条指令如何被执行?栈帧不是仅存储数据吗?它也能存储指令?数据会被读取到寄存器,但这些指令是怎么被执行的?
问题详述
自学CMU 15-213《计算机系统》课程时,在Attack Lab第二阶段遇到栈帧被攻击代码覆盖的场景:调用getbuf()的返回地址被覆盖为%rsp指向的栈顶地址,而该栈顶地址处已注入上述攻击代码。
之前通过《CSAPP》了解到栈帧仅存储寄存器溢出的数据(包括返回地址、额外参数等),不明白为什么栈帧还能存储指令(攻击代码)并被执行。具体困惑点:
%rsp指向的栈帧内容是通过什么机制被执行的?%rsp存储的是调用函数的返回地址(是地址而非指令),这个地址是如何被当作指令执行的?
解答
1. 内存无“数据/指令”物理划分,栈可执行是前提
CPU执行指令的核心逻辑是根据程序计数器(%rip)指向的内存地址,读取该地址开始的字节序列作为指令解码执行。内存本身只是字节序列的容器,所谓“代码区”“数据区”“栈区”是操作系统和编译器的逻辑划分,默认会给栈区设置不可执行权限,但Attack Lab的实验环境特意关闭了栈执行保护,让栈段被标记为可执行——所以栈里的字节序列只要符合x86-64指令格式,就能被CPU当作指令执行。
2. 攻击代码的执行流程
这个攻击的核心是篡改getbuf()的返回地址:
- 正常流程:
getbuf()执行retq指令时,会从栈中弹出原本的返回地址,将其加载到%rip,CPU就会跳去执行该地址的指令。 - 攻击流程:我们把栈中存储的原返回地址替换为攻击代码的起始地址(即
%rsp指向的栈顶位置)。当getbuf()执行retq时,这个栈地址会被加载到%rip,CPU接下来就会从栈的这个位置开始读取字节,解码成你给出的三条指令并依次执行:mov $0x45374fee, %rdi:将目标值加载到%rdi,作为后续函数调用的第一个参数pushq $0x401860:把目标函数的地址压入栈中retq:弹出栈顶的目标函数地址并加载到%rip,跳去执行该函数
3. 纠正对%rsp的误解
%rsp是栈指针,它指向栈帧的栈顶(当前栈中可用的最高地址位置),它本身并不存储返回地址——返回地址是保存在栈帧的固定偏移位置里的。攻击中修改的是栈里存储的返回地址值,让它指向栈中注入代码的位置,而非修改%rsp本身。
内容的提问来源于stack exchange,提问作者sociala
相关产品推荐
相关产品推荐

