深入浅出CPU眼中的函数调用&栈溢出攻击

深入浅出CPU眼中的函数调用——栈溢出攻击

原理解读

函数调用,大家再耳熟能详了,我们先看一个最简单的函数:

#include <stdio.h>
#include <stdlib.h>

int func1(int a, int b){
	int c = a + b;
  return c;
}

int main(){
    int res = func1();
    printf("%d", res);
}

image-20240530160651597

函数调用前,调用者会先把需要传递的参数保存在对应的寄存器中,然后就会调用call函数,call函数会做两件事情,第一件事情是把下一条指令的地址入栈(对应的上图22行的指令地址),第二件事情是跳转到fun1所在的位置执行;跳转过去之后,首先要开辟当前函数的栈帧,对应push rbp; mov rbp, rsp两条指令;再往后便是

我们知道,局部变量都是保存在栈上的,每个函数也有自己的栈帧,在整个函数调用的过程中,我们回顾一下栈帧的变化;

image-20240530164206034

整个函数调用过程如上图

  1. call指令先将调用者函数的下一条指令入栈,之后跳转到被调用函数执行。

  2. 被调用者函数先将调用者函数的栈帧基地址入栈。

  3. 然后开辟自己的栈帧,主要是将ebp设置为自己的栈帧基地址。

    ...执行相关的函数操作...

  4. pop rbp将ebp恢复为main函数栈帧。

  5. ret将下一条指令程序计数器PC,然后该指令出栈。(有点像pop)。

栈溢出攻击

实验环境:

image-20240530165754151

经过上面的介绍,我们会发现,栈是从高地址向低地址增长的,并且局部变量都保存在当前栈帧的基地址之下;当前栈帧的基地址之上则包含了当前函数的返回地址,那么是不是可以通过某种方式,去修改这个返回地址,来实现栈溢出攻击呢,答案是可以的;

#include <stdio.h>
#include <stdlib.h>

void func2(){
    printf("☠️☠️☠️☠️☠️");
    exit(4);
}

void func1(){
    long a[2];
    a[1] = 1;
    a[0] = 2;
    // 修改返回地址
    a[4] = (long)func2;
}

int main(){
    func1();
    printf("hello");
}

CamScanner 05-30-2024 16.40_02_0

这段代码我们通过数组越界写,使用a[3]修改当前函数的返回地址,使得其去执行fun2。代码执行如下:

tackAttack.cpp:14:5: warning: array index 4 is past the end of the array (which contains 2 elements) [-Warray-bounds]
    a[4] = (long)func2;
    ^ ~
stackAttack.cpp:10:5: note: array 'a' declared here
    long a[2];
    ^
1 warning generated.
☠️☠️☠️☠️☠️
[Done] exited with code=4 in 0.231 seconds

我们可以发现虽然编译器提示我们数组发生越界,但是并没有阻止,将返回地址修改后,程序执行了我们的恶意代码,并且没有输出hello

posted @ 2024-05-30 21:58  CuriosityWang  阅读(20)  评论(0编辑  收藏  举报