1. 程式人生 > >深入彙編理解緩衝區溢位攻擊

深入彙編理解緩衝區溢位攻擊

1.基本知識
    子彙編程式裡,呼叫函式使用CALL偽指令,原始的傳遞引數的方法可以是使用暫存器和全域性標記(和高階語言,如C中的全域性變數,在.data段定義的標記)。但是由於這樣子函式不能模組化,而且如果程式功能稍大的話,程式碼將非常難於理解和維護,所以後來統一使用棧來管理函式呼叫,包括函式的引數傳遞,返回地址,區域性變數。這樣函式就可以模組化,並且可以寫在另一個檔案中。不過,在Linux核心中,系統呼叫都是使用暫存器傳遞引數的。
2.寫一個簡單的C程式stack.c,如下
------------------------stack.c--------------------------
#include<stdio.h>
void test_func(int,char,char);
int main(){
    test_func(1,'B','C');
    return 0;
}

void test_func(int a,char b,char c){
    int func_a=a;
    char func_b=b;
    char func_c=c;
}
------------------------------------------------------------
3.編譯這個檔案到組合語言
    $gcc -S statk.c
    編譯後生成彙編檔案stack.s,我使用的是gcc4.4.1(Ubuntu-9.10(Karmic Koala)),內容如下
--------------------------stack.s----------------------------
    .file    "stack.c"
    .text
.globl main        #全域性標記宣告,gcc使用main標記作為程式入口,而gas使用_start標記。
    .type    main, @function
main:
    pushl    %ebp
    movl    %esp, %ebp     #規範化的函式呼叫開頭:首先儲存棧頂指標
    andl    $-16, %esp        #Intel推薦16位元組對齊,為了更快的預取
    subl    $16, %esp          #棧向下增長16位元組,留出新參的記憶體,棧很特別,它從高記憶體向低記憶體增長               
    movl    $66, 8(%esp)    #將3個引數入棧,入棧是反序的,引數1即在棧最上端(低地址)
    movl    $65, 4(%esp)
    movl    $1, (%esp)         #從這裡可以知道,引數的增長方式是從低地址往高地址增長,否則
                                              #這個int引數1就要超過棧頂指標了,從另一方面理解,Intel是小端
                                             #對齊的,所有資料的地址都是低位元組地址,所以其餘位元組都放在高記憶體
    call    test_func              #呼叫函式,call偽指令會把返回地址入棧
    movl    $0, %eax          #從這裡算起要修改3行,否則使用gas,ld編譯連結後的程式最後找不到出口,會發生段錯誤,但是並不影響我們。
    leave               
    ret
    .size    main, .-main
.globl test_func
    .type    test_func, @function
test_func:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $24, %esp                 #在前面棧已經16位元組對齊,所以這裡不需要了
    movl    12(%ebp), %edx    #將引數'B'放入EDX暫存器,這裡為什麼會取得'A'將在後面用圖
                            #來表示
    movl    16(%ebp), %eax    #將引數'C'放入EAX暫存器
    movb    %dl, -20(%ebp)    #'B'是字元,所以只取一個位元組,暫存
    movb    %al, -24(%ebp)    #同上
    movl    8(%ebp), %eax     #取引數1,放入EAX暫存器
    movl    %eax, -8(%ebp)    #給func_a賦值
    movzbl    -20(%ebp), %eax    #擴充套件傳送,表示將一個位元組傳送到暫存器EAX,EAX高24位補0
    movb    %al, -1(%ebp)            #賦值給func_b
    movzbl    -24(%ebp), %eax   
    movb    %al, -2(%ebp)            #賦值給func_c
    leave
    ret
    .size    test_func, .-test_func
    .ident    "GCC: (Ubuntu 4.4.1-4ubuntu8) 4.4.1"
    .section    .note.GNU-stack,"",@progbits
-------------------------------------------------------------------------
3.修改程式
    為了便於除錯,我們將stack.s檔案修改為gas格式,也就是把main換成_start即可,可以使用vi的替換功能方便的完成,並且還要修改__main的退出程式碼,見註釋。修改以後的檔案如下:
----------------------------------stack.s------------------------------
    .file    "stack.c"
    .text
.globl _start
    .type _start, @function
_start:
    pushl    %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    $67, 8(%esp)
    movl    $65, 4(%esp)
    movl    $1, (%esp)
    call    test_func
    pushl    $0                #這兩行是修改後的
    call    exit
    .size    _start, .-_start
.globl test_func
    .type    test_func, @function
test_func:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    12(%ebp), %edx
    movl    16(%ebp), %eax
    movb    %dl, -20(%ebp)
    movb    %al, -24(%ebp)
    movl    8(%ebp), %eax
    movl    %eax, -8(%ebp)
    movzbl    -20(%ebp), %eax
    movb    %al, -1(%ebp)
    movzbl    -24(%ebp), %eax
    movb    %al, -2(%ebp)
    leave
    ret
    .size    test_func, .-test_func
    .ident    "GCC: (Ubuntu 4.4.1-4ubuntu8) 4.4.1"
    .section    .note.GNU-stack,"",@progbits
-------------------------------------------------------------------------
使用如下命令生成可以除錯的程式
    $as -o stack.o stack.s -gstabs
    $ld -dynamic-linker /lib/ld-linux.so.2 -o stack stack.o -lc
解釋:as即gas彙編器,-gstabs引數生成可供gdb除錯的資訊;
    ld是聯結器,由於使用了C庫,使用-l引數指定庫libc,由於需要動態連線庫,所以還要指定動態連線程式,使用引數-dynamic-linker來指定。
4.除錯程式
    $gdb stack
    gdb的使用方法不再詳細介紹,下面檢視一些關鍵的結果。使用一個圖來表示棧,如下(由於不能上傳圖片,見相簿:Ubuntu



5.緩衝區溢位
    上面的程式,只是用來詳細介紹函式呼叫過程中,堆疊的變化,緩衝區溢位攻擊的原理就是使用超出緩衝區的字元來填充緩衝區,這樣就有可能覆蓋棧中存放的函式返回地址,覆蓋以後,函式返回時就不能執行原來的程式,而是惡意攻擊者安排的程式碼,為什麼會覆蓋?關鍵是函式區域性變數的增長方式是從低地址向高地址增長,而這時候,call偽指令已經把函式的返回地址入棧了,所以它位於區域性變數的高階,當局部變數錯誤的增長時,就可能覆蓋返回地址。下面舉例
-----------------------------overflow.c--------------------------------
#include<stdio.h>
void func();
int main(){
    func();
    return 0;
}
void func(){
    char input[4];
    gets(input);
}
-------------------------------------------------------------------------
    main函式中呼叫了一個有名的會發生緩衝區溢位的函式gets,所以不用我們自己去編寫一個函式,使用如下命令
    $gcc -S overflow.c    //生成彙編檔案overflow.s,修改彙編檔案中main為_start以及程式出口,修改後如下
---------------------------------overflow.s-----------------------------
    .file    "overflow.c"
    .text
.globl _start
    .type    _start, @function
_start:
    pushl    %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    call    func
    movl    $0, %eax
    movl    %ebp, %esp
    popl    %ebp
    pushl    $0
    call    exit
    .size    _start, .-_start
.globl func
    .type    func, @function
func:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    leal    -12(%ebp), %eax    #leal表示將源運算元地址傳送到目的,由於gets函式的引數是地址
    movl    %eax, (%esp)        #將引數(一個地址)放入棧中,前面說過,所有呼叫函式的引數都必須倒序入棧。
    call    gets                            #呼叫函式gets
    leave
    ret
    .size    func, .-func
    .ident    "GCC: (Ubuntu 4.4.1-4ubuntu8) 4.4.1"
    .section    .note.GNU-stack,"",@progbits
-----------------------------------------------------------------------
    $as -o overflow.o overflow.s -gstabs    //編譯彙編檔案到目標檔案
    $ld -dynamic-linker /lib/ld-linux.so.2 -o overflow overflow.o -lc
    執行完上述命令以後,就可以使用gdb來除錯程式overflow了,在連結檔案的時候,會提示gets很危險,不應該使用它。
6.實驗結果
    下面使用gdb來除錯這個程式,gdb的具體使用方法就不介紹了,下面是一些截圖,能夠說明棧是怎樣被覆蓋的(由於不能上傳圖片,見相簿Ubuntu)