2019 3月所學(xué) 棧溢出入門級方法

一、操作系統(tǒng)

操作系統(tǒng)是管理計算機硬件資源,控制其他程序運行并為用戶提供交互操作界面的系統(tǒng)軟件的集合。操作系統(tǒng)是計算機系統(tǒng)的關(guān)鍵組成部分,負(fù)責(zé)管理與配置內(nèi)存、決定系統(tǒng)資源供需的優(yōu)先次序、控制輸入與輸出設(shè)備、操作網(wǎng)絡(luò)與管理文件系統(tǒng)等基本任務(wù)。操作系統(tǒng)的種類很多,各種設(shè)備安裝的操作系統(tǒng)可從簡單到復(fù)雜,可從手機的嵌入式操作系統(tǒng)到超級計算機的大型操作系統(tǒng)。目前流行的現(xiàn)代操作系統(tǒng)主要有Android、BSD、iOS、Linux、Mac OS X、Windows、Windows Phone和z/OS等,除了Windows和z/OS等少數(shù)操作系統(tǒng),大部分操作系統(tǒng)都為類Unix操作系統(tǒng)。

操作系統(tǒng)可以將函數(shù)調(diào)用棧的起始地址設(shè)為隨機化(這種技術(shù)被稱為內(nèi)存布局隨機化,即Address Space Layout Randomization (ASLR) ),這樣程序每次運行時函數(shù)返回地址會隨機變化。反之如果操作系統(tǒng)關(guān)閉了上述的隨機化(這是技術(shù)可以生效的前提),那么程序每次運行時函數(shù)返回地址會是相同的,這樣我們可以通過輸入無效的溢出數(shù)據(jù)來生成core文件,再通過調(diào)試工具在core文件中找到返回地址的位置,從而確定 shellcode 的起始地址。

操作系統(tǒng)打開了 ASLR,程序每次運行時動態(tài)庫的起始地址都會變化,也就無從確定庫內(nèi)函數(shù)的絕對地址。在 ASLR 被關(guān)閉的前提下,我們可以通過調(diào)試工具在運行程序過程中直接查看 system() 的地址,也可以查看動態(tài)庫在內(nèi)存的起始地址,再在動態(tài)庫內(nèi)查看函數(shù)的相對偏移位置,通過計算得到函數(shù)的絕對地址。

現(xiàn)代操作系統(tǒng)內(nèi)存通常是以分段的形式存放不同類型的信息的。

二、緩存區(qū)

1.棧,由編譯器在需要的時候分配,在不需要的時候自動清除的變量的存儲區(qū)。里面的變量通常是局部變量、函數(shù)參數(shù)等。

? 2.堆,由new分配的內(nèi)存塊,編譯器不能控制其釋放,由我們的應(yīng)用程序去控制,一般一個new就要對應(yīng)一個delete。如果程序員沒有釋放掉,那么在程序結(jié)束后,操作系統(tǒng)會自動回收。

? 3.自由存儲區(qū),由malloc等分配的內(nèi)存塊,和堆是十分相似,不過它是用free來結(jié)束自己的生命的。

? 4.全局/靜態(tài)存儲區(qū),全局變量和靜態(tài)變量被分配到同一塊內(nèi)存中,在以前的C語言中,全局變量又分為初始化的和未初始化的,在C++里面沒有這個區(qū)分了,他們共同占用同一塊內(nèi)存區(qū)。

? 5.常量存儲區(qū),這是一塊比較特殊的存儲區(qū),他們里面存放的是常量,不允許修改(可通過非正當(dāng)手段修改)

三、寄存器

通用寄存器和特殊寄存器

通用寄存器:

包括一般寄存器(eax、ebx、ecx、edx),索引寄存器(esi、edi),以及堆棧指針寄存器(esp、ebp)。

一般寄存器用來存儲運行時數(shù)據(jù),是指令最常用到的寄存器,除了存放一般性的數(shù)據(jù),每個一般寄存器都有自己較為固定的獨特用途。eax 被稱為累加寄存器(Accumulator),用以進(jìn)行算數(shù)運算和返回函數(shù)結(jié)果等。ebx 被稱為基址寄存器(Base),在內(nèi)存尋址時(比如數(shù)組運算)用以存放基地址。ecx 被稱為記數(shù)寄存器(Counter),用以在循環(huán)過程中記數(shù)。edx 被稱為數(shù)據(jù)寄存器(Data),常配合 eax 一起存放運算結(jié)果等數(shù)據(jù)。

索引寄存器通常用于字符串操作中,esi 指向要處理的數(shù)據(jù)地址(Source?Index),edi 指向存放處理結(jié)果的數(shù)據(jù)地址(Destination?Index)。

堆棧指針寄存器(esp、ebp)用于保存函數(shù)在調(diào)用棧中的狀態(tài)。

Intel 格式,寄存器名稱和數(shù)值前無符號:

AT&T 格式,寄存器名稱前加“%”,數(shù)值前加“$”:

特殊寄存器:

包括段地址寄存器(ss、cs、ds、es、fs、gs),標(biāo)志位寄存器(EFLAGS),以及指令指針寄存器(eip)。

四、內(nèi)存

內(nèi)存分段還包括堆(Heap Segment)、數(shù)據(jù)段(Data Segment),BSS段,以及代碼段(Code Segment)。代碼段存儲可執(zhí)行代碼和只讀常量(如常量字符串),屬性可讀可執(zhí)行,但通常不可寫。數(shù)據(jù)段存儲已經(jīng)初始化且初值不為0的全局變量和靜態(tài)局部變量,BSS段存儲未初始化或初值為0的全局變量和靜態(tài)局部變量,這兩段數(shù)據(jù)都有可寫的屬性。堆用于存放程序運行中動態(tài)分配的內(nèi)存,例如C語言中的 malloc() 和 free() 函數(shù)就是在堆上分配和釋放內(nèi)存。

五、棧

EIP寄存器里存儲的是CPU下次要執(zhí)行的指令的地址

EBP寄存器里存儲的是是棧的棧底指針,通常叫?;?/p>

ESP寄存器里存儲的是在調(diào)用函數(shù)fun()之后,棧的棧頂

函數(shù)調(diào)用時,調(diào)用函數(shù)(caller)的狀態(tài)被保存在棧內(nèi),被調(diào)用函數(shù)(callee)的狀態(tài)被壓入調(diào)用棧的棧頂;在函數(shù)調(diào)用結(jié)束時,棧頂?shù)暮瘮?shù)(callee)狀態(tài)被彈出,棧頂恢復(fù)到調(diào)用函數(shù)(caller)的狀態(tài)。函數(shù)調(diào)用棧在內(nèi)存中從高地址向低地址生長,所以棧頂對應(yīng)的內(nèi)存地址在壓棧時變小,退棧時變大。

1.被調(diào)用函數(shù)(callee)的參數(shù)按照逆序依次壓入棧內(nèi)(保存在調(diào)用函數(shù)(caller)的函數(shù)狀態(tài)內(nèi),之后壓入棧內(nèi)的數(shù)據(jù)都會作為被調(diào)用函數(shù)(callee)的函數(shù)狀態(tài)來保存)

2.調(diào)用函數(shù)(caller)進(jìn)行調(diào)用之后的下一條指令地址作為返回地址壓入棧內(nèi)

3.將被調(diào)用函數(shù)的返回地址壓入棧內(nèi),并將 ebp 寄存器的值更新為當(dāng)前棧頂?shù)牡刂罚ㄕ{(diào)用函數(shù)(caller)的 ebp(基地址)信息得以保存。同時,ebp 被更新為被調(diào)用函數(shù)(callee)的基地址。)

4.將調(diào)用函數(shù)的基地址(ebp)壓入棧內(nèi),并將當(dāng)前棧頂?shù)刂穫鞯?ebp 寄存器內(nèi)

5.將被調(diào)用函數(shù)(callee)的局部變量等數(shù)據(jù)壓入棧內(nèi)

6.將被調(diào)用函數(shù)的局部變量彈出棧外

7.將調(diào)用函數(shù)(caller)的基地址(ebp)彈出棧外,并存到 ebp 寄存器內(nèi)

8.將被調(diào)用函數(shù)(callee)的返回地址彈出棧外,并存到 eip 寄存器內(nèi)

四、基礎(chǔ)棧溢出

發(fā)生條件:1.程序要有向棧內(nèi)寫入數(shù)據(jù)的行為。2.程序并不限制寫入數(shù)據(jù)的長度。

基本案例:

1.利用棧溢出執(zhí)行攻擊指令

(一)發(fā)生原理:當(dāng)溢出數(shù)據(jù)中包括攻擊指令的內(nèi)容或地址,并且攻擊指令獲得程序的控制權(quán)時。

(二)控制程序執(zhí)行指令最關(guān)鍵的寄存器就是 eip,目標(biāo)就是讓 eip 載入攻擊指令的地址。讓溢出數(shù)據(jù)用攻擊指令的地址來覆蓋返回地址。(在溢出數(shù)據(jù)內(nèi)包含一段攻擊指令,或在內(nèi)存其他位置尋找可用的攻擊指令。)

(三)將原本指定的函數(shù)在調(diào)用時替換為其他函數(shù)。

技術(shù):

修改返回地址,讓其指向溢出數(shù)據(jù)中的一段指令(shellcode

修改返回地址,讓其指向內(nèi)存中已有的某個函數(shù)(return2libc

修改返回地址,讓其指向內(nèi)存中已有的一段指令(ROP

修改某個被調(diào)用函數(shù)的地址,讓其指向另一個函數(shù)(hijack GOT

第一項技術(shù)

Padding?(填充)屬性定義元素邊框與元素內(nèi)容之間的空間

padding1 + address of shellcode + padding2 + shellcode

(padding1 處的數(shù)據(jù)可以隨意填充(注意如果利用字符串程序輸入溢出數(shù)據(jù)不要包含 “\x00” ,否則向程序傳入溢出數(shù)據(jù)時會造成截斷),長度應(yīng)該剛好覆蓋函數(shù)的基地址。address of shellcode 是后面 shellcode 起始處的地址,用來覆蓋返回地址。padding2 處的數(shù)據(jù)也可以隨意填充,長度可以任意。shellcode 應(yīng)該為十六進(jìn)制的機器碼格式。)

1.求返回地址之前的填充數(shù)據(jù)(padding1)長度

查看匯編代碼來確定這個距離,也可以在運行程序時用不斷增加輸入長度的方法來試探

(cyclic 100就是輸出100個亂碼字符)

2.求shellcode起始地址

在調(diào)試工具里查看返回地址的位置(可以查看 ebp 的內(nèi)容然后再加4(32位機)64位機則加8)

在調(diào)試工具里的這個地址和正常運行時并不一致,這是運行時環(huán)境變量等因素有所不同造成的。所以這種情況下我們只能得到大致但不確切的 shellcode 起始地址,解決辦法是在 padding2 里填充若干長度的 “\x90”(表示NOP)。

3.求?shellcode 所用溢出數(shù)據(jù)的最終構(gòu)造

這種方法生效的一個前提是在函數(shù)調(diào)用棧上的數(shù)據(jù)(shellcode)要有可執(zhí)行的權(quán)限(另一個前提是上面提到的關(guān)閉內(nèi)存布局隨機化)。

第二項技術(shù)

padding1 + address of system() + padding2 + address of “/bin/sh”

address of system() 是 system() 在內(nèi)存中的地址,用來覆蓋返回地址。padding2 處的數(shù)據(jù)長度為4(32位機),對應(yīng)調(diào)用 system() 時的返回地址。因為我們在這里只需要打開 shell 就可以,并不關(guān)心從 shell 退出之后的行為,所以 padding2 的內(nèi)容可以隨意填充。address of “/bin/sh” 是字符串 “/bin/sh” 在內(nèi)存中的地址,作為傳給 system() 的參數(shù)。

1.求返回地址之前的填充數(shù)據(jù)(padding1)長度

同上

2.求system() 函數(shù)地址

當(dāng)函數(shù)被動態(tài)鏈接至程序中,程序在運行時首先確定動態(tài)鏈接庫在內(nèi)存的起始地址,再加上函數(shù)在動態(tài)庫中的相對偏移量,最終得到函數(shù)在內(nèi)存的絕對地址。

3.求“/bin/sh” 的地址

在動態(tài)庫里搜索這個字符串,如果存在,就可以按照動態(tài)庫起始地址+相對偏移來確定其絕對地址。如果在動態(tài)庫里找不到,可以將這個字符串加到環(huán)境變量里,再通過 getenv() 等函數(shù)來確定地址。

第三項技術(shù)

針對程序棧溢出所要實現(xiàn)的效果,找到若干段以 ret 作為結(jié)束的指令片段,按照上述的構(gòu)造將它們的地址填充到溢出數(shù)據(jù)中。

padding + address of gadget 1 + address of gadget 2 + ...... + address of gadget n

padding + address of gadget 1 + param for gadget 1 + address of gadget 2 + param for gadget 2 + ...... + address of gadget n + shellcode

1.棧溢出之后要實現(xiàn)什么效果?

ROP 常見的拼湊效果是實現(xiàn)一次系統(tǒng)調(diào)用,Linux系統(tǒng)下對應(yīng)的匯編指令是 int 0x80。執(zhí)行這條指令時,被調(diào)用函數(shù)的編號應(yīng)存入 eax,調(diào)用參數(shù)應(yīng)按順序存入 ebx,ecx,edx,esi,edi 中。

2.如何尋找對應(yīng)的指令片段?

若干開源工具可以實現(xiàn)搜索以 ret 結(jié)尾的指令片段,著名的包括?ROPgadget、rp++、ropeme?等,甚至也可以用 grep 等文本匹配工具在匯編指令中搜索 ret 再進(jìn)一步篩選。

3.如何傳入系統(tǒng)調(diào)用的參數(shù)?

可以用 pop 指令將棧頂數(shù)據(jù)彈入寄存器。如果在內(nèi)存中能找到直接可用的數(shù)據(jù),也可以用 mov 指令來進(jìn)行傳輸(用pop更簡單)

注意:

第一,很多時候并不能一次湊齊全部的理想指令片段,這時就要通過數(shù)據(jù)地址的偏移、寄存器之間的數(shù)據(jù)傳輸?shù)确椒▉怼扒€救國”。

第二,第二,要小心 gadget 是否會破壞前面各個 gadget 已經(jīng)實現(xiàn)的部分,比如可能修改某個已經(jīng)寫入數(shù)值的寄存器。另外,要特別小心 gadget 對 ebp 和 esp 的操作,因為它們的變化會改變返回地址的位置,進(jìn)而使后續(xù)的 gadget 無法執(zhí)行。

第四項技術(shù):

GOT 全稱是全局偏移量表(Global?Offset?Table),用來存儲外部函數(shù)在內(nèi)存的確切地址。GOT 存儲在數(shù)據(jù)段(Data Segment)內(nèi),可以在程序運行中被修改。PLT 全稱是程序鏈接表(Procedure?Linkage?Table),用來存儲外部函數(shù)的入口點(entry),程序總會到 PLT 這里尋找外部函數(shù)的地址。PLT 存儲在代碼段(Code Segment)內(nèi),在運行之前就已經(jīng)確定并且不會被修改, PLT 并不會知道程序運行時動態(tài)鏈接庫被加載的確切位置。 PLT 表內(nèi)存儲的入口點就是 GOT 表中對應(yīng)條目的地址。

1.如何確定函數(shù) A 在 GOT 表中的條目位置?

程序調(diào)用函數(shù)時是通過 PLT 表跳轉(zhuǎn)到 GOT 表的對應(yīng)條目,所以可以在函數(shù)調(diào)用的匯編指令中找到 PLT 表中該函數(shù)的入口點位置,從而定位到該函數(shù)在 GOT 中的條目。

2.如何確定函數(shù) B 在內(nèi)存中的地址?

知道了函數(shù) A 的運行時地址(讀取 GOT 表內(nèi)容),也知道函數(shù) A 和函數(shù) B 在動態(tài)鏈接庫內(nèi)的相對位置,就可以推算出函數(shù) B 的運行時地址。

3.如何實現(xiàn) GOT 表中數(shù)據(jù)的修改?

找到若干條 gadget,改寫 GOT 表中數(shù)據(jù),從而實現(xiàn)函數(shù)的偽裝。

防御措施:

首先,通常情況下程序在默認(rèn)編譯設(shè)置下都會取消棧上數(shù)據(jù)的可執(zhí)行權(quán)限,這樣簡單的 shellcode 溢出攻擊就無法實現(xiàn)了。其次,可以在操作系統(tǒng)內(nèi)開啟內(nèi)存布局隨機化(ASLR),這樣可以增大確定堆棧內(nèi)數(shù)據(jù)和動態(tài)庫內(nèi)函數(shù)的內(nèi)存地址的難度。編譯程序時還可以設(shè)置某些編譯選項,使程序在運行時會在函數(shù)棧上的 ebp 地址和返回地址之間生成一個特殊的值,這個值被稱為“金絲雀”。這樣一旦發(fā)生了棧溢出并覆蓋了返回地址,這個值就會被改寫,從而實現(xiàn)函數(shù)棧的越界檢查。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容