C/C++ and Buffer Overflow Topics
原創作品,允許轉載,轉載時請務必以超鏈接形式標明文章原始出處、作者信息和本聲明。否則將追究法律責任。http://blog.csdn.net/taotaoyouarebaby/article/details/24010649
之前翻譯的一份文檔,未逐字翻譯,只翻譯了主要知識點。複製到網頁後,格式有點亂,提供PDF下載
英文原文網址:http://www.tenouk.com/cncplusplusbufferoverflow.html
緩衝區溢出由於病毒與蠕蟲在互聯網的大規模影響而爲人熟知。C/C++程序因爲緩衝區溢出,產生了許多安全問題。
1. 介紹
1.1. 產生緩衝區溢出的情況:
n 使用非類型安全的語言:C/C++,無數組邊界檢查和類型安全檢查。
n 以不安全的方式操作或複製一個棧緩衝區。
eg:未正確使用strcpy(), gets(), scanf(), sprintf(), strcat()操作字符串,導致其它區域被複寫。
n 編譯器將緩衝區與重要的數據結構放的太近。
緩衝區與函數返回地址、virtual-table,局部變量,異常handler地址,函數指針都放在棧中相鄰區域。使得可以通過緩衝區溢出,覆寫以上重要的數據結構,從而引導程序執行惡意代碼。
比如:如果將函數返回地址修改爲惡意代碼地址,那麼在函數返回時就會執行相應的惡意代碼。
1.2. 緩衝區溢出的後果:
www.cert.org Computer Emergency Response Team (CERT)
www.frsirt.com 代碼示例
www.caida.org 病毒與蠕蟲攻擊的分析
n 程序崩潰。
n 運行惡意代碼。
1.3. 緩衝區溢出的相關概念
緩衝區(Buffer):一塊內存區域,用於存儲變量。有以下兩種類型的緩衝區:
n 棧(Stack):運行時隱式分配的,用於存儲變量的一塊內存區域。棧結構。
n 堆(Heap):運行時顯示分配的,用於存儲變量的一塊內存區域。堆結構。
溢出類型 |
描述 |
棧溢出 |
向一個緩衝區寫入數據時,大於了分配給它的內存大小。很有可能複寫棧中的其它重要數據,進而破壞棧結構。通常是由於未檢查用戶輸入數據造成的。 |
堆溢出 |
與棧溢出類似。這類溢出不易被惡意利用。 |
數組溢出 |
數組下標爲負,或大於最大下標。 |
2. X86架構基礎——32位處理器
2.1. 基本的寄存器
寄存器種類 |
|
8個32位的通用寄存器 |
|
6個16位的段寄存器/段選擇器 |
FS、GS在Itel32中引入的 |
1個32位的的標誌寄存器(EFLAGS) |
|
1個32位的指令寄存器(EIP) |
通用寄存器的主要作用:
Register Name |
Size (in bits) |
Purpose |
AL, AH/AX/EAX |
8,8/16/32 |
也叫做累加器,主要用於保存算術運算結果和函數返回值。 |
BL, BH/BX/EBX |
8,8/16/32 |
基址寄存器,指向DS段中的數據,用於保存程序的基址。 |
CL, CH/CX/ECX |
8,8/16/3 |
計數器,通常用於循環計數和字符串操作 |
DL, DH/DX/EDX |
8,8/16/32 |
通常用於I/O操作,也用於擴展EAX爲64位。 |
SI/ESI |
16/32 |
源地址寄存器。指向DS段中的數據,常被用來作爲字符串和數組操作中的偏移量,保存數據源的地址。 |
DI/EDI |
16/32 |
目的地址寄存器。指向ES段中的數據,常被用來作爲字符串和數組操作中的偏移量,保存目標地址。 |
BP/EBP |
16/32 |
棧基址指針寄存器。保存當前棧結構底部的地址,指向SS段中的數據,通常用於引用局部變量。 |
SP/ESP |
16/32 |
棧頂指針寄存器(SS)。指向當前棧結構的頂部,也用於引用局部非靜態變量。 |
2.2. 段寄存器
6個段寄存器保存了段地址的高16位(低位爲0),由此定位內存中的段。4個數據段寄存器:DS, ES, FS, GS。爲高效而安全的訪問不同類型的數據提供了支持。
eg:可能的4種數據段
l 當前模塊的數據 l 上層模塊輸出的數據 l 動態創建的數據 l 程序間共享的數據 |
X86段寄存器及其用處:
寄存器 |
位 |
目的 |
|
CS |
16 |
代碼段寄存器。代碼段基地址(.text 段), 用於獲取指令。 |
這些段寄存器用於將程序分成不同的部分。當程序執行時,各段的基地址賦給了段寄存器。通過段寄存和偏移量就可以操作程序的不同內存區域。 |
DS |
16 |
數據段寄存器。 數據的默認基地址(.data 段), 用於操作數據。。 |
|
ES |
16 |
Extra段寄存器,用於字符串操作。 |
|
SS |
16 |
棧段寄存器,棧段的基地址,配合SP, ESP, BP, EBP使用。 |
|
FS |
16 |
通用的段寄存器 |
|
GS |
16 |
注:
l CS不能由程序設置。
l SS可以能和程序設置,從而一個程序可以有多個棧。
2.3. 內存模型
內存模型 |
支持的地址 |
flat 內存模型 |
near pointers (32 bits) |
segmented內存模型 |
near pointers (32 bits)、far pointers (48 bits) |
real-address模式內存模型 |
20bit bus |
2.3.1. Flat內存模型
線性地址空間:一個程序的代碼、數據和棧全都在該地址空間中。
2.3.2. Segmented內存模型
程序使用的內存被分爲幾個獨立的地址空間(叫做段)。代碼、數據和棧通常被分成單獨的段。段模型的地址究竟與處理器的物理地址空間之間的映射:直接映射與分頁機制(虛擬內存:段地址->虛擬內存地址->物理地址)
地址定位:
segment:offset |
計算:
通常的段寄存器使用方式:
2.3.3. real-address mode (實模式)內存模型
用於兼容8086程序。內存分段,每段<=64KB,最大訪存空間1M.
2.4. 標誌寄存器
標誌類型:狀態、控制、系統標誌
2.4.1. 狀態標誌:
Flag |
Bit |
Purpose |
CF |
0 |
進位標誌。算術操作中,如果最高位發生進位或借位則被設置上,否則清空。該標誌指示了無符號整型變量,在算術運算時的溢出情況。它也可用於多精度算術運算。 |
PF |
2 |
Parity flag. Set if the least-significant byte of the result contains an even number of 1 bit, cleared otherwise. |
AF |
4 |
Adjust flag. Set if an arithmetic operation generates a carry or a borrow out of bit 3 of the result, cleared otherwise. This flag is used in Binary-Coded-Decimal (BCD) arithmetic. |
ZF |
6 |
Zero flag. Set if the result is zero, cleared otherwise. |
SF |
7 |
Sign flag. Set equal to the most-significant bit of the result, which is the sign bit of a signed integer. 0 indicates a positive value, 1 indicates a negative value. |
OF |
11 |
Overflow flag. Set if the integer result is too large a positive number or too small a negative number, excluding the sign bit, to fit in the destination operand, cleared otherwise. This flag indicates an overflow condition for signed-integer that is two’s complement arithmetic. |
2.5. EIP指令地址寄存器
不能通過指令顯式操作,只能利用程序流程控制指令操作,此外在調用函數時可以從函數棧中取得。
Register |
size (bits) |
Purpose |
IP/EIP |
16/32 |
保存下一條要執行的指令的地址。 |
2.6. 控制寄存器
32位的控制寄存器(CR0, CR1, CR2, CR3, and CR4)用於決定處理器的執行模式,以及當前所執行的任務的特性。
Control Register |
Description |
CR0 |
控制標識,用於控制處理器的執行模式與狀態。 |
CR1 |
保留 |
CR2 |
包含產生缺頁的線性地址。 |
CR3 |
包含頁目錄的基址(物理地址)和兩個標識(PCD and PWT)。也叫:頁目錄基址寄存器(PDBR)。 只有頁目錄基址只有高20位被指定,低12位設定爲0。 When using the physical address extension, the CR3 register contains the base address of the page-directory-pointer table. |
CR4 |
Contains a group of flags that enable several architectural extensions. In protected mode, the move-to-or-from-control-registers forms of the MOV instruction allow the control registers to be read (at any privilege level) or loaded (at privilege level 0 only). This restriction means that application programs (running at privilege levels 1, 2, or 3) are prevented from loading the control registers; however, application programs can read these registers. |
2.7. 小端與大端
CD12AB90H
Big Endian |
Little Endian |
高位à低地址 |
高位à高地址 |
CD12AB90H |
CD12AB90H |
3. 彙編語言
通用規則:
l 源:內存、寄存器、常量
l 目標:內存、非段寄存器
l 源與目標不能同時爲內存
l 源與目標必須具有同樣大小。(位?)
指令分類:
Instruction Category |
Meaning |
Example |
Data Transfer |
move from source to destination |
mov, lea, les, push, pop, pushf, popf |
Arithmetic |
arithmetic on integers |
add, adc, sub, sbb, mul, imul, div, idiv, cmp, neg, inc, dec, xadd, cmpxchg |
Floating point |
arithmetic on floating point |
fadd, fsub, fmul, div, cmp |
Logical, Shift, Rotate and Bit |
bitwise logic operations |
and, or, xor, not, shl/sal, shr, sar, shld and shrd, ror, rol, rcr and rcl |
Control transfer |
conditional and unconditional jumps, procedure calls |
jmp, jcc, call, ret, int, into, bound.
|
String |
move, compare, input and output |
movs, lods, stos, scas, cmps, outs, rep, repz, repe, repnz, repne, ins |
I/O |
輸入輸出 |
in, out |
Conversion |
彙編數據類型轉換 |
movzx, movsx, cbw, cwd, cwde, cdq, bswap, xlat |
Miscellaneous |
manipulate individual flags, provide special processor services, or handle privileged mode operations |
clc, stc, cmc, cld, std, cl, sti |
4. 編譯器、彙編器、鏈接器和加載器
處理流程:
4.1. 對象文件與可執行文件
對象文件格式:
Object File Format |
Description |
a.out |
a.out格式是UNIX最初的執行文件格式。它由一部分組成:text, data, 和bss,分別表示代碼, 已初始化數據, 和未初始化數據。 不包含調試信息。 |
COFF |
COFF (Common Object File Format) 格式由System V Release 3 (SVR3) Unix引入。 COFF 可以有多個部分,每部分以一下特定的header作爲前綴,數量受限。支持調試,但調試信息有限。 |
ECOFF |
COFF的變種。 ECOFF 爲 Mips and Alpha 工作站設計. |
XCOFF |
XCOFF (eXtended COFF),COFF sections, symbols, and line numbers are used, 調試信息保存在.debug section (rather than the string table). The default name for an XCOFF executable file is a.out. |
PE |
PE(Portable Executable) 是由 COFF 和其它一些頭信息構成。Windows 9x and NT使用PE做執行文件的格式。 |
ELF |
ELF (Executable and Linking Format) 格式由 System V Release 4 (SVR4) Unix引入。 ELF與 COFF 相似,但沒有COFF的一些限制。 ELF 被用於現代UNIX系統、GNU/Linux, Solaris 和Irix。也用於一些嵌入式系統。 |
SOM/ESOM |
SOM (System Object Module) and ESOM (Extended SOM) is HP's object file and debug format |
Section可能包含的內容:
l 代碼 l 數據 l 動態鏈接信息 l 調試信息 l 符號表 l 重定位信息 l 註釋 l 字符串表 l Note |
所有可執行文件格式都包含的Section:編譯器不同可能名稱不同。
Section |
Description |
.text |
包含程序指令,該程序的所有進程共享此部分。READ, EXECUTE權限。 |
.bss |
BSS(Block Started by Symbol)包含未初始化的全局變量與靜態變量。 因爲BSS包含的變量沒有值,所有該部分並沒有保存變量的映像,只是在對象文件中記錄了運行時該部分需要的內存大小。也就是說,.BSS並沒有佔用實際的對象文件空間。 |
.data |
包含已初始化的全局變量與靜態變量,以及值。 該部分往往是可執行文件中最大的。READ/WRITE權限。 |
.rdata |
也叫做.rodata (read-only data) section. 包含常量與字符串常量。 |
.reloc |
保存在加載時,重定位映像所需要的信息。 |
Symbol table |
符號表:也就是變量/函數名,及其定義地址(相對於段的偏移量)。 包含定位程序符號引用與定義時,所需要的信息。 |
Relocation records |
Relocation 是連接符號引用與定義的過程。 relocation records用於鏈接器調整section內容。 |
4.2. Relocation Records
Relocation:分配加載地址,並按加載地址調整程序與數據的過程。在鏈接器對Object文件進行鏈接時,需要將所有Object文件中的相同section進行合併,並重新分配section的地址,從而合併爲一個單獨的可執行文件。進而導致可能需要修改原section中的部分symbol地址,使其滿足新的section。
Relocation Records:是由編譯器與匯率器創建的一個指針列表,保存在對象文件或可執行文件中。每一個條目表示了加載器重定位時需要修改的地址。用於支持程序的重定位。
4.3. Linker
使源文件單獨編譯成爲可能。
4.3.1. 動態鏈接
對於C標準庫中的函數,如果每個程序都單獨複製一份,那麼會造成很大的浪費。因此,將這類鏈接延遲到運行時進行。鏈接器只是將動態鏈接所需要知道的信息放到了可執行文件中:代碼存在於哪個共享庫中,使用哪個運行時鏈接器去查看和鏈接。
優點:
l 程序更小
l 使得動態升級程序成爲可能。通過DLL
l 可以使程序有計劃的自行加載當前需要的模塊
l 與虛擬內存結合,使得進程可以共享同一份代碼,大大節省了內存。
4.3.2. 靜態鏈接
將程序依賴的代碼全部鏈接進來。適用於程序運行時,環境中找不到需要鏈接的標準庫版本。缺點是生成的文件很大。
eg: GCC靜態鏈接
gcc -static filename.c -o executable-filename |
4.4. 怎樣使用共享的Object文件
4.4.1. ELF格式詳情
簡化了共享庫實現,增強了運行時模塊的動態加載。使用Hash表進行symbol查找。
ELF section列表:
//從低到高 .init - Startup .text - String .fini - Shutdown .rodata - Read Only .data - Initialized Data .tdata - Initialized Thread Data .tbss - Uninitialized Thread Data .ctors - Constructors .dtors - Destructors .got - Global Offset Table .bss - Uninitialized Data |
簡化的ELF文件格式:
Linking View :Section,包含指令、數據、重定位信息、符號表、調試信息……
Execution View :Segment,合併了對象文件中相關的Sections(也許是不同類型的Section,比如:.data與bbs被合併)爲一個段。通常可執行代碼與只讀數據的section被合併爲一個text段。其中有些段是需要加載的,但有些段是不需要加載的。
操作系統利用Program Header table提供的信息加載需要的段,並且可以利用這些段來生成共享的內存資源。
4.4.2. 進程加載
Linux進程將ELF格式文件從文件系統中加載。如果文件系統是塊設備,則需要將代碼與數據加載到主存中;如果文件系統是由內存映射的(eg:ROM/FLASH),代碼將在原地執行。如果相同的進程被加載了多次,那麼它的代碼將被共享。程序需要先加載,之後才能運行。
l 加載器Loader的加載過程:
1. 內存與接入權限驗證:
操作系統讀取程序文件中的頭信息,之後驗證type,access permissions and right, 內存需求,以及是否支持程序指令。驗證文件是可執行文件,並計算內存需求。
2. 進程安裝
1) 分配主存 2) 將地址空間從輔存複製到主存 3) 複製.text, .data 段到主存 4) 複製程序參數(如:命令行參數)到堆棧 5) 初始化寄存器:設置ESP指向棧頂,清空其它。 6) 跳到啓動點:複製main()的參數,跳到main()函數。 |
l 簡化的進程內存空間:
注意Stack與Heap的位置與增長方向。
4.4.3. 進程運行時的數據結構
內存分配的不同區域及含義:
區域 |
描述 |
Code/text segment |
text段,保存指令,對應執行文件中的text section。任何時候,一個程序的指令在內存中只有一份。 |
Initialized data – data segment |
包含初始化爲非0值的靜態變量和全局變量,對應執行文件中的data section。同一程序的每個進程擁有單獨的data段。 |
Uninitialized data – bss segment |
BSS 表示‘Block Started by Symbol’. 包含初始化爲0值的靜態變量和全局變量。進程私有。在ELF格式中,只有非0值變量才佔用可執行文件的空間。 |
Heap |
動態內存區域,由malloc(), calloc(), realloc() and new – C++等進行分配,並通過指針來操作。 緊隨bss段,Heap結束位置由break指令標記,大小可以通過brk(), sbrk()進行擴展(改變break指針)。向地址增大方向增長。 |
Stack |
棧段保存局部非靜態變量,如:局部非靜態變量,臨時信息與數據,函數參數,返回地址。當前調用函數時,對需要的信息進行壓棧,返回時彈出棧。 向地址減小方向增長(與Heap相反)。 |
當程序運行時,initializeddata, BSS and heap areas通常合併爲data段,stack段和 code/text 段是與data段分離的。
Sections vs Segments:executable program segments andtheir locations.
Executable file section (disk file) |
Address space segment |
Program memory segment |
.text |
Text |
Code |
.data |
Data |
Initialized data |
.bss |
Data |
BSS |
- |
Data |
Heap |
- |
Stack |
Stack |
4.4.4. 進程
進程空間中位於stack與heap中間的區域是保留給共享代碼的。
典型的C程序進程的內存佈局(X86):
4.4.5. 運行時鏈接器與共享庫加載
對於共享代碼的鏈接時間:
l 加載時動態鏈接:加載到內存時進行鏈接
l 運行動態鏈接:引用時才進行鏈接
鏈接器的鏈接步驟:
共享庫:提供其依賴的其它庫信息,需要的重定位操作,查找外部符號。
1. 鏈接器開始加載共享庫依賴的其它庫(遞歸進行)
2. 爲每個庫,執行需要的重定位操作和涉及到的符號查找操作。
3. 在共享庫的.initsection註冊的初始化函數將會被調用。
4.4.6. 動態地址翻譯
動態定位/動態地址翻譯提供了以下錯覺:
l 每個進程都能使用使0到Max的地址空間
l 地址究竟是受保護的
l 進程可以認爲其可以使用的內存(虛擬內存)大於物理內存大小。
地址翻譯由內存管理單元(MMU:Memory Management Unit)與處理器協作完成。
5. C/C++函數操作
5.1. C函數
l 語法:
global_variables;
int main(int argc, char *argv[]) { function_name(argument list); function_return_address here }
return_type function_name(parameter list) { local_variables;
static variables; function’s code here return something_or_nothing; }
|
函數調用通過棧實現。發生函數調用時,需要的信息將被壓棧,函數返回時將內容出棧。
l 棧的使用,進程地址空間與物理地址空間映射:
5.2. 函數調用約定(VC++)
描述了函數調用時棧的創建與銷燬操作是如何進行的。不同的函數調用規則,方式不同。
5.2.1. 函數調用的一般過程:
還可參考:Win/Intel平臺,函數調用過程的數據入棧順序
1. 所有參數都被增寬到4字節 (onWin32, of course),並被存儲到合適的內存位置。通常的位置是棧,也可能是寄存器,由調用規則不同而不同。
2. 程序執行流程跳轉到被調用函數(地址)
3. 進入函數後,ESI,EDI, EBX, EBP 寄存器值被保存到棧上,該操作由編譯器自動生成的代碼執行。
4. 函數指令被執行,返回值保存在EAX中。
5. 從棧上恢復ESI,EDI, EBX, EBP的值。 該操作由編譯器自動生成的代碼執行。
6. 清除棧上保存的參數,也叫做清棧。該操作可以由調用者或被調用者執行,取決於調用規則。
5.2.2. 具體指定以下三種規則:
1. 函數參數的壓棧順序
2. 清棧是調用者還是被調用函數的任務
3. 函數名命名規則:編譯器用來標識一個函數的名字
5.2.3. VC++支持的函數調用規則:
只有__cdecl是由調用者清棧。
keyword |
Stack cleanup |
Parameter passing |
__cdecl |
caller |
函數參數從右到左進行壓棧,調用者清棧。這是C/C++的默認調用方式。__cdecl調用方式產生的執行文件比__stdcall產生的要大,因爲每個函數都需要清棧代碼。但支持變長參數列表。 |
__stdcall |
callee |
也叫做 __pascal。 函數參數從右到左進行壓棧,被調用者清棧,需要一個函數原型(?)。Win32 API函數的標準調用方式(WINAPI)。 |
__fastcall |
callee |
參數優先考慮通過寄存器傳遞,其次是棧,被調用者清棧。最開頭的兩個<=32bit的參數分別由ECX, EDX傳遞,其它的按右到左的順序壓棧。
|
Thiscall |
callee |
C++成員函數的調用方式。壓棧順序從右到左,this指針由ECX傳遞。對於帶可變參數列表的的成員函數,this指針的傳遞方式不同,this指針最後入棧。被調用者清棧。 |
5.2.4. 代碼中指定函數調用規則:
// Borland and Microsoft void __cdecl TestFunc(float a, char b, char c);
// GNU GCC void TestFunc(float a, char b, char c) __attribute__((cdecl)); |
5.2.5. 清棧的彙編表示:
/* example of __cdecl */ push arg1 push arg2 call function add ebp, 12 ;stack cleanup
/* example of __stdcall */ push arg1 push arg2 call function /* no stack cleanup, it will be done by caller */
|
5.3. 鏈接符號與名稱修飾
void CALLTYPE TestFunc(void)
Calling convention |
extern "C" or .c file |
.cpp, .cxx |
Remarks |
__cdecl |
_TestFunc |
?TestFunc@@ZAXXZ |
參數數目並不重要,因爲調用者負責創建和銷燬棧。 |
__fastcall |
@TestFunc@N |
?TestFunc@@YIXXZ |
N—參數的byte數,0表示void。 |
__stdcall |
_TestFunc@N |
?TestFunc@@YGXXZ |
N—參數的byte數,0表示void。 |
示例:C語言
Function declaration/prototype |
Decorated name |
void __cdecl TestFunc(void); |
_TestFunc |
void __cdecl TestFunc(int x); |
_TestFunc |
void __cdecl TestFunc(int x, int y); |
_TestFunc |
void __stdcall TestFunc(void); |
_TestFunc@0 |
void __stdcall TestFunc(int x); |
_TestFunc@4 |
void __stdcall TestFunc(int x, int y); |
_TestFunc@8 |
void __fastcall TestFunc(void); |
@TestFunc@0 |
void __ fastcall TestFunc(int x); |
@TestFunc@4 |
void __ fastcall TestFunc(int x, int y); |
@TestFunc@8 |
5.4. 函數調用棧
函數調用中涉及的寄存器:
Register |
Description |
ESP – Stack Pointer |
通過PUSH, POP, CALL,RET來修改,總是指向當前棧的棧頂。 |
EBP – Base Pointer |
也叫做: Frame Pointer. 直接通過偏移量操作參數與局部變量。 |
EIP – Instruction Pointer |
下一條指令的地址。 |
函數調用的棧:
6. Stack
6.1. 處理器的Stack Frame佈局
不同操作系統可以有所不同。由上圖可知,如果緩衝區溢出,可以覆寫其它重要的數據結構。
6.1.1. Win/Intel平臺,函數調用過程的數據入棧順序
參考:函數調用的一般過程:
1. 在進行函數調用之前,參數被壓棧(右->左)
2. 函數返回地址(執行call指令時的EIP值),由call指令入棧。
3. 棧幀指針(EBP)入棧。保存之前的棧幀地址。
4. 如果函數包含異常處理結構(try/catch, SEH<Structured Exception Handling>),編譯器添加的異常處理上將入棧。
5. 分配局部變量、緩衝區空間
6. 最後,被調用者將EBX, ESI,EDI寄存器值被入棧。對於Linux/Intel,這一步發生在第四步之後。
6.2. 處理器的棧操作
指令 |
描述 |
PUSH |
*--SP = src. |
POP |
dst = *SP++ |
PUSHAD |
將通用寄存器值入棧。 |
POPAD |
將通用寄存器值出棧。 |
PUSHFD |
將EFLAGS寄存器值入棧。 |
POPFD |
將EFLAGS寄存器值出棧。 |
6.3. 函數調用過程及棧的分析
6.3.1. 程序源碼
#include <stdio.h> //MyFunc(7, ‘8’); int MyFunc(int parameter1, char parameter2) { int local1 = 9; char local2 = ‘Z’; return 0; } |
6.3.2. 與函數調用和棧操作相關的彙編代碼,分析
在main函數中調用MyFunc函數:
;in main MyFunc(7, '8'); 01281B1E push 38h ; ‘8’入棧, 從右往左入棧 01281B20 push 7 ; 7入棧 01281B22 call @ILT+460(_MyFunc) (12811D1h) ;調用函數,函數返回地址(EIP:01281B27 )入棧 01281B27 add esp,8 ; 將MyFunc函數棧清空 |
函數符號表及跳轉指令:
@ILT+460(_MyFunc): ;函數修飾名 12811D1 jmp MyFunc (01281490h) |
MyFunc函數:
int MyFunc(int parameter1, char parameter2) { 01281490 push ebp ;保存調用者的EBP,位於MyFunc的[EBP+0]位置 01281491 mov ebp,esp ;ESP的值成爲MyFunc的EBP值,ESP,EBP指向相同位置。 01281493 sub esp,0D8h ;減去216字節,爲變量和緩衝區分配空間。ESP位於[EBP-216]的位置 01281499 push ebx ;push ebx at [EBP-220] 0128149A push esi ;push esi at [EBP-224] 0128149B push edi ;push edi at [EBP-228] //... return 0; 012814B9 xor eax,eax ;清空EAX,返回值爲0 } 012814BB pop edi ;恢復edi from [EBP-228] 012814BC pop esi ;恢復esi from [EBP-224] 012814BD pop ebx ;恢復ebx from [EBP-220] 012814BE mov esp,ebp ;清空局部變量與緩衝區空間,ESP, EBP指向相同位置 012814C0 pop ebp ;恢復調用者的EBP 012814C1 ret ;將返回地址(01281B27H)從棧(MyFunc的[EBP+4]位置)載入EIP中, ;執行後繼指令 |
//back to main 01281B27 add esp,8 ; 清空函數參數,7和’8’共8byte(參數都擴充爲了4byte) |
注意:棧幀大小必須是棧寬度(stackslot)的整數倍。所以棧寬爲32bit的棧,5字節的數據實際佔用8字節內存,10字節數據實際佔用12字節內存。
6.3.3. 函數調用棧內存佈局
EBP寄存器被用來與偏移一起索引棧上的數據。
重要數據結構 |
棧地址 |
函數最左邊的參數 |
[EBP+8] |
函數返回地址/舊EIP |
[EBP+4] |
舊棧幀指針/舊EBP |
[EBP+0] |
第一個局部變量 |
[EBP-4] |
EBX, ESI, EDI |
ESP, ESP-4, ESP-8 |
6.3.4. 定位返回地址
int main(int argc, char *argv[ ]) { char buffer[12]; strcpy(buffer, argv[1]); return 0; } |
l 使用gcc2.96或更低版本的棧內存佈局
函數返回地址位置:&frist_local_var+ 4(EBP) + 4(ret)
l 使用gcc2.96或更高版本的棧內存佈局
函數返回地址位置:&first_local_var+ (dummy) + 4(EBP) + 4(ret),根據dummy大小調整偏移量
6.3.5. 示例:修改返回地址
void hello() { printf("hello\n"); return ; }
void stackOverflow(int a,int b) { int buf[2] = {1,2}; int *p; p = &a - 1; //函數返回地址 *p = (int)hello; return ; }
int main(void) { stackOverflow(1, 2);
getchar(); return EXIT_SUCCESS; }
// // 運行結果: hello // |
6.4. 寄存器使用
通用寄存器
l ESP, EBP用於管理函數進出;
l EBX, ESI, EDI必須在進入函數後入棧保存舊值;
l ECX, EDX, EAX只有在需要時,才入棧保存舊值。
進入函數時常見的代碼片段:
push ebx push esi push edi ; here should be codes that uses ; the EBX, ESI and EDI ;
pop edi pop esi pop ebx ret |
6.4.1. GCC與C調用規則——標準棧幀
Steps |
32-bit code/platform |
創建標準棧幀,爲局部變量與緩衝區分配32byte的空間。保存寄存器值。 |
push ebp mov ebp, esp sub esp, 0x20 push edi push esi ... |
恢復寄存器值,銷燬標準棧幀 |
... pop esi pop edi mov esp, ebp pop ebp ret |
棧的寬度 |
32 bits |
棧幀槽的位置 |
... [ebp + 12] [ebp + 8] [ebp + 4] [ebp + 0] [ebp – 4] ... |
6.4.2. GCC與C調用規則——返回值
C函數返回值存放位置:
Size |
32-bit code/platform |
8-bit return value |
AL |
16-bit return value |
AX |
32-bit return value |
EAX |
64-bit return value |
EDX:EAX |
128-bit return value |
hidden pointer |
6.4.3. GCC與C調用規則——保存寄存器值
被調用者需要保存的寄存器:
EBX, EDI, ESI, EBP, DS, ES, SS
不需要保存的寄存器:
EAX, ECX, EDX, FS, GS, EFLAGS, floating pointregisters
一些操作系統中,FS, GS段寄存器被用於保存線程局部存儲空間地址,如果你要修改它們,那也需要保存。
7. 基於棧的緩衝區溢出與利用
下面的測試代碼主要用於實現以下目的:覆寫棧上保存的EBP和返回地址。通過gets()這個不安全的函數實現以上數據的覆寫。
/* test buffer program */ #include <unistd.h>
void Test() { char buff[4]; printf("Some input: "); gets(buff); puts(buff); }
int main(int argc, char *argv[ ]) { Test(); return 0; }
|
輸入12個A:
在實際的攻擊中,會利用有意義的地址(攻擊代碼所在地址)對返回地址進行覆寫。
7.1. 緩衝區溢出攻擊中的目標
l 注入攻擊代碼(命令行輸入、socket輸入,或其它高級方法)
l 改變程序正常執行路徑(通過覆寫返回地址實現),執行攻擊代碼。
7.2. 基於棧的緩衝區溢出利用的變異
l 利用程序自身存在的緩衝區溢出漏洞,欺騙函數將大於緩衝區的數據寫入,從而覆寫返回地址,將執行路徑導向攻擊代碼。這種方式可以通過多種途徑阻止。
l 利用程序使用的共享庫中存在的緩衝區溢出漏洞,覆寫返回地址。
緩衝區溢出攻擊時,攻擊時必須要大致的知道返回地址的所在位置。
利用不可執行棧(不能在棧上執行代碼)就可以阻上大部分這類型的攻擊。
7.2.1. 更高級更新的攻擊手段:覆寫其它地址
l 函數指針
l ELF文件中的GOT指針(.got)
l ELF文件中的DTORS塊(.dtors)
阻止手段:隨機化以下地址
l 共享庫
l 棧
l 程序堆
8. Shellcode
8.1. 基本概念
產生shell/命令行環境代碼,緩衝區溢出時覆寫的返回地址,通常就是shellcode代碼所在的地址。而shellcode通常是提前編譯,並將其二進制代碼利用char數組保存爲全局變量。當程序由修改的返回地址轉到該全局變量所在位置時,就能執行該代碼,從而創建一個shell環境。利用得到的shell環境可以執行攻擊命令。
廣義是講,只要通過上述方式運行另一個程序的代碼都叫做shellcode。
通常目標:通過有較高權限的程序,使得創建的shell具有root權限(在Windows中就是管理員權限或更高的LocalSystem權限)。
8.1.1. 通常的緩衝區溢出攻擊涉及兩個主要方面:
l 緩衝區溢出漏洞的利用技術
l 獲得高權限的運行環境(playload),用於運行任意代碼
8.1.2. 使程序運行shellcode的技術:
l 基於棧的緩衝區溢出
l 基於堆的緩衝區溢出
l 整數溢出
l 格式化字符串
l 競爭條件
l 內存污染
8.1.3. Shellcode元素
shellcode必須是二進制形式的代碼。不能含有’\0’, 0X0A, 0X0D, ‘\’, nop。可以使用Encoder工具消除它們。
寫的時候需要考慮:處理器,操作系統,網絡防護軟件(如:防火牆),入侵檢測系統(IDS:Intrusion DetectionSystem)
8.2. Shellcode的不同表現形式
8.2.1. 彙編
#a very simple assembly (AT&T/Linux) program for spawning a shell .section .data .section .text .globl _start
_start: xor %eax, %eax mov $70, %al #setreuid is syscall 70 xor %ebx, %ebx xor %ecx, %ecx int $0x80
jmp ender
starter: popl %ebx #get the address of the string xor %eax, %eax mov %al, 0x07(%ebx) #put a NULL where the N is in the string movl %ebx, 0x08(%ebx) #put the address of the string #to where the AAAA is movl %ebx, 0x0c(%ebx) #put 4 null bytes into where the BBBB is mov $11, %al #execve is syscall 11 lea 0x08(%ebx), %ecx #load the address of where the AAAA was lea 0x0c(%ebx), %edx #load the address of the NULLS int $0x80 #call the kernel
ender: call starter .string "/bin/shNAAAABBBB" |
8.2.2. C語言
#include <unistd.h>
int main(int argc, char*argv[ ]) { char *shell[2];
shell[0] = "/bin/sh"; shell[1] = NULL; execve(shell[0], shell, NULL); return 0; } |
8.2.3. 字符串
char shellcode[ ] = "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50 \x53\x89\xe1\x99\xb0\x0b\xcd\x80"; |
8.3. 創建可移植的shellcode
要創建可移植的shellcode,代碼中就不能出現硬編碼的地址(比如:字符串參數地址)。
.section .data #only use register here... .section .text .globl _start
jmp dummy
_start: #pop register, so we know the string location #Here we have assembly instructions which will use the string
dummy: call _start
.string "Simple String" |
dummy標籤中使用call調用_start標籤,主要是爲了利用call會將其後的指令地址(EIP)作爲返回地址壓入棧中,這樣一來就可以在_start標籤中,從棧上彈出字符串地址。如下圖所示:
圖表1獲取字符串地址的技巧
利用這種方法,可以將多個.string放到call指令之後,利用相對位置就可以得到.string數據的位置。
9. 附錄
9.1. 中英名詞對照
stack frame/frame:棧幀,棧中一個函數佔據的空間。
section:塊
function decorated name:函數修飾名,編譯器用來標識一個函數的名稱,也就是函數ID(唯一性)。
non-executable stack:不可執行棧,棧上不能執行代碼。