1. 概述: http://blog.csdn.net/chengyun_chu/article/details/4644227
在前面安全編碼實踐中我們介紹過GS編譯選項和緩衝溢出,以及資料保護DEP。首先,緩衝溢出的直接後果就是可能導致惡意代碼的遠程執行,於是編譯器提供了GS保護。但是,GS選項有自身的局限,存在若干方法可以繞過GS選項的保護。於是進一步,作業系統提供了資料執行防止,即DEP,以及與之對應的NXCOMPAT編譯選項。
那麼是不是現在我們就可以高枕無憂了?在安全領域中,系統的攻防是一個不斷髮展進化的過程。DEP提出後,就出現了針對DEP的Ret2libc攻擊手段。這一點我們曾在介紹DEP的安全編碼實踐文章的最後簡單提及過。
ASLR(Address Space Layout Randomization),地址空間格局的隨機化,就是用來防範Ret2libc攻擊手段的另一個重要的安全特性。那麼,什麼是Ret2libc攻擊,ASLR的原理是什麼,開發人員如何使用這個安全特性,就是我們這篇文章要探討的內容。
2. DEP和Ret2libc攻擊
2.1 DEP對堆疊溢位的保護
在棧溢出介紹中提及到Windows體繫結構下函數堆棧布局(地址從高向低)如下:
調用參數 |
返回地址 |
EBP上層函數堆棧基址 |
異常處理代碼入口地址 (如果函數設定異常處理) |
局部變數 |
表1:Windows系統的函數堆棧結構
如果發生堆疊溢位,惡意代碼通過覆蓋在堆棧(stack)上的局部變數,從而修改函數的返回地址,而導致惡意代碼執行。下面是這類攻擊方式的堆棧結構的一個典型例子。
調用參數 |
覆蓋方向—> |
惡意代碼 |
返回地址 |
惡意代碼的入口地址 |
EBP上層函數堆棧基址 |
溢出的變數覆蓋地區,往往包括必要的填充位元組 |
異常處理代碼入口地址 (如果函數設定異常處理) |
局部變數 |
表2:堆疊溢位時的堆棧結構
當DEP保護機制被使用後,由於惡意代碼是存放在系統的資料頁面(堆棧頁面上),那麼函數返回時,指令寄存器EIP將跳轉到惡意代碼的入口地址。此時該頁面是非可執行檔(non-executable),於是DEP就會觸發系統異常而導致程式中止。
2.2 Ret2libc攻擊
在上述的DEP保護機制中,可以看到關鍵是在函數返回時EIP跳轉到了非可執行頁面時被DEP檢測到。那麼Ret2libc的攻擊原理是,攻擊者設定的函數的返回地址並不直接指向惡意代碼,而是指向一個已存在的系統函數的入口地址。由於系統函數所在的頁面許可權是可執行檔,這樣就不會觸發DEP異常。
那麼,攻擊者應該將EIP控制指向那個特殊的系統入口函數?一個例子是在Unix 系統下,libc是一個共用的C動態執行庫,裡面有許多非常有用的函數,例如system函數。它的定義如下:
int system(const char *string);
函數system()可通過運行環境來執行其它程式,例如啟動Shell等等。那麼,攻擊者就可以通過構造以下的堆棧結構【1】:
調用參數 |
覆蓋方向—> |
/bin/sh |
虛假的返回地址 |
返回地址 |
system函數的入口地址 |
EBP上層函數堆棧基址 |
溢出的變數覆蓋地區,往往包括必要的填充位元組 |
異常處理代碼入口地址 (如果函數設定異常處理) |
局部變數 |
表3:Ret2libc攻擊的堆棧結構
這樣,當發生堆疊溢位的函數返回時,EIP跳轉到system函數。因為system函數本身就是可執行檔,這時不會產生DEP異常。攻擊者通過構造system函數的調用參數來可以啟動其它程式。在攻擊過程中,函數返回到libc庫(return to libc)是關鍵,這也就是Ret2libc名字的來由。
細心的讀者也許已經發現,在表3中,沒有任何惡意代碼被插入。攻擊者雖然可以通過system或者其它系統函數來執行很多敏感的操作,但在多數情況下,還是更希望可以執行自身定製的惡意代碼。如何可以做到這一點?於是在最初的Ret2libc的攻擊方式的基礎上,又發展出特別針對Windows系統攻擊的手段。它的原理是通過VirtualProtect函數來修改惡意代碼所在記憶體頁面的執行許可權,然後再將控制轉移到惡意代碼。
VirtualProtect是Windows系統kernel32.dll提供的函數,其功能是修改調用進程所在虛擬位址空間(virtual address)的記憶體地區的保護許可權。它的定義如下:
BOOL WINAPI VirtualProtect(
__in LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD flNewProtect,
__out PDWORD lpflOldProtect
);
攻擊者構造以下的堆棧結構【2】來調用VirtualProtect:
調用參數 |
覆蓋方向—> |
惡意代碼 |
lpflOldProtect值 |
設定可執行許可權參數 |
惡意字碼頁面的大小 |
惡意代碼所在記憶體頁面的基址 |
惡意代碼的入口地址 |
返回地址 |
VirtualProtect函數的入口地址 |
EBP上層函數堆棧基址 |
溢出的變數覆蓋地區,往往包括必要的填充位元組 |
異常處理代碼入口地址 (如果函數設定異常處理) |
局部變數 |
表4:使用VirtualProtect攻擊的堆棧結構
首先,當發生堆疊溢位的函數返回時,EIP跳轉到VirtualProtect函數。注意到這裡攻擊者特別構造將惡意代碼的入口地址作為VirtualProtect函數退出時的返回地址。由於在VirtualProtect的執行過程中,惡意代碼所在的頁面被修改為可執行許可權,這樣當VirtualProtect返回時,EIP再跳轉到惡意代碼時就不會觸發任何DEP異常。
除了使用VirtualProtect函數,攻擊者還可以使用其它函數,例如NtSetInformationProcess等等。
3. ASLR和/dynamicbase連結選項
在上面對Ret2libc攻擊方式的介紹中,我們看到最為關鍵的一點是攻擊者事先預知了特定函數,如system或VirtualProtect的入口地址。在Windows XP或Windows 2000上,這些函數的入口地址是固定的,即攻擊者事先可以確定的。
在Windows Vista中引入了ASLR安全特性。它的原理就是在當一個應用程式或動態連結程式庫,如kernel32.dll,被載入時,如果其選擇了被ASLR保護,那麼系統就會將其載入的基址隨機設定。這樣,攻擊者就無法事先預知動態庫,如kernel32.dll的基址,也就無法事先確定特定函數,如VirtualProtect,的入口地址了。
ASLR是系統一級的特性。系統動態庫,如kernel32.dll,載入地址,是在系統每次啟動的時候被隨機設定的。
下面是一個簡化的ASLR示範程式【3】。
// aslr.cpp : Demo the dynamic base of DLLs due to ASLR
//
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
void foo( void )
{
printf( "Address of function foo = %p/n", foo );
}
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE hMod = LoadLibrary( L"Kernel32.dll" );
// Note—this is for release builds
HMODULE hModMsVc = LoadLibrary( L"MSVCR90.dll" );
void* pvAddress = GetProcAddress(hMod, "LoadLibraryW");
printf( "Kernel32 loaded at %p/n", hMod );
printf( "Address of LoadLibrary = %p/n", pvAddress );
pvAddress = GetProcAddress( hModMsVc, "system" );
printf( "MSVCR90.dll loaded at %p/n", hModMsVc );
printf( "Address of system function = %p/n", pvAddress );
foo();
if( hMod ) FreeLibrary( hMod );
if( hModMsVc ) FreeLibrary( hModMsVc );
return 0;
}
這段程式的目的是輸出kerner32.dll和msvcr90.dll的基址,loadlibrary和system函數的入口地址,以及應用程式本身一個函數foo()的入口地址。
使用ASLR非常簡單。從Visual Studio 2005 SP1開始,增加了/dynamicbase連結選項。/dynamicbase選項可以通過Project Property -> Configuration Properties -> Linker -> Advanced -> Randomized Base Address,或直接修改linker的命令列編譯選項即可。見。
圖1:/dynamicbase連結選項配置
在Visual Studio 2008環境,用Win32 Console Application類型,編譯連結示範程式。注意,如果使用Visual Studio 2005 SP1的話,需要將msvcr90.dll更改為msvcr80.dll。
如果程式沒有使用ASLR功能的話,在Windows Vista下運行。輸出的結果是:
Kernel32 loaded at 763F0000
Address of LoadLibrary = 7641361F
MSVCR90.dll loaded at 671F0000
Address of system function = 6721C88B
Address of function foo = 00401800
重啟系統
Kernel32 loaded at 76320000
Address of LoadLibrary = 7634361F
MSVCR90.dll loaded at 6A340000
Address of system function = 6A36C88B
Address of function foo = 00401800
我們看到,即使程式本身沒有使用ASLR,Kernel32.dll和MSVCR90.dll的載入地址也發生了變化。這是因為這兩個庫都已經選擇了被ASLR保護。但是應用程式自身foo()函數的地址是固定的。
如果程式使用ASLR功能的話,在Windows Vista下運行。輸出的結果是:
Kernel32 loaded at 763F0000
Address of LoadLibrary = 7641361F
MSVCR90.dll loaded at 671F0000
Address of system function = 6721C88B
Address of function foo = 003B1800
重啟系統
Kernel32 loaded at 76320000
Address of LoadLibrary = 7634361F
MSVCR90.dll loaded at 697A0000
Address of system function = 697CC88B
Address of function foo = 00871800
應用程式自身函數foo()的載入地址也隨著系統重啟發生了變化。即一旦使用了/dynamicbase選項,產生的程式在運行時候就會受到ASLR機制的保護。
4. ASLR的局限
首先,ASLR安全特性只在Windows Vista和其後的Windows版本(如Windows Server 2008)中實現。
其次,ASLR是需要和DEP配合使用的。如果CPU不提供對於DEP的硬體支援,或者應用程式沒有選擇被DEP保護的話,惡意代碼一旦可以執行,就可以通過程式進程表結構來獲得特定DLL的載入基址。
就效能和相容性而言,ASLR的實現上都做了考慮,沒有太多的影響。一個範例是Microsoft Office 2007。Office 2007的程式全面使用ASLR功能,並沒有發現對其效能和相容性帶來太大的影響【4】。
5. 總結
ASLR安全特性在Windows Vista和其後的Windows版本(如Windows Server 2008)中實現。它可以防範基於Ret2libc方式的針對DEP的攻擊。ASLR和DEP配合使用,能有效阻止攻擊者在堆棧上運行惡意代碼。建議開發人員使用/dynamicbase連結選項讓開發的應用程式或動態連結程式庫使用ASLR功能。
6. 參考文獻
【1】 Bypassing non-executable-stack during exploitation using return-to-libc,http://www.infosecwriters.com/text_resources/pdf/return-to-libc.pdf,c0ntex
【2】 A Brief History of Exploitation Techniques & Mitigations on Windows,
http://hick.org/~mmiller/presentations/misc/exploitation_techniques_and_mitigations_on_windows.pdf, Matt Miller
【3】 Writing Secure Code for Windows Vista, Michael Howard, David LeBlanc
【4】 Use of ASLR, NX, etc,http://blogs.msdn.com/david_leblanc/archive/2008/03/14/use-of-aslr-nx-etc.aspx,David LeBlanc