1. 程式人生 > 實用技巧 >緩衝區溢位的保護機制

緩衝區溢位的保護機制

目錄

緩衝區溢位的保護機制

緩衝區溢位原理

緩衝區是記憶體中存放資料的地方。在程式試圖將資料放到機器記憶體中的某一個位置的時候,因為沒有足夠的空間就會發生緩衝區溢位。而人為的溢位則是有一定企圖的,攻擊者寫一個超過緩衝區長度的字串,植入到緩衝區,然後再向一個有限空間的緩衝區中植入超長的字串,這時可能會出現兩個結果:一是過長的字串覆蓋了相鄰的儲存單元,引起程式執行失敗,嚴重的可導致系統崩潰;另一個結果就是利用這種漏洞可以執行任意指令,甚至可以取得系統root特級許可權。

緩衝區是程式執行的時候機器記憶體中的一個連續塊,它儲存了給定型別的資料,隨著動態分配變數會出現問題。大多時為了不佔用太多的記憶體,一個有動態分配變數的程式在程式執行時才決定給它們分配多少記憶體。如果程式在動態分配緩衝區放入超長的資料,它就會溢位了。一個緩衝區溢位程式使用這個溢位的資料將組合語言程式碼放到機器的記憶體裡,通常是產生root許可權的地方。僅僅單個的緩衝區溢位並不是問題的根本所在。但如果溢位送到能夠以root許可權執行命令的區域,一旦執行這些命令,那可就等於把機器拱手相讓了。
造成緩衝區溢位的原因是程式中沒有仔細檢查使用者輸入的引數。例如下面程式:

void func1(char *input) 
{
	char buffer[16];
	strcpy(buffer, input);
}

上面的strcpy()將直接吧input中的內容copy到buffer中。這樣只要input的長度大於16,就會造成buffer的溢位,使程式執行出錯。存在像strcpy這樣的問題的標準函式還有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在迴圈內的getc(),fgetc(),getchar()等。

當然,隨便往緩衝區中填東西造成它溢位一般只會出現Segmentation fault 錯誤,而不能達到攻擊的目的。最常見的手段是通過製造緩衝區溢位使程式執行一個使用者shell,再通過shell執行其他命令。如果該程式屬於root且有suid許可權的話,攻擊者就獲得了一個有root許可權的shell,便可以對系統進行任意操作了。

請注意,如果沒有特別說明,下面的內容都假設使用者使用的平臺為基於Intel x86 CPU的Linux系統。對其他平臺來說,本文的概念同樣適用,但程式要做相應修改。

CANNARY(棧保護)

這個選項表示棧保護功能有沒有開啟。

棧溢位保護是一種緩衝區溢位攻擊緩解手段,當函式存在緩衝區溢位攻擊漏洞時,攻擊者可以覆蓋棧上的返回地址來讓shellcode能夠得到執行。當啟用棧保護後,函式開始執行的時候會先往棧裡插入cookie資訊,當函式真正返回的時候會驗證cookie資訊是否合法,如果不合法就停止程式執行。攻擊者在覆蓋返回地址的時候往往也會將cookie資訊給覆蓋掉,導致棧保護檢查失敗而阻止shellcode的執行。在Linux中我們將cookie資訊稱為canary。

gcc在4.2版本中添加了-fstack-protector和-fstack-protector-all編譯引數以支援棧保護功能,4.9新增了-fstack-protector-strong編譯引數讓保護的範圍更廣。

因此在編譯時可以控制是否開啟棧保護以及程度,例如:

gcc -fno-stack-protector -o test test.c  //禁用棧保護
gcc -fstack-protector -o test test.c   //啟用堆疊保護,不過只為區域性變數中含有 char 陣列的函式插入保護程式碼
gcc -fstack-protector-all -o test test.c //啟用堆疊保護,為所有函式插入保護程式碼

FORTIFY

這個保護機制查了很久都沒有個很好的漢語形容,根據我的理解它其實和棧保護都是gcc的新的為了增強保護的一種機制,防止緩衝區溢位攻擊。由於並不是太常見,也沒有太多的瞭解。

舉個例子可能簡單明瞭一些:
一段簡單的存在緩衝區溢位的C程式碼

void fun(char *s) {
        char buf[0x100];
        strcpy(buf, s);
        /* Don't allow gcc to optimise away the buf */
        asm volatile("" :: "m" (buf));
}

用包含引數-U_FORTIFY_SOURCE編譯

08048450 <fun>:
  push   %ebp               ; 
  mov    %esp,%ebp

  sub    $0x118,%esp        ; 將0x118儲存到棧上
  mov    0x8(%ebp),%eax     ; 將目標引數載入eax
  mov    %eax,0x4(%esp)     ; 儲存目標引數
  lea    -0x108(%ebp),%eax  ; 陣列buf
  mov    %eax,(%esp)        ; 儲存
  call   8048320 <strcpy@plt>

  leave                     ; 
  ret

用包含引數-D_FORTIFY_SOURCE=2編譯

08048470 <fun>:
  push   %ebp               ; 
  mov    %esp,%ebp

  sub    $0x118,%esp        ; 
  movl   $0x100,0x8(%esp)   ; 把0x100當作目標引數儲存
  mov    0x8(%ebp),%eax     ; 
  mov    %eax,0x4(%esp)     ; 
  lea    -0x108(%ebp),%eax  ; 
  mov    %eax,(%esp)        ; 
  call   8048370 <__strcpy_chk@plt>

  leave                      ; 
  ret

我們可以看到gcc生成了一些附加程式碼,通過對陣列大小的判斷替換strcpy, memcpy, memset等函式名,達到防止緩衝區溢位的作用。

NX(DEP)

NX即No-eXecute(不可執行)的意思,NX(DEP)的基本原理是將資料所在記憶體頁標識為不可執行,當程式溢位成功轉入shellcode時,程式會嘗試在資料頁面上執行指令,此時CPU就會丟擲異常,而不是去執行惡意指令。
gcc編譯器預設開啟了NX選項,如果需要關閉NX選項,可以給gcc編譯器新增-z execstack引數。
例如:

gcc -z execstack -o test test.c

在Windows下,類似的概念為DEP(資料執行保護),在最新版的Visual Studio中預設開啟了DEP編譯選項。

PIE(ASLR)

一般情況下NX(Windows平臺上稱其為DEP)和地址空間分佈隨機化(ASLR)會同時工作。

記憶體地址隨機化機制(address space layout randomization),有以下三種情況

0 - 表示關閉程序地址空間隨機化。
1 - 表示將mmap的基址,stack和vdso頁面隨機化。
2 - 表示在1的基礎上增加棧(heap)的隨機化。

可以防範基於Ret2libc方式的針對DEP的攻擊。ASLR和DEP配合使用,能有效阻止攻擊者在堆疊上執行惡意程式碼。

Built as PIE:位置獨立的可執行區域(position-independent executables)。這樣使得在利用緩衝溢位和移動作業系統中存在的其他記憶體崩潰缺陷時採用面向返回的程式設計(return-oriented programming)方法變得難得多。

liunx下關閉PIE的命令如下:

sudo -s echo 0 > /proc/sys/kernel/randomize_va_space

ASLR效果

1、映像隨機化

映像隨機化是在PE檔案對映到記憶體時,對其載入的虛擬地址進行隨機化處理,這個地址是在系統啟動時確定的,系統重啟後這個地址會有變化。

2、堆疊隨機化

程式執行時隨機選擇堆疊的基址,與映像隨機化不同的是堆疊的基址不是在系統啟動時確定的,而是在程式開啟時確定的,也就是說同一個程式任意兩次執行時的堆疊基址都不相同。

3、PEB與TEB隨機化

微軟在XP SP2之後不再使用固定的PEB基址0x7FFDF000和TEB基址0x7FFDE000,而是使用具有一定隨機性的基址。

TEB存放在FS:0和FS:[0x18]處,PEB存放在TEB偏移0x30的位置

RELRO

設定符號重定向表格為只讀或在程式啟動時就解析並繫結所有動態符號,從而減少對GOT(Global Offset Table)攻擊。

檢測工具checksec

checksec是一個指令碼軟體,也就是用指令碼寫的一個檔案,不到2000行,可用來學習shell。

原始碼參見

http://www.trapkit.de/tools/checksec.html

https://github.com/slimm609/checksec.sh/

下載方法之一為

wget https://github.com/slimm609/checksec.sh/archive/1.6.tar.gz

checksec到底是用來幹什麼的?

它是用來檢查可執行檔案屬性,例如PIE, RELRO, PaX, Canaries, ASLR, Fortify Source等等屬性。

checksec的使用方法:

checksec –file /usr/sbin/sshd

一般來說,如果是學習二進位制漏洞利用的朋友,建議大家使用gdb裡peda外掛裡自帶的checksec功能,如下:

gdb-peda$ checksec start
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

溢位原理講解

參考連結

繞過參考