测试代码:
#include <stdio.h>
int test(int a,int b)
{
int c = a + b;
printf("%d\n", c);
return c;
}
int main(int argc, char const *argv[])
{
test(1,2);
}
关闭堆栈保护进行编译::
gcc -no-pie -fno-stack-protector 1.c -o test
检查一下:
checksec test

使用 ida 看程序伪代码:


使用 gdb 动调:
gdb test
反汇编 main 函数:
disass main

和我们在 ida 看到的是一样的

我们主要来看函数调用栈的过程
在 main 函数调用 test 函数处下断点、运行:
b *0x40118e
r
进入函数内部:
si

程序首先会进入到 endbr64
这个是 Intel CPU 为了缓解 ROP 做的一个防护措施(可以直接看做 NOP 指令,什么都不做)
此时:
rbp 0x7fffffffdeb0
首先保存原来 main 函数的栈帧
push rbp
可以看到 0x7fffffffdeb0 入栈到栈顶

接下来,生成子函数(test函数)的栈帧:
mov rbp, rsp
将 rbp 指向 rsp,也就是将当前的栈顶和栈底重合
此时可以看到 rsp 和 rbp 在一条线上了,都指向 0x7fffffffdeb0

接下来,开辟一段新的内存空间
sub rsp, 0x20
开辟 32 字节的内存空间,rsp 上移 4 行(每一行是 8 字节)
可以看出来,栈是从高地址往低地址生长

至此,新的栈帧已经形成
接下来,将寄存器 edi 中的值(即函数的第一个int 参数,这里是 1)存到栈上 rbp - 0x14 的位置
mov dword ptr [rbp - 0x14], edi
查看 rbp - 0x14 处的值:
x $rbp-0x14

同理,将寄存器 esi 中的值存到栈上 rbp - 0x18 的位置

继续移动到 edx 和 eax
mov edx, dword ptr [rbp - 0x14]
mov eax, dword ptr [rbp - 0x18]

接下来,eax 和 edx 相加,保存结果放到 eax
add eax, edx
加完之后,eax 会变成 3

将结果写到 rbp - 4 的位置( 这里将函数的返回值 3 保存在栈上的局部变量 [rbp - 4] 中)
mov dword ptr [rbp - 4], eax

然后又移动到 eax(一般 eax 代表函数的返回值)
mov eax, dword ptr [rbp - 4]
看起来其实是多余的,主要是为了确保局部变量的内存地址和返回值寄存器都保持一致
接下来,将返回值移动到 esi,为后续调用 printf 函数做准备
mov esi, eax
然后,将 rip + 0xea5 处的内容移动到 rax,然后再移动到 rdi
lea rax, [rip + 0xea5]
mov rdi, rax

x/s 0x402004
内容是 %d\n ,作为 printf 函数的第一个参数
%d: 这是格式说明符,告诉 printf 将后续的参数(即 ESI 寄存器中的整数值 3)以十进制整数形式打印出来
参数准备就绪,调用 printf 函数:
call printf@plt

结果输出了 3

之后,取 rbp - 4 处的值给到 eax 作为 test 函数的返回值
mov eax, dword ptr [rbp - 4]
然后执行
leave
ret

leave 等价于:
mov rsp, rbp ; 把栈顶指针恢复到函数开始时的位置(取消局部变量空间)
pop rbp ; 恢复原来的基址指针
ret 等价于:
pop rip ;从栈顶弹出一个值给到 rip
leave 执行前:
rsp 指向 0x7fffffffde70,rbp 指向 0x7fffffffde90
00:0000│ rsp 0x7fffffffde70 —▸ 0x7ffff7fc1000 ◂— jg 0x7ffff7fc1047
01:0008│-018 0x7fffffffde78 ◂— 0x100000002
02:0010│-010 0x7fffffffde80 ◂— 2
03:0018│-008 0x7fffffffde88 ◂— 0x31f8bfbff
04:0020│ rbp 0x7fffffffde90 —▸ 0x7fffffffdeb0 ◂— 1
执行后:
先将 rsp 和 rbp 重叠,都指向 0x7fffffffde90
弹出 rbp 后,rsp 下移 8字节,指向 0x7fffffffde98
00:0000│ rsp 0x7fffffffde98 —▸ 0x401193 (main+34) ◂— mov eax, 0
01:0008│-010 0x7fffffffdea0 —▸ 0x7fffffffdfc8 —▸ 0x7fffffffe270 ◂— '/root/test/test'
02:0010│-008 0x7fffffffdea8 ◂— 0x100401050
03:0018│ rbp 0x7fffffffdeb0 ◂— 1

rbp 恢复成原始 main 函数的栈帧
此时的 rbp 是 0x7fffffffdeb0,和我们一开始保存的是一样的

继续执行 ret,其中 0x401193 就是我们的返回地址
将这个地址弹给 rip,又回到了 main 函数上继续执行后续的指令

491

被折叠的 条评论
为什么被折叠?



