修改函數的返回地址


這篇隨筆源自今天看的這篇文章http://www.cnblogs.com/bluesea147/archive/2012/05/19/2508208.html

1. 如何修改函數返回地址

今天主要寫測試程序思考和驗證了一下這個問題,先看一下這個C程序

 1 #include <stdio.h>
 2 void foo(){
 3         int a, *p;
 4         p = (void*)((long)&a + 12);
 5         *p += 20;
 6 }
 7 int main(){
 8         foo();
 9         printf("First printf call\n");
10         printf("Second printf call\n");
11         printf("Third printf call\n");
12         return 0;
13 }

在我的機子上運行這個程序,結果是:

third printf call

在foo返回后直接跳到了11去執行,這個程序和我看的那篇文章的程序稍有不同,主要是我的機子Intel64架構的,指針是用8個字節來表示的,主要不同在4行,long也是8個字節的,所以gcc沒有做任何的warning,之前在一篇隨筆中就提到過,c應該是在很早的時候就支持各種類型的指針的轉換,因為這里是對指針指向的內容操作,所以用什么類型的指針都是可以的,這里是存儲的是指令,所以就用(void *)了。 第4行代碼是讓p指向調用main調用foo()時壓入棧的那個返回地址,改變這個地址的值讓它指向一個語句的開頭,就做到了更改函數的返回地址。

怎么確定如何修改p中的值讓它是另一個指令的合法地址呢,即指向另一個指令的開始,這就要用到objdump了,編譯鏈接上面的程序生成可執行文件,然后objdump -d。得到下面的片段。

 1 00000000004004f4 <foo>:
 2   4004f4:    55                       push   %rbp
 3   4004f5:    48 89 e5                 mov    %rsp,%rbp
 4   4004f8:    48 8d 45 fc              lea    -0x4(%rbp),%rax
 5   4004fc:    48 83 c0 0c              add    $0xc,%rax
 6   400500:    48 89 45 f0              mov    %rax,-0x10(%rbp)
 7   400504:    48 8b 45 f0              mov    -0x10(%rbp),%rax
 8   400508:    8b 00                    mov    (%rax),%eax
 9   40050a:    8d 50 14                 lea    0x14(%rax),%edx
10   40050d:    48 8b 45 f0              mov    -0x10(%rbp),%rax
11   400511:    89 10                    mov    %edx,(%rax)
12   400513:    5d                       pop    %rbp
13   400514:    c3                       retq   
14 
15 0000000000400515 <main>:
16   400515:    55                       push   %rbp
17   400516:    48 89 e5                 mov    %rsp,%rbp
18   400519:    b8 00 00 00 00           mov    $0x0,%eax
19   40051e:    e8 d1 ff ff ff           callq  4004f4 <foo>                    # call foo
20   400523:    bf 3c 06 40 00           mov    $0x40063c,%edi                  # printf("first..")
21   400528:    e8 c3 fe ff ff           callq  4003f0 <puts@plt>
22   40052d:    bf 4e 06 40 00           mov    $0x40064e,%edi                  # printf("second..")
23   400532:    e8 b9 fe ff ff           callq  4003f0 <puts@plt>
24   400537:    bf 61 06 40 00           mov    $0x400661,%edi                  # printf("third..")
25   40053c:    e8 af fe ff ff           callq  4003f0 <puts@plt>
26   400541:    b8 00 00 00 00           mov    $0x0,%eax
27   400546:    5d                       pop    %rbp
28   400547:    c3                       retq   
29   400548:    90                       nop
30   400549:    90                       nop
31   40054a:    90                       nop
32   40054b:    90                       nop
33   40054c:    90                       nop
34   40054d:    90                       nop
35   40054e:    90                       nop
36   40054f:    90                       nop

call foo時壓入的返回地址應該是20行的地址,0x400523,現在把這個值加20改到0x400537,就把返回值定位到了24行的指令,從上面也可以看出各條指令的大小,push是一個字節,而上面的mov帶了參數也才5個字節。

那如何確定調foo時壓入棧的那個返回地址在存儲器中的位置而好去修改它呢,現在看一下上面c程序中foo()函數對應的gas代碼

 1 foo:
 2     pushq    %rbp
 3     movq    %rsp, %rbp
 4     leaq    -4(%rbp), %rax      # 取&a
 5     addq    $12, %rax           # &a+12
 6     movq    %rax, -16(%rbp)     # p存在-16(%rbp)中
 7     movq    -16(%rbp), %rax
 8     movl    (%rax), %eax
 9     leal    20(%rax), %edx      #  *p+20=>%edx
10     movq    -16(%rbp), %rax     #   p=>%rax
11     movl    %edx, (%rax)        #   %edx=>*p
12     popq    %rbp
13     ret

從第4行可以看出a就存在棧最開始的4個字節中,a之上的肯字是入棧的 %rbp,這占8個字節, 而這之上的就是由main壓入的返回地址,因此內存中返回地址的地址就是  &a+12。

 

2。gdb的簡單使用

用gdb查看一下當匯編指令剛進入foo時棧頂的值,這個值應該要是調用foo后main中下條要執行的指令的地址。

如圖可以看到,在進入foo,執行 pushq %rbp前時,棧頂的值確實是main中調用foo之后地那個指令的地址,而我們所修改的也就是這個值。

簡單的說一說這里gdb的使用,在用gcc編譯的時候帶上-g才會把源代碼的信息放在可執行文件中,如上面我是從匯編直接編譯的,帶上-g就會把匯編的源代碼信息編進可執行碼中,這樣在gdb中才可以單步執行以及在該列出源碼的時候列出源碼。b是break的簡寫,打斷點,可以指令某一行代碼,某個函數,或某個地址(地址前加上*), 若指定一個函數,則在這個函數開始的代碼執行前停住,gdb會列出下面一行要執行的代碼,n是nexti的縮寫,可以接一個參數表示執行的代碼行數,這里我說是代碼的行數,gdb確實是這么做的,我把一行放兩個語句(用;分割),一個n也就執行過了,看來在debug的信息中,行是很重要的單位,n遇到subroutine call會直接當作一行代碼跳過,而s(stepi)會進入到函數調用內部。上面有s進入到foo中,然后用x查看棧頂的內容, x是用來查看內存中內容的(examin memory),實際上x必須跟上一些信息表示你要查看多少個字節,因為地址只會指向一個字節,只用x的話,默認是上次用過的count和letter size, 圖中的x實際上是 x/1xw, 而實際上因為地址用了8個字節來存,所以我應該用 x/1xg 的, b, h(half word), w(word), g(giant)分別表示1,2,4,8個字節,前面的數是count表示看幾個,而中間那個x表示hex,以16進制顯示,除了x還有a(address), t(binary), o(octal), d(decimal), i(instruction), c(char), s(string).  其中a(address)這個我在看虛表中內容的時候直接就把函數名給我顯示出來了,很有用

關於gdb,以后會深入的寫一些


免責聲明!

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



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