這是網絡安全老師布置的實驗,覺得是大學以來做過的最有意思的一個實驗。
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的部分截圖: