更改函數的返回地址


這是網絡安全老師布置的實驗,覺得是大學以來做過的最有意思的一個實驗。

Task Description:

C語言編寫程序,包含一個函數,改變函數的返回地址,使函數返回后跳轉到某個指定的指令位置,而不是函數調用后緊跟的位置。

先上代碼:

#include <stdio.h>

void foo(){
    int a, *p;
    p = (int*)((int)&a + 8);
    *p += 12;
}

int main(){
    foo();
    printf("First printf call\n");
    printf("Second printf call\n");
    return 0;
}

編譯運行,結果輸出

Second printf call.

並沒有輸出First printf call.達到預期效果。

 

原理並不復雜:在函數體中修改return地址,即找到return的地址的位置,然后修改它。

下面詳細介紹下實驗過程:

編譯:
gcc main.c -g

反編譯:

objdump a.out -d

得到部分匯編代碼如下:

080483e4 <foo>:
 80483e4:    55                       push   %ebp
 80483e5:    89 e5                    mov    %esp,%ebp
 80483e7:    83 ec 10                 sub    $0x10,%esp
 80483ea:    8d 45 fc                 lea    -0x4(%ebp),%eax
 80483ed:    83 c0 08                 add    $0x8,%eax
 80483f0:    89 45 f8                 mov    %eax,-0x8(%ebp)
 80483f3:    8b 45 f8                 mov    -0x8(%ebp),%eax
 80483f6:    8b 00                    mov    (%eax),%eax
 80483f8:    8d 50 0c                 lea    0xc(%eax),%edx
 80483fb:    8b 45 f8                 mov    -0x8(%ebp),%eax
 80483fe:    89 10                    mov    %edx,(%eax)
 8048400:    c9                       leave  
 8048401:    c3                       ret    

08048402 <main>:
 8048402:    55                       push   %ebp
 8048403:    89 e5                    mov    %esp,%ebp
 8048405:    83 e4 f0                 and    $0xfffffff0,%esp
 8048408:    83 ec 10                 sub    $0x10,%esp
 804840b:    e8 d4 ff ff ff           call   80483e4 <foo>
 8048410:    c7 04 24 f0 84 04 08     movl   $0x80484f0,(%esp)
 8048417:    e8 fc fe ff ff           call   8048318 <puts@plt>
 804841c:    c7 04 24 02 85 04 08     movl   $0x8048502,(%esp)
 8048423:    e8 f0 fe ff ff           call   8048318 <puts@plt>
 8048428:    b8 00 00 00 00           mov    $0x0,%eax
 804842d:    c9                       leave  
 804842e:    c3                       ret    
 804842f:    90                       nop

 

從上述的匯編代碼中,我們可以看到foo后面的指令地址是8048410,而進入調用printf("Second printf call“)的指令是地址804841c, 二者相差12,故我們應該將返回地址的值+12即可。

再說點函數調用的過程:

調用函數的過程是這樣的:
調用者從右往左將參數入棧,再調用call 指令,call 指令完成事情:

1) 將返回地址入棧,2)修改eip寄存器,即計算機組成原理里面說的PC,使程序的指令指針指向被調用函數的起始指令地址。

接着就進入了被調用的函數的函數體中,C語言的函數調用習慣是:
pushl %ebp,

movl %esp %ebp

再對棧頂寄存器esp減去一個值,為函數中的局部變量預留空間。(因為棧的增長方向是從高地址往低地址增長的)

好的,介紹完畢。

 

從上述的匯編代碼中:

80483ea: 8d 45 fc lea -0x4(%ebp),%eax

這一句匯編代碼:把-4(%ebp)的地址賦值給%eax寄存器。對應的C語言的代碼是:
p = &a;

所以我們便從中可以知道:foo()函數中的變量a是存儲在-4(%ebp)位置的,即圖中的Local Variable1的位置

如此看來,a的地址往上8個單位就是return的地址。
所以p = &a + 8就獲得了return返回值跳轉地址的地址。

對這個地址的內容進行修改:
*p += 12 // 之所以+12剛才已經分析出來了。

便完成了讓函數調用結束后跳轉到第二個printf函數調用的位置。

 

ps:

自己在做得時候,用gdb驗證了一些知識,比較有意思:
如,查看%eip里面的值,發現的確是指向當前程序的指令的地址,就是PC的效果

在剛進入foo函數的時候,查看棧頂位置存儲的值,發現是8048410,心中一塊大石頭落下來了,棧頂果然是return的指令地址。

gdb的部分截圖:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM