深入淺出CPU眼中的函式呼叫&棧溢位攻擊

CuriosityWang發表於2024-05-30

深入淺出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

相關文章