从 push rbp 到 ret:GDB 视角下函数跳转与栈帧演变的保姆级追踪

测试代码:

#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 函数上继续执行后续的指令

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

My6n

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值