緩存溢出問題簡述

緩存溢出

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

緩存溢出(Buffer overflow),是指在存在緩存溢出安全漏洞的計算機中,攻擊者可以用超出常規長度的字符來填滿一個域,通常是內存區地址。這篇文章就是講解簡單的緩存溢出問題。文章以x86_32 linux 系統平臺爲藍本。爲了介紹緩存溢出,數據的存儲地址、主要的彙編指令、重要的寄存器等內容都要講解。

 

1.      變量存儲

C語言中,變量屬性有很多中,但是對於緩存溢出問題,我們主要關心的數據的存儲位置,或存儲空間。因此,這裏我們主要關心全局變量,局部變量和靜態變量。在C語言中,通常全局變量和靜態變量被分配於數據段(data section)中,但是局部變量分配在棧(stack)中。另外,大家清楚,棧空間中還存儲CSIP等一些列與指令地址相關的重要數據,因此,在對局部變量進行數據拷貝的時候,如果拷貝數據塊過大,就可能將IPCS等寄存器存放的數據空間覆蓋掉,寫入一些非法的數據,當從設IP值時,計算機就跳轉到新的非法數據代碼空間了。這裏有一個簡單的C文件,overflow1.c

int data = 0x66666666;

 

int func1(void)

{

        static int sdata = 0x55555555;

        int ret = 0;

        return ret;

}

 

int main(int argc, char *argvs[])

{

        func1();

        return 0;

}

 

我們首先對它進行編譯,然後查看編譯後的信息。

 

# gcc –o overflow1.o –c overflow1.c

# objdump –t overflow1.o

 

over3.o:     file format elf32-i386

 

SYMBOL TABLE:

<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />00000000 l    df *ABS*  00000000 over3.c

00000000 l    d  .text  00000000 .text

00000000 l    d  .data  00000000 .data

00000000 l    d  .bss   00000000 .bss

00000004 l     O .data  00000004 sdata.1280

00000000 l    d  .note.GNU-stack        00000000 .note.GNU-stack

00000000 l    d  .comment       00000000 .comment

00000000 g     O .data  00000004 data

00000000 g     F .text  00000012 func1

00000012 g     F .text  0000001e main

 

       從結果中我們很容易找到data sdata變量的size, section等信息。如果想獲取變量存儲的地址,可以連接後在執行該命令。但是對於局部ret,恐怕你不容易找到它的存放地址,那麼ret的空間在哪裏呢? 不要着急,我們繼續。

       # objdump –d overflow1.o

 

over3.o:     file format elf32-i386

 

Disassembly of section .text:

 

00000000 <func1>:

   0:   55                      push   %ebp

   1:   89 e5                    mov    %esp,%ebp

   3:   83 ec 10                  sub    $0x10,%esp

   6:   c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

   d:   8b 45 fc                  mov    -0x4(%ebp),%eax

  10:   c9                       leave 

  11:   c3                       ret   

 

00000012 <main>:

  12:   8d 4c 24 04             lea    0x4(%esp),%ecx

  16:   83 e4 f0                and    $0xfffffff0,%esp

  19:   ff 71 fc                 pushl  -0x4(%ecx)

  1c:   55                      push   %ebp

  1d:   89 e5                   mov    %esp,%ebp

  1f:   51                      push   %ecx

  20:   e8 fc ff ff ff              call   21 <main+0xf>

  25:   b8 00 00 00 00           mov    $0x0,%eax

  2a:   59                      pop    %ecx

  2b:   5d                      pop    %ebp

  2c:   8d 61 fc                lea    -0x4(%ecx),%esp

  2f:   c3                      ret   

 

從彙編代碼裏面恐怕還是不明白ret在哪裏存儲吧?不用擔心,我們回頭看看C源文件,在func1函數裏有一個 int ret = 0 的聲明,聰明的你現在是不是找到對應的彙編語句了,你猜對了,就是下面這句話:

   6:   c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

 

也就是說,ret的空間分配在-0x4%(ebp)指向的空間中。從

1:   89 e5                    mov    %esp,%ebp

你會發現ebp存放的是esp的內容,也就說-0x4%(ebp)指向的是棧空間地址,而ret就存放在哪裏。

 

現在明白了變量的存放空間了,下面我們要繼續講解關於棧中其它的信息。

 

2.      重要指令和寄存器

爲了更好的瞭解緩存溢出,就要了解與其相關的指令和寄存器。我在這部分內容中會講解這些信息。

IPCSebpesp是我們要講的寄存器。CS:IP指向將要執行的指令的存儲地址。一般的函數跳轉指令和函數調用指令就是通過修改CS:IP的值來達到跳轉目的。當指令段發生改變時,CS寄存器的數值才改變,在同一個指令段中,通常只改變IP的數值就可以了。我們今天介紹的默認只是通過修改IP的值而達到跳轉目的。Esp寄存器存放的當前棧頂地址,而ebp作爲一個備份寄存器,保存着進入新函數後esp的值。

 

在重要的指令中,主要有push, pop, call, ret, leave. Push pop是一對棧操作指令,push完成入棧操作,將數據寫入棧中,並更新esp的內容,而pop指令與其相反,它將數據從棧中取出,並更新esp的內容。 其中 call指令是函數調用指令,它主要完成指令計數器寄存器(IP) 的入棧操作(爲了簡單期間,這裏不考慮CS入棧問題,有興趣的同學可以去查看引用文獻),類似於指令 “push ip”。而ret指令與call指令相反,是一個函數返回指令,將IP的值從棧中彈出,類似”pop ip”Leave也可以看成一個符合指令,它類似於 “mov ebp, esp; pop ebp” 兩個指令的效果。

 

瞭解了上面的基本知識,我們來分析一下彙編代碼。爲了清楚期間,我們將overflow1.o 進行連接,然後查看連接後的重要彙編代碼片段。

# gcc –o overflow1 overflow.o

# objdump –d overflow1

…..

08048324 <func1>:

 8048324:       55                      push   %ebp

 8048325:       89 e5                    mov    %esp,%ebp

 8048327:       83 ec 10                 sub    $0x10,%esp

 804832a:       c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

 8048331:       8b 45 fc                  mov    -0x4(%ebp),%eax

 8048334:       c9                       leave 

 8048335:       c3                       ret   

 

08048336 <main>:

 8048336:       8d 4c 24 04             lea    0x4(%esp),%ecx

 804833a:       83 e4 f0                and    $0xfffffff0,%esp

 804833d:       ff 71 fc                pushl  -0x4(%ecx)

 8048340:       55                      push   %ebp

 8048341:       89 e5                   mov    %esp,%ebp

 8048343:       51                      push   %ecx

 8048344:       e8 db ff ff ff          call   8048324 <func1>

 8048349:       b8 00 00 00 00          mov    $0x0,%eax

 804834e:       59                      pop    %ecx

 804834f:       5d                      pop    %ebp

 8048350:       8d 61 fc                lea    -0x4(%ecx),%esp

….

 

從對func1函數開始調用開始,我們跟蹤棧裏面的內容。當call指令執行後,計算機會跳到func1函數處繼續執行,現在就將這些指令合併:

8048344:       e8 db ff ff ff          call   8048324 <func1>

8048324:       55                      push   %ebp

8048325:       89 e5                    mov    %esp,%ebp

8048327:       83 ec 10                 sub    $0x10,%esp

804832a:       c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

執行後的結果是什麼呢?

       第一條指令是將IP壓棧;

       第二條指令將ebp壓棧

       第三條是將esp的值保存到ebp

       第四條指令更新esp的值,向前16bytes

       第五條指令給ret賦初值0,並且可以確定ret的地址是%ebp - 4

 

因此,我們得到當前棧的值

                     IP                  0x88-0x8B      ;;High address 

Ebp->   oldEBP           0x84-0x87      ;; ebp = 0x84

        ret                 0x80-0x83

Nil                 0x7c-0x7f

Nil                 0x78-0x7b

Esp->   Nil                 0x74-0x77      ;; esp = 0x74

   ….                 ….                 ;;low address

 

並且ebp會作爲備份寄存器保留老的esp寄存器的值,當函數返回時,還原espebp,以及IP。緩存溢出就是在還原之前首先將棧中IP的值修改成其餘的數值,從而是CPU跳轉到一個錯誤地址或無效地址。

如果依照上面棧的地址,那麼ret變量的地址應該是0x80, 而老的IP數據的存儲地址應該是0x88,如果在向ret進行數據拷貝時,數據過長,將會覆蓋oldebpIP的地址,從而導致程序在返回時,將錯誤的IP值彈出到指令計數器中,CPU將會跳轉到該錯誤地址進行代碼執行。下面提供了兩個案例程序,給大家參考。

Examples

 

Overflow2中是一個典型的內存溢出,作者通過向一個局部變量數組中寫入過長數據,使程序無條件跳轉的my_func() 一個非法函數中。

 

#include <stdio.h>

#include <string.h>

 

char strs[32] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, /

                 0xc4, 0x83, 0x04, 0x08, 0xc4, 0x83, 0x04, 0x08, /

                  0xc4, 0x83, 0x04, 0x08, 0xc4, 0x83, 0x04, 0x08} ;

/*my_func的地址是0x080483c4*/

int my_func(void)

{

        printf("in My Func!/n");

        return 87;

}

         

int print(void)

{        

        int tmp = 0x33;

        int ret = 0x22;

        char str[4];

        char *data;

        strncpy(str, strs, 24);

 

        return ret;

}

 

int main(int argc, char *argvs[])

{

        int ret = print();

 

        printf("ret = %x/n", ret);

 

        return 0;

}

 

 

Overflow3.c 是一個不通過函數調用,強制跳轉到my_func()函數,併成功返回到主函數。

 

#include <stdio.h>

#include <string.h>

 

int my_func(void)

{

        printf("in My Func!/n");

        return 87;

}

 

int print(void)

{

        int ret = 0x22;

        int str[4];

 

        asm (                           /

                "mov 0(%%ebp), %%ebx;   /

                 mov %%eax, 0(%%ebp);   /

                 push %%eax;            /

                 sub $4, %%ebp;         /

                 mov %%ebx, 0(%%ebp)"   /

                :               /

                : "a"(my_func));

 

        return ret;

}

 

int main(int argc, char *argvs[])

{

        int ret = print();

        printf("ret = %x/n", ret);

 

        return 0;

}

 

 

Reference

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章