軟件與系統(tǒng)安全復(fù)習(xí)
課程復(fù)習(xí)內(nèi)容
其中
軟件與系統(tǒng)安全基礎(chǔ)
威脅模型
對于影響系統(tǒng)安全的所有信息的結(jié)構(gòu)化表示
本質(zhì)上,是從安全的視角解讀系統(tǒng)與其環(huán)境
用于理解攻擊者
- 什么可信、什么不可信
- 攻擊者的動機(jī)、資源、能力;攻擊造成的影響
具體場景
接受客戶端請求的Web服務(wù)器
可信:Web服務(wù)器
不可信:客戶端
- 客戶端可能發(fā)送惡意輸入
- 客戶端可能發(fā)起拒絕服務(wù)攻擊
- 客戶端有可能接管Web服務(wù)器:提取,入侵等
威脅建模
捕獲、組織和分析這些影響系統(tǒng)安全的信息的過程
結(jié)構(gòu)化推理攻擊面
- 確定入口點(diǎn)
- 從攻擊者的角度審視系統(tǒng)
- 分析、識別系統(tǒng)結(jié)構(gòu)
- 確定各種威脅及威脅嚴(yán)重程度
- 確定對策和緩解措施
漏洞、攻擊與危害
漏洞(Vulnerability):可以被對缺陷具有利用能力的攻擊者訪問并利用的缺陷
攻擊:攻擊者嘗試?yán)寐┒?/p>
- 主動、被動、DDOS
危害
攻擊成功則危害發(fā)生
安全策略與策略執(zhí)行
安全策略
-
允許什么、不允許什么
-
誰被允許做什么
安全策略是一個系統(tǒng)所應(yīng)具備的安全屬性的高層次規(guī)約
安全策略模型是安全策略的簡明(規(guī)范化)描述
策略執(zhí)行
-
為了安全策略被遵守,需要做什么
-
策略執(zhí)行的方法: 利用某些“機(jī)制”(mechanism)
- 說服
- 監(jiān)控和威懾
- 技術(shù)上禁止 (這是我們最感興趣的)
- 激勵管理
安全策略CIA模型
機(jī)密性(Confidentiality)
-
數(shù)據(jù)機(jī)密性,未授權(quán)者無法訪問
-
隱私性
信息僅能被授權(quán)者知道
完整性(Integrity)
- 數(shù)據(jù)完整性
- 系統(tǒng)完整性
系統(tǒng)中存儲的信息是正確(未被篡改的), 攻擊者無法修改被保護(hù)的信息
可用性(Availability)
- 保證系統(tǒng)及時運(yùn)轉(zhuǎn),保證不會拒絕已授權(quán)用戶
當(dāng)需要信息或服務(wù)時, 信息或服務(wù)可用, 攻擊者無法阻礙計算過程
舉個栗子
- Carol將Angelo的支票的數(shù)額從100改為1000:
- 違反完整性:由于金額被更改,支票的數(shù)據(jù)已被篡改,損害了支票的完整性。
- 李雷在抄韓梅梅的作業(yè):
- 違反機(jī)密性:李雷未經(jīng)韓梅梅的允許訪問和復(fù)制她的作業(yè),侵犯了韓梅梅的個人隱私和機(jī)密性。
- 張三注冊了域名
xidian.edu.cn
,且拒絕讓西電購買或使用此域名:- 違反可用性:張三注冊了
xidian.edu.cn
域名并拒絕讓西電購買或使用,導(dǎo)致西電無法使用該域名作為其官方網(wǎng)站,損害了西電的可用性
- 違反可用性:張三注冊了
其他安全目標(biāo)
-
隱私
-
非否認(rèn)性或可追責(zé)性
技術(shù)基礎(chǔ)
IA-32: 32位版本的x86指令集體系結(jié)構(gòu)
字節(jié)序
需要存儲的數(shù)字0x12345678
內(nèi)存模型
IA-32內(nèi)存模型
程序內(nèi)存由一系列獨(dú)立的地址空間(稱為“段”)組成。代碼、數(shù)據(jù)和棧在不同的段中
邏輯地址=段選擇器+偏移量
ip寄存器讀取指令的時候,實際上是cs:ip,通過sp寄存器訪問棧的時候,實際上是ss:sp。
分段內(nèi)存模型
保護(hù)模式下的內(nèi)存管理: 分段(必須)+ 分頁(可選)
現(xiàn)代的IA32
下添加的特殊段選擇器:GS FS
段寄存器里面存放的不再是段基地址,而是一個叫段選擇子的東西。
程序線性地址空間≤4GB, 物理地址空間≤64GB
每個段最大 232 字節(jié), IA-32 程序最多使用 16383 個段
-
物理地址是CPU訪問的實際內(nèi)存位置
-
CPU的內(nèi)存管理單元(MMU)透明地將虛擬地址(邏輯地址)轉(zhuǎn)換為物理地址
X86 Linux
系統(tǒng)的線性地址空間分層
為了充分利用和管理系統(tǒng)內(nèi)存資源,Linux采用虛擬內(nèi)存管理技術(shù),利用虛擬內(nèi)存技術(shù)讓每個進(jìn)程都有4GB
互不干涉的虛擬地址空間。
進(jìn)程初始化分配和操作的都是基于這個「虛擬地址」,只有當(dāng)進(jìn)程需要實際訪問內(nèi)存資源的時候才會建立虛擬地址和物理地址的映射,調(diào)入物理內(nèi)存頁。
寄存器與數(shù)據(jù)類型
通用寄存器
EAX:(操作數(shù)和結(jié)果數(shù)據(jù)的)AX
累加器 EBX
:(在DS段中數(shù)據(jù)的指針)基址寄存器
ECX
:(字符串和循環(huán)操作的)計數(shù)器
EDX
:(I/O指針)數(shù)據(jù)寄存器
EDI
: 變址寄存器, 字符串/內(nèi)存操作的目的地址
ESI
: 變址寄存器, 字符串/內(nèi)存操作的源地址
EBP:(SS段中的)棧內(nèi)數(shù)據(jù)指針, 棧幀的基地址, 用于為函數(shù)調(diào)用創(chuàng)建棧幀
ESP:(SS段中的)棧指針, 棧區(qū)域的棧頂?shù)刂?br>
指令指針寄存器
- x86 上 32 位的 EIP, 存放當(dāng)前代碼段中將被執(zhí)行的下一條指令的線性地址偏移
- 程序運(yùn)行時, CPU 根據(jù) CS 段寄存器和 EIP 寄存器中的地址偏移讀取下一條指令, 將指令傳送到指令緩沖區(qū), 并將 EIP 寄存器值自增, 增大的大小即被讀取指令的字節(jié)數(shù)
- 不能直接修改EIP,修改途徑:
- 指令JMP,Jcc,CALL,RET
- 中斷或異常
RIP 相對尋址
x64 允許指令在引用數(shù)據(jù)時使用相對于 RIP 的地址,常用于訪問全局變量, 全局變量 a 常通過 a(%rip) 進(jìn)行訪問
數(shù)據(jù)類型
棧幀
是將調(diào)用函數(shù)和被調(diào)用函數(shù)聯(lián)系起來的機(jī)制, 棧被分割為棧幀,棧幀組成棧。棧幀的內(nèi)容包含
- 函數(shù)的局部變量
- 向被調(diào)用函數(shù)傳遞的參數(shù)
- 函數(shù)調(diào)用的聯(lián)系信息(棧幀相關(guān)的指針:棧幀基址針,返回指令指針)
CALL指令語義(及后繼指令語義)
-
將EIP的當(dāng)前值(返回指令指針)壓棧
-
將CALL的目標(biāo)指令(被調(diào)用函數(shù)的第一條指令)的地址偏移載入EIP寄存器
-
CALL指令結(jié)束后開始執(zhí)行被調(diào)用函數(shù)
- PUSH EBP: 將調(diào)用函數(shù)的棧幀的 EBP 壓棧
- ESP -> EBP,確立新棧幀(被調(diào)用函數(shù)棧幀)的基址針
- 執(zhí)行被調(diào)用函數(shù)的具體功能
-
RETN指令執(zhí)行之前
- EBP -> ESP: 清空當(dāng)前被調(diào)用函數(shù)的棧幀(此時ESP指向的棧頂內(nèi)容恰好為調(diào)用者函數(shù)的EBP)(可選)
- POP EBP: 將EBP恢復(fù)為調(diào)用者函數(shù)的原始EBP
-
將棧頂?shù)膬?nèi)容(返回指令指針)彈出到EIP
-
若RETN指令有參數(shù)n, 則將ESP增加n字節(jié), 從而釋放棧上的參數(shù)恢復(fù)對調(diào)用函數(shù)的執(zhí)行
棧幀基指針: 由 EBP
指向的被調(diào)用函數(shù)棧幀的固定參考點(diǎn)
返回指令指針: 由 CALL 指令壓入棧中的 EIP
寄存器中的指令地址
指令集和調(diào)用慣例
匯編代碼風(fēng)格
AT&T: source在destination前
在較早期的GNU工具中使用
Intel: destination在source前, “[. . .]”含義類似于解引用
#AT&T
mov $4,%eax
mov $4,%(eax)
#Intel
mov eax,4
mov [eax],4
-
代碼段:存放可執(zhí)行程序的代碼,可讀不可寫
-
數(shù)據(jù)段:存放程序中已經(jīng)初始化的靜態(tài)(全局)變量,可讀寫
-
bss段:存放程序中未初始化的靜態(tài)(全局)變量,可讀寫
-
堆(heap):存放動態(tài)分配的內(nèi)容,需要程序猿手動分配和釋放
-
棧(stack):存放局部變量,如函數(shù)的參數(shù)、返回地址、局部變量等,有系統(tǒng)自動分配和釋放
-
棧增長方向:高地址->低地址
-
ESP:棧指針寄存器,指向棧頂?shù)牡偷刂?/p>
-
EBP:基址指針寄存器,指向棧底的高地址
-
EIP:指令指針,存儲即將執(zhí)行的程序指令的地址
IA-32指令編碼
即二進(jìn)制編碼,匯編
Mode R/M:操作數(shù)類型。R:寄存器;M:內(nèi)存單元
SIB:當(dāng)出現(xiàn)基址加變址尋址或者基址尋址時要用到 Scale Index Base
立即數(shù):用于操作數(shù)為常量值的情況
A
d
d
r
e
s
s
=
R
e
g
[
B
a
s
e
]
+
R
e
g
[
i
n
d
e
x
]
?
2
S
c
a
l
e
+
D
i
s
p
l
a
c
e
m
e
n
t
Address=Reg[Base]+Reg[index]*2^{Scale}+Displacement
Address=Reg[Base]+Reg[index]?2Scale+Displacement
舉例
MOV EAX [ESI+ ECX*4 + 4]
Scale: 4( 2 S c a l e 2^{Scale} 2Scale一般是1,2,4,8)
Index: ECX
Base: ESI
偏移量:4
MOVS(MOVSB/MOVSW/MOVSD): 用于實現(xiàn)字符串或內(nèi)存的復(fù)制
SCAS: 用AL/AX/EAX減去[EDI],更新EFLAGS,并對EDI自增/自減
STOS: 將AL/AX/EAX的值寫入EDI指向的內(nèi)存
CMP: 算數(shù)比較
-
比較兩個操作數(shù)(通過相減), 并設(shè)置EFLAGS中的適當(dāng)標(biāo)識位
-
常與條件跳轉(zhuǎn)Jcc指令配合使用, 跳轉(zhuǎn)依據(jù)即CMP運(yùn)算結(jié)果
TEST: 邏輯比較
- 比較兩個操作數(shù)(通過邏輯AND運(yùn)算), 并設(shè)置EFLAGS中的適當(dāng)標(biāo)識位
JMP: 無條件跳轉(zhuǎn)到目標(biāo)指令地址(可用相對地址或絕對地址)
函數(shù)調(diào)用慣例
調(diào)用方式 | 參數(shù)傳遞 | 棧清理 | 常用場景 |
---|---|---|---|
cdecl |
從右到左壓棧,返回值由EAX 返回 |
調(diào)用者 | C語言 |
stdcall |
從右到左壓棧 | 函數(shù)自身 | Win32 API |
fastcall |
左邊兩個參數(shù)分別放在ECX 和EDX 寄存器,其余的參數(shù)從右到左壓棧 |
函數(shù)自身 |
在調(diào)用一個函數(shù)時,系統(tǒng)會為這個函數(shù)分配一個棧幀,棧幀空間為該函數(shù)所獨(dú)有。
函數(shù)調(diào)用一個函數(shù)的大致過程
- 函數(shù)參數(shù)從右到左入棧
- 返回地址入棧,esp向下移動,為返回地址騰出空間
- 上一函數(shù)ebp入棧,新的ebp被設(shè)置為當(dāng)前棧指針esp的地址,以成為新棧幀的基址指針
函數(shù)執(zhí)行過程中的棧操作:
- 在函數(shù)執(zhí)行過程中,根據(jù)函數(shù)的需求,esp 的值可能會不斷變化,以進(jìn)行棧上的數(shù)據(jù)操作。
- 通過減小 esp 的值,為局部變量和臨時數(shù)據(jù)分配空間。
- 通過增加 esp 的值,釋放先前分配的空間。
- 通過將數(shù)據(jù)壓入和彈出棧來進(jìn)行參數(shù)傳遞和函數(shù)調(diào)用。
函數(shù)返回時:
-
彈出臨時變量
-
當(dāng)函數(shù)執(zhí)行完畢并準(zhǔn)備返回時,處理器會恢復(fù)舊的ebp值,將其存入到ebp寄存器的值,以恢復(fù)到調(diào)用者的棧幀。
-
返回地址從棧中彈出,存入到
eip
寄存器中,將控制權(quán)返回給調(diào)用者。 -
最后,ebp 和 esp 的值恢復(fù)到調(diào)用者棧幀的位置,以指向調(diào)用者棧的正確位置。
注意
調(diào)用函數(shù)的參數(shù)入棧后是通過ADD ESP回收棧的
System V amd64優(yōu)化
前6個參數(shù)通過RDI, RSI, RDX, RCX, R8, R9傳遞
Red-Zone優(yōu)化
rsp指針向下(低地址)的128字節(jié)??臻g可保留為不被信號或中斷處理程序更改, 從而作為函數(shù)的臨時數(shù)據(jù)空間, 稱為red zone
中斷指令
中斷: 通常指由I/O設(shè)備觸發(fā)的異步事件
異常(exception): CPU在執(zhí)行指令時, 檢測到一個或多個預(yù)定義條件時產(chǎn)生的同步事件
- 故障 (fault): 可修正的異常。故障處理后執(zhí)行產(chǎn)生故障的指令
- 陷入 (trap): 調(diào)用特定指令(如SYSENTER)時產(chǎn)生的異常。陷入處理后執(zhí)行產(chǎn)生陷入的指令的下一條指令
中斷和異常的處理
中斷與一個索引值相關(guān), 該索引值是一個函數(shù)指針數(shù)組(中斷向量表IVT/中斷描述符表IDT)的索引, 當(dāng)中斷發(fā)生時,CPU執(zhí)行對應(yīng)索引處的函數(shù), 然后恢復(fù)中斷發(fā)生前的執(zhí)行
控制流圖
節(jié)點(diǎn):由一系列匯編指令組成的基本塊
- 一個基本塊由一系列順序執(zhí)行的匯編代碼組成, 其間沒有跳轉(zhuǎn)指令,也沒有其他外部跳轉(zhuǎn)指令以其間為跳轉(zhuǎn)目標(biāo)
有向邊:連接各基本塊
- 從基本塊b1到基本塊b2的有向邊的含義是:在執(zhí)行完b1后,有可能開始執(zhí)行b2從
一個基本塊可以發(fā)出多條有向邊
- 例如, 當(dāng)該基本塊的最后一條指令為條件跳轉(zhuǎn)指令時
ELF
可重定位文件(.o)
其代碼和數(shù)據(jù)與其他對象文件進(jìn)行鏈接, 以構(gòu)造.so文件或可執(zhí)行文件
共享對象文件(.so)
鏈接器(ld)可將其與其他.o和.so文件共同創(chuàng)建新的對象文件;
動態(tài)鏈接器可以將多個.so與可執(zhí)行文件結(jié)合以創(chuàng)建進(jìn)程映像
對象文件的試圖
由于對象文件參與程序的鏈接和執(zhí)行,因此ELF格式提供兩個并發(fā)的視圖
ELF頭
節(jié)頭表(Section header table)
-
用于鏈接視圖
-
將文件的主體看作一系列的節(jié)(section)
程序頭表(Program header table)
-
用于執(zhí)行視圖
-
將文件的主體看作一系列的段(segment)
代碼混淆
混淆(Obfuscation)
-
通過重構(gòu)增加代碼逆向分析難度的技術(shù)
-
混淆是一種程序變換
- 存在不同的混淆/解混淆模型
- 輸入程序與輸出程序應(yīng)在語義上等價
- 可以在不同層次進(jìn)行:源代碼級,匯編/二進(jìn)制級,中間代碼/字節(jié)碼級
理論上的安全性
完美混淆器: 一個概率算法 O O O, 該算法滿足以下三個條件
-
功能性:對于每個 P P P,字符串 O ( P ) O(P) O(P)是一個功能與 P P P相同的程序
-
多項式減速:相較 P P P而言,程序 O ( P ) O(P) O(P)的時間和空間開銷,最差情況下呈多項式級放大
-
虛擬黑盒:任何擁有對 O ( P ) O(P) O(P)文本的訪問權(quán)限的概率多項式時間算法(攻擊者),都無法比一個對 P P P擁有oracle訪問權(quán)限的概率多項式時間算法推斷出更多的東西
- 算法(攻擊者)對程序擁有oracle訪問權(quán)限: 指算法(攻擊者)可以將程序當(dāng)作黑盒使用, 對任意輸入x, 可在多項式時間內(nèi)獲得輸出P(x)
- 白盒攻擊上下文(WBAC): 攻擊者可以在一個他能夠完全控制的環(huán)境下執(zhí)行應(yīng)用程序
-
滿足虛擬黑盒屬性的完美混淆器不存在
-
只需要使逆向分析成本大于可能收益
代碼混淆應(yīng)用場景
-
惡意軟件
通過代碼混淆繞過殺毒軟件和逆向工程師的審查
-
軟件防篡改和保護(hù)代碼等知識產(chǎn)權(quán)
數(shù)據(jù)混淆:常量展開
假定在輸入程序中用到了一個常量,混淆器可將該常量替換為某個計算過程,這個計算過程的結(jié)果是該常量
push 0F9CBE47AH
ADD DWORD PTR [ESP], 6341B86H
#等價于
PUSH 0H
數(shù)據(jù)混淆: 數(shù)據(jù)編碼方案
設(shè)定一個編碼函數(shù)y = f(x)
-
混淆時,將代碼中的數(shù)據(jù)x0混淆為f(x0)
-
解混淆時(運(yùn)行時處理到f(x0)),將代碼中的f(x0)恢復(fù)為x0
對編碼函數(shù)y = f(x)的選擇要求
-
從任意y0 = f(x0)難以推斷出x0
-
或從任意的(x0, y0)難以推斷出編碼函數(shù)f
提高混淆強(qiáng)度
- 多項式編碼
- 剩余數(shù)編碼
- 同態(tài)
- 一般的編碼運(yùn)行時還需要進(jìn)行動態(tài)解碼
- 希望能夠直接在編碼后的代碼上直接實施運(yùn)算
對于編碼后的變量定義一個等價運(yùn)算, 使得對編碼后變量的運(yùn)算結(jié)果等于編碼前變量運(yùn)算結(jié)果的編碼
同態(tài)
對于編碼后的變量定義一個等價運(yùn)算,使得編碼后變量的運(yùn)算結(jié)果等于編碼前變量運(yùn)算結(jié)果的編碼
-
在抽象代數(shù)中,兩個群 G G G和 H H H分別支持 + g +_g +g?和 + h +_h +h?運(yùn)算,同態(tài)是 G G G到 H H H之間的映射 f f f使得 f ( x + g y ) = f ( x ) + h f ( y ) f(x+_gy)=f(x)+_hf(y) f(x+g?y)=f(x)+h?f(y)
-
同態(tài)可以泛化到任何代數(shù)結(jié)構(gòu)
-
全同態(tài):對運(yùn)算沒有任何限制的映射
混淆是全同態(tài)的一個應(yīng)用
- 未編碼域->源代數(shù) G G G
- 編碼域->目標(biāo)代數(shù) H H H
構(gòu)造變換,將一條或多條相鄰指令映射為具有相同語義的更復(fù)雜的指令序列
高級語言上的語義等價示例
運(yùn)算 | 等價運(yùn)算 |
---|---|
-x | ~x+1 |
rotate left(x,y) | (x<<y)|(x>>(bits(x)-y)) |
x-1 | ~(-x) |
x+1 | -(~x) |
控制流混淆: 二進(jìn)制靜態(tài)分析對控制流的假定
-
CALL指令只用于函數(shù)調(diào)用, 且調(diào)用目標(biāo)即函數(shù)的起始地址
-
絕大多數(shù)函數(shù)調(diào)用會返回, 且返回到CALL指令的后一條指令的位置; RET和RETN代表函數(shù)邊界
-
遇到條件跳轉(zhuǎn)時, 假定: 分支兩側(cè)均可能被執(zhí)行; 分支兩側(cè)均為代碼而非數(shù)據(jù)
-
容易確定間接跳轉(zhuǎn)的目標(biāo)地址
-
只有switch結(jié)構(gòu)能夠生成間接跳轉(zhuǎn); 只有對函數(shù)指針的調(diào)用能夠生成間接調(diào)用
-
所有控制轉(zhuǎn)移目標(biāo)地址都是代碼而非數(shù)據(jù)
-
異常以一種可預(yù)測的方式使用
控制流混淆通過打破這些假定中的一些假定來增大軟件逆向的難度
組合使用函數(shù)內(nèi)聯(lián)和外聯(lián)
函數(shù)內(nèi)聯(lián):將子函數(shù)代碼合并到調(diào)用該函數(shù)的調(diào)用者代碼的每個調(diào)用點(diǎn)
- 打破目的地址
函數(shù)外聯(lián):將代碼的一部分提取出來構(gòu)成單獨(dú)的函數(shù)
通過跳轉(zhuǎn)破壞局部性
向基本塊中加入無條件跳轉(zhuǎn),破壞局部性的模式
不透明謂詞
一種特殊的條件表達(dá)式,該條件表達(dá)式的值僅在編譯時或混淆時已知(對于加混淆者來說容易判斷)
- P T P^T PT:取值為True的不透明謂詞
- P F P^F PF:取值為False的不透明謂詞
- P ? P^? P?:取值為True或者False的不透明謂詞
將不透明謂詞作為分支條件, 向CFG中加入額外的偽分支
def opaque_predicate(x):
result = ((x ^ 0x5A) & 0xFF) + 0xC3
return result % 2 == 0
def main():
secret_value = 42
if opaque_predicate(secret_value):
print("Access granted!")
print("I will now reveal the secret to you!")
# ...
else:
# 無關(guān)緊要的代碼塊
print("Access denied!")
# ...
main()
控制流混淆:插入無效代碼
在兩段有效代碼之間插入一些無效代碼
- 對數(shù)據(jù)混淆->死代碼的插入
- 控制流混淆->插入到不會執(zhí)行的程序分支->垃圾代碼插入
死代碼消除: 與死代碼插入相對應(yīng)的編譯優(yōu)化技術(shù)
垃圾代碼通常被引入到特定的程序分支上, 與跳轉(zhuǎn)指令共同作用,看似引入了一個新的分支
JMP <目標(biāo)地址> <垃圾代碼>
目標(biāo)地址: 有效代碼
更隱蔽的插入方法: 與數(shù)據(jù)混淆相結(jié)合, 通過引入寄存器操作,
將無條件跳轉(zhuǎn)偽裝為條件跳轉(zhuǎn)
PUSH EAX
XOR EAX, EAX
JZ <目標(biāo)地址>
<垃圾代碼>
目標(biāo)地址: POP EAX
無效的條件跳轉(zhuǎn)
JZ <目標(biāo)地址1>
JMP <目標(biāo)地址2>
...
目標(biāo)地址1: NOP ;或等價于NOP的語句序列
目標(biāo)地址2: 有效代碼
控制流混淆: 基于處理器的控制流間接化
選擇間接跳轉(zhuǎn)點(diǎn):在代碼中選擇一些關(guān)鍵的跳轉(zhuǎn)點(diǎn),如函數(shù)調(diào)用、條件分支等。這些跳轉(zhuǎn)點(diǎn)是程序的關(guān)鍵控制流決策點(diǎn)。
替換為間接跳轉(zhuǎn):將直接跳轉(zhuǎn)指令替換為間接跳轉(zhuǎn)指令,如函數(shù)指針調(diào)用、虛函數(shù)調(diào)用等。這些間接跳轉(zhuǎn)指令將控制流的決策推遲到運(yùn)行時。
控制流目標(biāo)混淆:通過修改間接跳轉(zhuǎn)的目標(biāo)地址或使用一些額外的指令進(jìn)行控制流變換,使得控制流路徑變得復(fù)雜和難以預(yù)測。這可以使用加密、動態(tài)計算、代碼生成等技術(shù)來實現(xiàn)。
解密或計算目標(biāo)地址:在運(yùn)行時,解密或計算間接跳轉(zhuǎn)的目標(biāo)地址,以便執(zhí)行正確的控制流路徑。這可以通過一些密鑰或算法來實現(xiàn)。
#include <stdio.h>
void secret_function() {
printf("Access granted!\n");
// 關(guān)鍵代碼塊
// ...
}
void public_function() {
printf("Access denied!\n");
// 無關(guān)緊要的代碼塊
// ...
}
void encrypt_function_pointers(void (*func_ptr)()) {
unsigned char* ptr = (unsigned char*)&func_ptr;
for (int i = 0; i < sizeof(func_ptr); i++) {
ptr[i] ^= 0xAB; // 使用異或操作進(jìn)行簡單加密
}
}
void* calculate_target_address(int input) {
if (input == 1) {
return (void*)secret_function;
} else {
return (void*)public_function;
}
}
int main() {
int input;
void (*func_ptr)();//函數(shù)指針
printf("Enter 1 for access granted, or any other number for access denied: ");
scanf("%d", &input);
void* target_address = calculate_target_address(input);
encrypt_function_pointers((void (*)())target_address);
func_ptr = (void (*)())target_address;
// 通過間接調(diào)用執(zhí)行關(guān)鍵代碼塊或無關(guān)緊要的代碼塊
func_ptr();
return 0;
}
用動態(tài)計算的分支地址或?qū)MP和CALL指令的模擬實現(xiàn)混淆
控制流圖扁平化
控制流是代碼執(zhí)行的順序
控制流扁平化是一個代碼級別的混淆手段
將復(fù)雜的控制分支結(jié)構(gòu)由一個單一的分發(fā)器(dispatcher)結(jié)構(gòu)替代的代碼混淆方法
-
簡單分發(fā)器: switch
-
復(fù)雜分發(fā)器: 可使用單向函數(shù)和偽隨機(jī)生成器, 提供對程序靜態(tài)分析的密碼學(xué)抵抗機(jī)制
-
每個基本塊負(fù)責(zé)更新分發(fā)器的上下文, 使得分發(fā)器可以連接到下一個基本塊
-
基本塊之間的關(guān)系被隱藏在對分發(fā)器上下文的控制操作之中
一般扁平化需要經(jīng)過的幾個步驟:將函數(shù)體拆分為多個語句塊、構(gòu)建流程圖;將所有拆分的語句塊用switch分支去處理;用一個狀態(tài)變量來邏輯順序控制。
軟件漏洞利用與防護(hù)
棧溢出
棧溢出是指向向棧中寫入了超出限定長度的數(shù)據(jù),溢出的數(shù)據(jù)會覆蓋棧中其它數(shù)據(jù),從而影響程序的運(yùn)行。
void function(int a, int b) {
char buffer[12];
gets(buffer);
long* ret = (long *) ((long)buffer+28);
*ret = *ret + 7;
return;
}
void main() {
int x = 0;
function(1,2);
//原始的return address
x = 1;
//新的return address
printf("%d\n",x);
}
改正: 使用fgets(包含緩沖區(qū)大小作為參數(shù))
gets(buf)
->fgets(buf,size,stdin)
從stdin中最多讀size-1個字符,直到讀到換行,buf將以0
結(jié)束
strcpy
->strncpy( char *dest, const char *src, std::size_t count );
strncpy(dest, src, sizeof(dest))可能會有截斷NULL錯誤->strncpy(dest, src, sizeof(dest)-1)
strcat
->strncat
sprintf
->snprintf
gets
,fgets
#include <unistd.h>
ssize_t read(int fd, void *buffer, size_t count);
-
fd
:文件描述符,指定要讀取的文件或輸入源。通常是通過調(diào)用open()
函數(shù)獲取的文件描述符。 -
buffer
:用于存儲讀取數(shù)據(jù)的緩沖區(qū)的指針。 -
count
:要讀取的字節(jié)數(shù),即期望讀取的數(shù)據(jù)量。
緩沖區(qū)溢出
指數(shù)據(jù)寫出到為特定數(shù)據(jù)結(jié)構(gòu)開辟的內(nèi)存空間的邊界之外通??赡馨l(fā)生在緩沖區(qū)邊界被忽略或沒有被檢查時
緩沖區(qū)溢出可被利用于修改
-
棧上的: 返回指令指針, 函數(shù)指針, 局部變量
-
堆數(shù)據(jù)結(jié)構(gòu)
void function(int a, int b) {
char buffer[12];
gets(buffer);
return;
}
加入輸入的是fffffffffffffffffffffffffffffffffffffffffffffffff
如果輸入很大, 則gets(buffer)將寫出buffer的邊界,且返回指令指針被覆寫
覆寫為“ffff”(字符串對應(yīng)的數(shù)值不是合法代碼地址) —— Segment fault
整數(shù)溢出
二進(jìn)制補(bǔ)碼(10000000表示-128; 11111111表示-1)
負(fù)索引漏洞
是指在使用數(shù)組或緩沖區(qū)時,通過使用負(fù)數(shù)作為索引來訪問數(shù)組或緩沖區(qū)中的數(shù)據(jù),從而導(dǎo)致越界訪問或不正確的內(nèi)存訪問。
截斷錯誤(Truncation Error)是指在數(shù)據(jù)轉(zhuǎn)換或處理過程中,由于數(shù)據(jù)的截斷或縮小導(dǎo)致精度或數(shù)據(jù)損失的問題。
堆溢出
堆管理器
堆管理器位于用戶程序和內(nèi)核中間,主要負(fù)責(zé):
- 哪些內(nèi)存區(qū)域已被開辟,它們的大小
- 哪些內(nèi)存區(qū)域可以被開辟
arena
內(nèi)存分配區(qū),可以理解為堆管理器所持有的內(nèi)存池 操作系統(tǒng)–>堆管理器–>用戶物理內(nèi)存–>arena -->可用內(nèi)存 堆管理器與用戶的內(nèi)存交易發(fā)生于arena中,可以理解為堆管理器向操作系統(tǒng)批發(fā)來的有冗余的內(nèi)存庫存
chunk
用戶申請內(nèi)存的基本單位,也是堆管理器管理內(nèi)存的基本單位malloc()
返回的指針指向一個chunk的數(shù)據(jù)區(qū)域
bin
管理arena中空閑chunk的結(jié)構(gòu),以數(shù)組的形式存在,數(shù)組元素為相應(yīng)大小的chunk鏈表的鏈表頭,存在與arena的malloc state中
請求堆
響應(yīng)用戶的申請內(nèi)存請求,向操作系統(tǒng)申請內(nèi)存,然后返回給用戶程序。為了保持內(nèi)存管理的高效性,內(nèi)核一般會預(yù)先分配很大的一塊連續(xù)的內(nèi)存。
釋放堆
管理用戶釋放的內(nèi)存。用戶釋放的內(nèi)存并不是直接返還給操作系統(tǒng),而是由堆管理器進(jìn)行管理。這些釋放的內(nèi)存可以用來響應(yīng)用戶新申請的內(nèi)存的請求。
堆溢出是指程序向某堆塊(chunk)中寫入的字節(jié)數(shù)超過了堆塊本身可使用的字節(jié)數(shù),因而導(dǎo)致了數(shù)據(jù)溢出,并覆蓋到物理地址相鄰的高地址的下一個堆塊。這里之所以是可使用而不是用戶申請的字節(jié)數(shù),是因為堆管理器會對用戶所申請的字節(jié)數(shù)進(jìn)行調(diào)整,這也導(dǎo)致可利用的字節(jié)數(shù)大于等于用戶申請的字節(jié)數(shù)。
實際中的內(nèi)存中堆的樣子
Heap:
+---------------------------------+
| [Heap Metadata] |
+---------------------------------+
| [Data] |
+---------------------------------+
Metadata包括了前塊大小, 本塊大小,previous指針, next指針
利用堆溢出的策略是
覆蓋與其物理相鄰的下一個 chunk的內(nèi)容。
-
prev_size
-
size,主要有三個比特位,以及該堆塊真正的大小。
- NON_MAIN_ARENA
- IS_MAPPED
- PREV_INUSE
- the True chunk size
-
chunk content,從而改變程序固有的執(zhí)行流。
利用堆中的機(jī)制(如 unlink 等 )來實現(xiàn)任意地址寫入( Write-Anything-Anywhere)或控制堆塊中的內(nèi)容等效果,從而來控制程序的執(zhí)行流。
格式化字符串
攻擊者能夠讀取堆棧上的數(shù)據(jù)并進(jìn)行內(nèi)存泄漏,是因為格式字符串漏洞使得
printf()
等函數(shù)對于參數(shù)數(shù)量的檢查不嚴(yán)謹(jǐn),從而導(dǎo)致讀取未指定的參數(shù)并泄漏內(nèi)存中的數(shù)據(jù)。當(dāng)使用格式化字符串函數(shù)(如
printf()
)時,程序會根據(jù)格式化字符串中的格式轉(zhuǎn)換說明符來讀取參數(shù)并進(jìn)行格式化輸出。然而,如果格式化字符串中的格式轉(zhuǎn)換說明符的數(shù)量多于提供的參數(shù)數(shù)量,函數(shù)將會從堆棧上繼續(xù)彈出參數(shù),尋找更多的參數(shù)。
格式化字符串中的%n
能夠?qū)⒌健?n”位置為止已經(jīng)由printf打印出的字節(jié)數(shù)寫到一個我們選定的變量中
int i;
printf ("foobar%n\n", (int *) &i);
printf ("i = %d\n", i);
// i 的值最終為6
攻擊者可以
- 查看/修改內(nèi)存的任意部分
- 執(zhí)行任意代碼, 只需要把代碼也放入buf
通過提供一個特殊的格式化字符串, 可以規(guī)避“%400s”的限制:
%497d\x3c\xd3\xff\xbf<nops><shellcode>
。創(chuàng)建一個497字符長的字符串,加上錯誤字符串(“ERR Wrong command: ”),超過了outbuf的長度4字節(jié)。雖然“user”字符串只允許 400字節(jié),可以通過濫用格式化字符串參數(shù)擴(kuò)展其長度。因為第二個sprintf不檢查長度, 它可以用來突破outbuf的長度界限。此時我們寫入了一個返回地址 (0xbfffd33c), 并可以以之前棧溢出的利用方式進(jìn)行攻擊。
防止格式化字符串漏洞
即限制攻擊者控制格式化字符串的能力
- 如果有可能,硬編碼字符串;且不用包含“%*”的格式化字符串
- 如果必須要用格式化字符串,至少不要用“printf(arg)”
- 不要使用 %n
- 小心其他的引用: %s 和 sprintf 能夠被用于構(gòu)造棧內(nèi)容披露攻擊
- 編譯器支持printf參數(shù)與格式化字符串的匹配檢查
高級防御與攻擊
Stack canary
是棧溢出的檢測機(jī)制, 又稱“棧cookies”
原理:將一個dummy值(或隨機(jī)值)寫到棧上的返回地址之前,并在函數(shù)返回時檢查該值如果不小心構(gòu)造的棧溢出(假定是順序棧粉碎)會覆寫該“canary”單元, 該行為將被探測到
攻破StackGuard的基本方法
對canary單元, 用正確的值覆寫
- 如果canary所使用的隨機(jī)值范圍很小, 則枚舉每種可能性
- 或先實施一個memory disclosure(內(nèi)存泄露攻擊)攻擊, 獲知canary的值
無法抵御disclosure攻擊是StackGuard的最大局限性
有時不需要覆寫返回地址, 可以溢出:
- 安全敏感的局部變量
- 堆數(shù)據(jù)
- 全局?jǐn)?shù)據(jù)
- 全局?jǐn)?shù)據(jù)溢出: 攻擊位于全局?jǐn)?shù)據(jù)區(qū)的緩沖區(qū)
如何防御?
- 讓函數(shù)指針位于其他類型數(shù)據(jù)的下方(更低地址)
- 在全局?jǐn)?shù)據(jù)區(qū)和其他管理表結(jié)構(gòu)之間使用守衛(wèi)頁
劫持函數(shù)指針
void foo () {...}
void bar () {...}
int main() {
char buf [16];
void (*f) () = &foo;
gets(buf);
f();
}
假定我們沒有機(jī)會溢出返回地址
可溢出緩沖區(qū), 使得函數(shù)指針被修改為 bar 的地址, 然后函數(shù)調(diào)用將調(diào)用 bar 而非 foo
劫持函數(shù)指針的其他方法
- 使用堆溢出,對堆上的函數(shù)指針進(jìn)行劫持
- 劫持全局函數(shù)指針
- 劫持全局偏移量表(GOT)中的函數(shù)指針, 被動態(tài)鏈接函數(shù)所使用
守衛(wèi)頁(Guard Pages)
也是一種運(yùn)行時檢測方法, 可以看作StackGuard的擴(kuò)展
在一個進(jìn)程地址空間中關(guān)鍵內(nèi)存區(qū)域之間放置守衛(wèi)頁 (像一些gaps)
- 需借助CPU內(nèi)存管理單元(MMU)的管理功能將它們標(biāo)記為非法地址
- 任何對其的訪問嘗試都導(dǎo)致進(jìn)程被終止
效果: 能失效緩沖區(qū)溢出攻擊, 特別是對全局?jǐn)?shù)據(jù)區(qū)的溢出攻擊
甚至可以在棧幀之間、或者堆緩沖區(qū)之間放置守衛(wèi)頁
- 可以提供更進(jìn)一步的保護(hù), 防止棧溢出和堆溢出攻擊
- 會導(dǎo)致執(zhí)行時間和內(nèi)存的很大開銷, 因為要支持大量頁映射
DEP
EP又稱作Nx-bit (non executable bit), W⊕X能夠阻止代碼注入攻擊
DEP基本原理是將數(shù)據(jù)所在的頁面標(biāo)識設(shè)置為不可執(zhí)行,當(dāng)程序溢出成功轉(zhuǎn)入shellcode時,程序會嘗試在數(shù)據(jù)基本頁面上執(zhí)行指令,此時CPU會拋出異常,而不是執(zhí)行惡意指令
很多緩沖區(qū)溢出攻擊涉及將機(jī)器碼復(fù)制到目標(biāo)緩沖區(qū), 然后將執(zhí)行轉(zhuǎn)移到這些緩沖區(qū)
一種防御方法就是阻止在棧/堆/全局?jǐn)?shù)據(jù)區(qū)中執(zhí)行代碼, 并假定可執(zhí)行代碼只能出現(xiàn)在進(jìn)程地址空間中除這些位置外的其他位置
-
需要CPU內(nèi)存管理單元(MMU)提供支持, 將虛擬內(nèi)存的對應(yīng)頁標(biāo)記為不可執(zhí)行
-
對于每一個被映射的虛擬內(nèi)存頁, 都有這樣額外的1個no-executebit, 置位時, 表示該頁的數(shù)據(jù)不能作為代碼執(zhí)行, 一旦程序控制流到達(dá)該頁, CPU會產(chǎn)生陷入
AMD:No-Execute Page-Protection (NX)
Intel:Execute Disable Bit (XD)
Return-to-libc
Return-to-libc: 用危險的庫函數(shù)的地址替換返回地址
“Return to libc”(返回到libc)是一種代碼重用攻擊,利用漏洞來繞過安全措施并執(zhí)行任意代碼。在這種攻擊中,攻擊者利用緩沖區(qū)溢出或類似的漏洞來覆蓋棧上函數(shù)調(diào)用的返回地址。攻擊者不直接跳轉(zhuǎn)到惡意代碼,而是修改返回地址,使其指向一個合法函數(shù),通常是C標(biāo)準(zhǔn)庫(libc)中的函數(shù)
-
攻擊1: 更改f的值, 改為一個libc中的系統(tǒng)函數(shù), 將參數(shù)放在棧上
-
攻擊2: 鏈接兩個對libc函數(shù)的調(diào)用
具體地
攻擊者用一個溢出填充buffer:
-
更改棧上保存的ebp為一個合適地址
-
更改返回指令指針為一個欲執(zhí)行的庫函數(shù)的地址
-
寫一個占位符值(庫函數(shù)會認(rèn)為其是返回地址,如果想利用它調(diào)用第二個庫函數(shù), 應(yīng)寫入第二個庫函數(shù)的地址)
-
寫一個或多個要傳遞給此庫函數(shù)的參數(shù)
當(dāng)被攻擊的函數(shù)返回時, 恢復(fù)(更改過的)ebp, 然后pop更改后的返回地址到eip, 從而開始執(zhí)行庫函數(shù)代碼
因為庫函數(shù)相信它已被調(diào)用, 故會將棧頂當(dāng)前值(占位符)作為它自己棧幀的返回指令指針, 之上是參數(shù)
最終會在占位符位置的下方創(chuàng)建起一個新的棧幀 (對應(yīng)于庫函數(shù)的執(zhí)行)
根據(jù)庫函數(shù)參數(shù)類型以及庫函數(shù)對參數(shù)的解釋方式, 攻擊者可能需要準(zhǔn)確地知道參數(shù)地址以做溢出寫
在很多攻擊中, 代碼重用攻擊用來作為禁用DEP的第一步
-
目標(biāo)是允許對棧內(nèi)存進(jìn)行執(zhí)行
-
有一個系統(tǒng)調(diào)用可以更改棧的讀/寫/執(zhí)行屬性
? int mprotect(void *addr, size_t len, int prot);
-
設(shè)置對于起始于addr的內(nèi)存區(qū)域的保護(hù)
-
調(diào)用此系統(tǒng)調(diào)用, 允許在棧上的“執(zhí)行”屬性, 然后開始執(zhí)行被注入的代碼
ROP及防御
ROP的全稱為Return-oriented programming(返回導(dǎo)向編程),這是一種高級的內(nèi)存攻擊技術(shù)可以用來繞過現(xiàn)代操作系統(tǒng)的各種通用防御(比如內(nèi)存不可執(zhí)行和代碼簽名等)
主要是用在棧溢出
在棧堆不可執(zhí)行開啟后,棧溢出不能簡單的向棧中寫入shellcode利用jmp rsp運(yùn)行,因此開發(fā)出的一種棧溢出利用手段,主要的意思就是修改ret的位置,運(yùn)行到一些程序內(nèi)部的代碼片段(gadget)的位置,然后通過這些去控制程序的??臻g,來控制:寄存器,參數(shù)傳遞,函數(shù)調(diào)用,由于gadget的使用要是最后ret結(jié)尾,由此成為面向ret的編程。
-
執(zhí)行任意行為, 不需要注入代碼
-
聯(lián)合現(xiàn)有的代碼片段(gadgets)
-
一系列圖靈完全的gadgets, 及一種串聯(lián)這些gadgets的方法,允許任意復(fù)雜度的計算
-
現(xiàn)有的展示已能針對小程序(如16KB)找到圖靈完全的gadgets集合
任意充分大的程序代碼基 ? 任意攻擊者計算和行為,無需代碼注入
gadgets用法介紹
保存棧數(shù)據(jù)到寄存器。彈出棧頂元素到寄存器中,然后跳轉(zhuǎn)到新的棧頂?shù)刂?/p>
pop eax;ret;
保存內(nèi)存數(shù)據(jù)到寄存器
mov ecx,[ecx];ret;
保存寄存器到內(nèi)存
mov [ecx],ecx;ret
正常機(jī)器指令序列
- 指令指針(%eip)決定哪一條指令被獲取和執(zhí)行
- 一旦CPU執(zhí)行了指令, 就會自動改變%eip的值到下一條指令
- 控制流隨著%eip的更改而演進(jìn)
ROP執(zhí)行
棧頂指針%esp決定哪個指令序列被獲取和執(zhí)行(作為程序計數(shù)器PC)
CPU不自動自增%esp, 而是由每個指令序列最后的“ret”指令進(jìn)行自增
No-op指令
通常NOP用于對齊指令的地址。
NOP指令的特性:
- 因為NOP是X86指令中最短的,只有1byte
- 處理器執(zhí)行NOP指令時,不進(jìn)行任何的操作,不會影響系統(tǒng)的狀態(tài)
NOP sled 如果我們想跳轉(zhuǎn)到某條具體的指令,但是卻不知道指令的地址,那么可以在目標(biāo)指令前加入足夠數(shù)量的NOP指令。跳轉(zhuǎn)的地址設(shè)置到NOP的范圍內(nèi),那么執(zhí)行完畢跳轉(zhuǎn)之后,就會持續(xù)的執(zhí)行NOP指令,直到最終跳轉(zhuǎn)到期望的目標(biāo)指令
NOP也可以進(jìn)行代碼替換,比如想非法的避開軟件的寫保護(hù)操作,原來的軟件代碼是if(genuineCopy),會進(jìn)行自動的拷貝,我們可以通過將genuineCopy這樣的檢查條件替換為NOP,這樣就不會在if中進(jìn)行任何有效地操作,從而非法的改動代碼。
-
No-op指令不做任何事情, 但會增大%eip
-
面向返回的等價形式
- 棧單元直接指向返回指令
- 效果是增大%esp
nop sled
立即數(shù)
- 指令可以編碼立即數(shù)
- 面向返回的等價模式
- 立即數(shù)存在棧上
- 用gadget中的pop指令pop到寄存器中使用
控制流
原始程序
- (有條件地)設(shè)置%eip為新的值
面向返回的等價形式
- (有條件地)設(shè)置%esp為新的值
ROP能做的事
-
條件分支(Conditional branching)
-
可以任意修改內(nèi)存
針對ROP的保護(hù)
控制流完整性(Control-flow integrity, CFI)
-
預(yù)先決定被攻擊程序的控制流圖
-
向該程序中插入檢測, 使得在程序運(yùn)行時發(fā)生非法控制流跳轉(zhuǎn)時,終止程序
- 通過編譯器或二進(jìn)制重寫進(jìn)行插入
ROP運(yùn)行時緩解:隨機(jī)化
- 緩沖區(qū)的起始地址
- 庫函數(shù)的地址
實現(xiàn)隨機(jī)化
-
對棧的位置進(jìn)行隨機(jī)化, 對堆上的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)進(jìn)行隨機(jī)化,對庫函數(shù)的位置進(jìn)行隨機(jī)化
-
隨機(jī)地填充棧幀
-
在編譯時, 隨機(jī)化代碼生成, 以抵御ROP
實現(xiàn)隨機(jī)化的時機(jī)
編譯時
鏈接時
運(yùn)行時
地址空間隨機(jī)化的問題或挑戰(zhàn)
- 信息泄露
- 暴力破解秘密值
- 對于長時間運(yùn)行的進(jìn)程,如何“再次隨機(jī)化”
地址隨機(jī)化的的有效性
- 每個被隨機(jī)出的位置的熵值
- 隨機(jī)化的完備性(所有對象都被隨機(jī)化了嗎)
- 信息泄露的避免程度
ASLR
ASLR可以將基數(shù),庫,堆和堆棧放在進(jìn)程地址空間中的任意隨機(jī)位置,這使攻擊程序很難預(yù)測下一條指令的內(nèi)存地址。
對于位置無關(guān)的可執(zhí)行程序(PIE),隨機(jī)化該可執(zhí)行程序的基地址
- 所有庫都是PIE,所以他們的基地址被隨機(jī)化
- 主可執(zhí)行可執(zhí)行程序可能不是PIE,故可能無法被ASLR保護(hù)
- 在內(nèi)存對象之間的相對距離不變
ASLR是一種粗顆粒度的隨機(jī)化形式
- 只有基地址被隨機(jī)化
- 在內(nèi)存對象之間的相對距離不變
ASLR攻擊
- 如果隨機(jī)地址空間很小, 可以進(jìn)行一個窮舉搜索
例如, Linux提供16位的隨機(jī)化強(qiáng)度, 可以在約200秒以內(nèi)被
- 窮舉搜索攻破
ASLR經(jīng)常被memory disclosure攻破
例如, 如果攻擊者可以讀取指向棧的指針值, 他就可以使用該指針值發(fā)現(xiàn)棧在哪里
防御性編程
預(yù)防:
-
使用更安全的編程語言
-
代碼審計
編程時預(yù)防
GCC編譯器內(nèi)建的防御選項
編寫內(nèi)存安全的C/C++代碼
邊界檢查
邊界檢查是一種重要的內(nèi)存安全措施,它可以防止數(shù)組越界訪問和緩沖區(qū)溢出
對于超出邊界的訪問,可以
- 停止訪問
- 忽略訪問,可能會導(dǎo)致截斷的數(shù)據(jù)
自動調(diào)整大小和緩沖區(qū)移動
- 使用動態(tài)內(nèi)存分配:在C中,可以使用
malloc
和realloc
函數(shù)來動態(tài)分配內(nèi)存,并根據(jù)需要調(diào)整大小。在C++中,可以使用new
和delete
運(yùn)算符,或者使用std::vector
和std::string
等容器,它們會自動進(jìn)行內(nèi)存管理和調(diào)整大小。 - 使用緩沖區(qū)移動:當(dāng)需要擴(kuò)展目標(biāo)緩沖區(qū)時,可以將數(shù)據(jù)從舊的緩沖區(qū)移動到更大的緩沖區(qū),以避免溢出??梢允褂煤瘮?shù)庫提供的功能,如
memmove
函數(shù),或者使用C++中的std::move
操作來移動對象。
傳統(tǒng)的C語言解決方案(邊界檢查函數(shù))
strncpy
函數(shù): char *strncpy( char *dest, const char *src, std::size_t count );
strncpy
函數(shù)用于將一個字符串復(fù)制到另一個字符串中,并指定最大復(fù)制的字符數(shù)。
然而,strncpy
存在以下問題:
- 如果源字符串的長度超過目標(biāo)字符串的長度,則目標(biāo)字符串不會以空字符或者NULL結(jié)尾,這可能導(dǎo)致緩沖區(qū)溢出。
- 如果源字符串的長度小于目標(biāo)字符串的長度,則目標(biāo)字符串會以空字符填充多余的部分。
#include <stdio.h>
#include <string.h>
int main() {
const char source[10] = "HelloWorl";
char destination[5];
strncpy(destination, source, sizeof(destination));
destination[sizeof(destination) - 1] = '\0';
printf("Source: %s\n", source);
printf("Destination: %s\n", destination);
printf("%ld",sizeof(destination));
return 0;
}
strncat
函數(shù):char *strncat( char *dest, const char *src,std::size_t count );
strncat
函數(shù)用于將一個字符串追加到另一個字符串的末尾,并指定最大追加的字符數(shù)。
- DST中的結(jié)果字符串總是null結(jié)尾
- 如果SRC包含n個或更多字節(jié),strncat()會寫 n+1個字節(jié)到DST
- 需要NULL字節(jié)
然而,strncat
存在以下問題:
- 如果目標(biāo)字符串的長度不足以容納源字符串和空字符,則可能導(dǎo)致緩沖區(qū)溢出。
sprintf
函數(shù):int sprintf( char* buffer, const char* format, … );
sprintf
函數(shù)用于格式化輸出字符串,并將結(jié)果存儲在目標(biāo)字符串中。
注意:"%10s"設(shè)置的是字段的最小寬度
“%.10s"意思是”<="字節(jié)(注意’.')
注意精度可以用“*
”指定,具體最大長度值可以以參數(shù)形式傳給“*
”
sprintf(dest, ”%.*s”, maxlen, src);
#include <stdio.h>
int main() {
char destination[10];
sprintf(destination, "%s", "This is a very long string that exceeds the size limit of the destination buffer");
printf("Destination: %s\n", destination);
return 0;
}
snprintf
函數(shù):int snprintf( char* buffer, std::size_t buf_size, const char* format, … );
- 將最多n字符寫入到緩沖區(qū)
s
(緩沖區(qū)溢出難) - 如果n>=1,總會在s末尾寫’\0’
- 必須提供格式化字符串,不讓攻擊者控制格式化字符串
- 返回已被處理的元數(shù)據(jù)的長度,如果出錯則返回負(fù)值
#include <stdio.h>
int main() {
char destination[10];
// sprintf(destination, "%s", "This is a very long string that exceeds the size limit of the destination buffer");
snprintf(destination, sizeof(destination), "%s", "This is a very long string that exceeds the size limit of the destination buffer");
printf("Destination: %s\n", destination);
return 0;
}
注意:
模糊測試
模糊測試原理
程序測試
測試: 在測試用例集合上運(yùn)行程序, 并比較實際結(jié)果與預(yù)期結(jié)果的過程
程序驗證
驗證:一種對于程序在“所有可能輸入”上表現(xiàn)的行為的邏輯論證
- 相比程序測試,是更可靠手段
黑盒測試
基于程序的規(guī)范生成測試用例
- 不考慮軟件內(nèi)部實現(xiàn)
不會有針對具體實現(xiàn)的傾向性
- 例如邊界條件(0,負(fù)數(shù),Null)
白盒測試
觀察程序內(nèi)部實現(xiàn),得出更充分的測試集
測試覆蓋率
思想:沒有被覆蓋測試的代碼更可能存在漏洞
- 將程序切成不同元素
- 測試覆蓋率:
被測試集執(zhí)行的元素的個數(shù) 程序中元素的格式 \begin{align*} &\frac{\text{被測試集執(zhí)行的元素的個數(shù)}}{\text{程序中元素的格式}} \end{align*} ?程序中元素的格式被測試集執(zhí)行的元素的個數(shù)??
-
可用于終止測試: 如果100%的程序元素都被測試到
-
可以作為一種度量指標(biāo)(metric):測試覆蓋率為80%的測試集比測試覆蓋率為70%的測試集更好
-
可以用于測試用例生成器: 查找能夠觸發(fā)一些新語句(沒有被當(dāng)前測試用例集覆蓋的語句)執(zhí)行的
不同的覆蓋率指標(biāo)
通?;诳刂屏鲌D(CFG)
-
測試數(shù)據(jù)
- table={3,4,5}; n=3; element=3
大體上, 代碼覆蓋率不能幫助糾正邏輯上被忽略的情況
如果程序存在loop,可能會有無限多的路徑
一個啟發(fā)式的方法
使用能夠覆蓋0, 1, 2次循環(huán)迭代的測試數(shù)據(jù)
- 避免第二次迭代的時候忘記重新初始化數(shù)據(jù)
模糊測試
在很多隨機(jī)的、不正常的
輸入上運(yùn)行程序, 找出程序?qū)@些輸入進(jìn)行響應(yīng)時的錯誤行為(如崩潰、掛起)
能夠找到的錯誤包括: 沒有檢查返回值, 數(shù)據(jù)訪問越界,沒有檢查空指針, …
黑盒fuzzing
給程序隨機(jī)輸入,觀察其是否崩潰
優(yōu)點(diǎn):容易配置
缺點(diǎn):查找低效
基于突變(mutation)的fuzzing
用戶提供一個良構(gòu)的輸入
Fuzzing: 對于這個輸入, 生成隨機(jī)的更改
但可能由于初始輸入的選擇而具有強(qiáng)偏向性
仍面臨黑盒測試的共性問題
-
低路徑覆蓋率(可能對同一路徑多次重復(fù)運(yùn)行)
-
對于特定路徑, 可能很難生成輸入(如校驗和, 哈希值, 限制條件等)
基于生成的Fuzzing
要求用戶指定一個格式或協(xié)議規(guī)范, 以生成輸入等價于寫一個生成良構(gòu)輸入的生成器
更精確,但代價更大
優(yōu)點(diǎn): 更完全的搜索
-
生成的值更特定于程序操作
-
能夠考慮到輸入之間的依賴關(guān)系
缺點(diǎn): 需要更多工作
-
獲得規(guī)范
-
寫專門的輸入生成器
-
對于每一個程序都需要這樣做
基于覆蓋(Coverage)的Fuzzing(灰盒fuzzing)
灰盒Fuzzing是一種結(jié)合了黑盒和白盒測試思想的Fuzzing技術(shù),它在進(jìn)行模糊測試時部分了解目標(biāo)系統(tǒng)的內(nèi)部結(jié)構(gòu)和行為。灰盒Fuzzing通常通過靜態(tài)分析、符號執(zhí)行或動態(tài)插樁等方法來獲取有關(guān)目標(biāo)系統(tǒng)的一些內(nèi)部信息,以輔助測試過程。
對程序進(jìn)行插樁, 跟蹤覆蓋率(如邊覆蓋率)
維護(hù)一個高質(zhì)量測試組成的測試池
-
由用戶選定的一些初始輸入開始
-
對測試池中的測試用例進(jìn)行突變, 生成新測試
-
運(yùn)行新測試
-
如果新測試能夠?qū)е滦赂采w(如新的邊被運(yùn)行),將新測試保存到測試池中; 否則拋棄該新測試
找到缺陷,
但仍不能理解程序
優(yōu)點(diǎn): 比黑盒fuzzing更好
-
大體上不需要配置
-
能發(fā)現(xiàn)大量崩潰
缺點(diǎn): 仍然有一點(diǎn)“瞎猜”
- 可能無法執(zhí)行一些路徑
- 對輸入的搜索獨(dú)立于程序
仍需進(jìn)一步改進(jìn)性能
滲透測試
SQL注入
SQL語言中的一些特殊符號
- 分號,意味著指令結(jié)束,有可能開始下一個指令
- 單引號,用于字符常量
- #或–+意味著注釋
尋找可能存在SQL
注入漏洞的鏈接
測試該網(wǎng)站是否有SQL注入漏洞
數(shù)字型漏洞
http://xxx.xxx.xxx/abcd.php?id=XX or 1=1
http://xxx.xxx.xxx/abcd.php?id=XX and 1=2
字符型漏洞
http://xxx.xxx.xxx/abcd.php?id=XX ’or ‘1’=‘1
http://xxx.xxx.xxx/abcd.php?id=XX ’and ‘1’=‘2
猜測管理表的字段
and exists (select id from admin)
and exists (select username from admin)
and exists (select password from admin)
猜測密碼長度
and exists (select id from admin where id=1)
and exists (select id from admin where len(username)<6 and id=1)
SQL注入防御措施
對SQL請求的動態(tài)解析樹
采用SQL引擎預(yù)先對SQL語法進(jìn)行分析,生成該SQL語句的語法樹。對客戶端輸入的參數(shù)中的SQL命令解析為字符串字面值參數(shù),進(jìn)而不會執(zhí)行
- 嚴(yán)格匹配和過濾
- 設(shè)置數(shù)據(jù)庫用戶權(quán)限
關(guān)鍵詞過濾繞過
and->&&
or->||
空格繞過
科學(xué)計數(shù)法+括號
浮點(diǎn)數(shù)+括號
內(nèi)聯(lián)注釋
有些web專用防火墻不過濾注釋里的內(nèi)容
輸入驗證
過濾輸入
-
省略號, 分號, 百分號, 連接符, 下劃線
-
任何有特殊意義的字符
檢查數(shù)據(jù)類型 (例如, 確定特定字段為整數(shù))
使用轉(zhuǎn)義字符
CSRF
木馬病毒
在計算機(jī)系統(tǒng)中, “特洛伊木馬”指系統(tǒng)中被植入的、人為設(shè)計的程序,目的包括通過網(wǎng)絡(luò)遠(yuǎn)程控制其他用戶的計算機(jī)系統(tǒng),竊取信息資料,并可惡意致使計算機(jī)系統(tǒng)癱瘓
根據(jù)傳統(tǒng)的數(shù)據(jù)安全模型的分類,木馬程序的企圖可以對應(yīng)分為三種?
試圖訪問未授權(quán)資源?
試圖阻止訪問?
試圖更改或破壞數(shù)據(jù)和系統(tǒng)
一個典型的特洛伊木馬(程序)通常具有以下四個特點(diǎn):
- 有效性
- 隱蔽性
- 頑固性
- 易植入性
此外,木馬還具有以下輔助型特點(diǎn):
- 自動運(yùn)行
- 欺騙性
- 自動恢復(fù)
- 功能的特殊
木馬的實現(xiàn)原理與攻擊步驟
木馬實現(xiàn)原理
本質(zhì)上說,木馬大多都是網(wǎng)絡(luò)客戶/服務(wù)(Client/Server)程序的組合。常由一個攻擊者控制的客戶端程序和一個運(yùn)行在被控計算機(jī)端的服務(wù)端程序組成
當(dāng)攻擊者要利用“木馬”進(jìn)行網(wǎng)絡(luò)入侵,一般都需完成如下環(huán)節(jié):
向目標(biāo)主機(jī)植入木馬
啟動和隱藏木馬
服務(wù)器端(目標(biāo)主機(jī))和客戶端建立連接
進(jìn)行遠(yuǎn)程控制
植入技術(shù)
自動加載技術(shù)
隱蔽性是木馬程序與其它程序的重要區(qū)別
連接技術(shù)
反彈窗口的連接技術(shù):更容易通過防火墻
監(jiān)控技術(shù)
木馬的遠(yuǎn)程監(jiān)控功能概括起來有以下幾點(diǎn):
木馬的發(fā)展趨勢
跨平臺
模塊化設(shè)計
無連接木馬
主動植入
木馬與病毒的融合
參考文獻(xiàn)
1.堆chunk介紹 - vi0let - 博客園 (cnblogs.com)文章來源:http://www.zghlxwxcb.cn/news/detail-705034.html
2.(148條消息) X86指令:NOP指令_x86 nop_南方鐵匠的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-705034.html
到了這里,關(guān)于軟件與系統(tǒng)安全復(fù)習(xí)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!