ASLR( Address Space Layout Randomization:地址空間布局隨機化)
程序加載到內存后不使用默認的加載地址,將加載基址進行隨機化,依賴重定位表進行地址修復。地址隨機化之后,shellcode中固定的地址值將失效。

圖-程序地址未隨機化處理
開啟/關閉軟件地址隨機化。
- struct IMAGE_NT_HEADERS NtHeader
- struct DLL_CHARACTERISTICS DllCharacteristics
- 1:開啟地址隨機化
- 0:關閉地址隨機化
- WORD IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE : 1
- struct IMAGE_OPTIONAL_HEADER64 OptionalHeader
想要開啟地址隨機化,需要當前程序中存在重定位表,如果程序沒有重定位表,及時將該位置置為1,也無法進行地址隨機化處理。

圖-程序沒有重定位表項

圖-手動開啟程序重定位

圖-重定位失敗
DEP(Data Execution Protection:數據執行保護)
系統內存的分配與使用是符合頁表機制的,具體的頁表機制請自行百度。
每個頁目錄表和頁表項都存在 基址與屬性控制位,通過修改這些控制位,達到當前內存是否有執行、讀、寫等權限。

圖-表項構成
windows 系統上可以調用 VirtualProtect 函數完成內存屬性的修改操作。
BOOL VirtualProtect( LPVOID lpAddress, // 目標地址起始位置 DWORD dwSize, // 大小 DWORD flNewProtect, // 請求的保護方式 PDWORD lpflOldProtect // 保存老的保護方式 );
常量/值說明PAGE_EXECUTE啟用對已提交頁面區域的執行訪問。PAGE_EXECUTE_READ啟用對頁面已提交區域的執行或只讀訪問。PAGE_EXECUTE_READWRITE啟用對頁面已提交區域的執行、只讀或讀/寫訪問權限。PAGE_EXECUTE_WRITECOPY啟用對文件映射對象的映射視圖執行、只讀或復制寫入訪問。。。。。。 |
其他的屬性權限請參考微軟官網聲明:
https://learn.microsoft.com/zh-cn/windows/win32/Memory/memory-protection-constants
在x64Dbg中,"內存布局"頁面也能看到當前調試程序的內存屬性分布信息。

圖-內存布局界面

圖-修改內存保護權限

圖-修改權限
具體實現原理與上述說明一致,參考windows VirtualProtect 函數。
cannary保護
官方文獻:
對于編譯器識別為受緩沖區溢出問題影響的函數,編譯器會在返回地址之前在堆棧上分配空間。調用該函數時,分配的空間中會加載一個安全 Cookie,在模塊加載期間會對該 Cookie 進行一次計算。退出調用該函數時,在 64 位操作系統上展開幀的過程中,會調用幫助程序函數來確保 Cookie 的值依然相同。如果值不同,則指示可能已覆蓋堆棧。 如果檢測到不同的值,將終止進程。
下述情況不給予保護:
- 函數不包含緩沖區
- 函數使用無保護的關鍵字標記
- __declspec(safebuffers)
- 函數在第一個語句中包含內嵌匯編代碼
- __declspec(naked)
- 緩沖區不是8字節類型且大小不大于4個字節
- 聲明全部函數進行保護
- #pragma strict_gs_check(on)
緩沖區溢出安全檢查對 GS 緩沖區執行。GS 緩沖區可以是以下對象之一:
- 大于 4 個字節、具有兩個以上元素且元素類型不是指針類型的數組。
- 大小超過 8 個字節且不包含指針的數據結構。
- 使用 _alloca 函數分配的緩沖區。(alloca是在棧上申請空間)
- 包含 GS 緩沖區的任何類或結構。
接下來通過程序來觀察該保護的操作流程。

圖-GS保護設置界面
主要觀察函數調用過程中,保護流程如何實現,測試使用的代碼如下:
#include <stdio.h>
#include <windows.h>
void func()
{
printf("func\n");
}
int main()
{
func();
system("pause");
return 0;
}
開啟GS保護:
01256AE0 | push ebp ; main.cpp:5 01256AE1 | mov ebp, esp ; 01256AE3 | sub esp, 0xEC ; 01256AE9 | push ebx ; 01256AEA | push esi ; 01256AEB | push edi ; 01256AEC | lea edi, dword ptr ss:[ebp - 0xEC] ; 01256AF2 | mov ecx, 0x3B ; 3B:';' 01256AF7 | mov eax, 0xCCCCCCCC ; 01256AFC | rep stosd ; 01256AFE | mov eax, dword ptr ds:[<___security_cookie>] ; eax = ___security_cookie 01256B03 | xor eax, ebp ; eax = 新棧底的異或值 01256B05 | mov dword ptr ss:[ebp - 0x4], eax ; 將亦或值插入棧中 01256B08 | mov byte ptr ss:[ebp - 0x28], 0x0 ; 初始化緩存空間 01256B0C | xor eax, eax ; 01256B0E | mov dword ptr ss:[ebp - 0x27], eax ; 01256B11 | mov dword ptr ss:[ebp - 0x23], eax ; 01256B14 | mov dword ptr ss:[ebp - 0x1F], eax ; 01256B17 | mov dword ptr ss:[ebp - 0x1B], eax ; 01256B1A | mov dword ptr ss:[ebp - 0x17], eax ; 01256B1D | mov dword ptr ss:[ebp - 0x13], eax ; 01256B20 | mov dword ptr ss:[ebp - 0xF], eax ; 01256B23 | mov word ptr ss:[ebp - 0xB], ax ; 01256B27 | mov byte ptr ss:[ebp - 0x9], al ; 01256B2A | push <cpp."func"> ; main.cpp:7, 12B8C88:"func"==L"畦據" 01256B2F | lea eax, dword ptr ss:[ebp - 0x28] ; 01256B32 | push eax ; 01256B33 | call cpp.12535F9 ; 調用strcpy函數 01256B38 | add esp, 0x8 ; 平衡堆棧 01256B3B | lea eax, dword ptr ss:[ebp - 0x28] ; main.cpp:8 01256B3E | push eax ; 01256B3F | push <cpp."%s\n"> ; 12B8C90:"%s\n"==L"猥\n" 01256B44 | call cpp.12533A6 ; 調用printf函數 01256B49 | add esp, 0x8 ; 01256B4C | push edx ; main.cpp:9 01256B4D | mov ecx, ebp ; 01256B4F | push eax ; 01256B50 | lea edx, dword ptr ds:[<>] ; 01256B56 | call cpp.1252668 ; _RTC_CheckStackVars檢查數組是否越界 01256B5B | pop eax ; 01256B5C | pop edx ; 01256B5D | pop edi ; 01256B5E | pop esi ; 01256B5F | pop ebx ; 01256B60 | mov ecx, dword ptr ss:[ebp - 0x4] ; 取出異或cookie 01256B63 | xor ecx, ebp ; 嘗試還原成舊的的cookie 01256B65 | call cpp.1252208 ; __security_check_cookie重新計算,檢查ebp是否正確 01256B6A | add esp, 0xEC ; 01256B70 | cmp ebp, esp ; 01256B72 | call cpp.1252F69 ; 01256B77 | mov esp, ebp ; 01256B79 | pop ebp ; 01256B7A | ret ;

圖-插入cookie
__security_check_cookie的原理實現如下:
0125F310 | cmp ecx, dword ptr ds:[<___security_cookie>] ;判斷cookie是否還原成功,如果堆棧被覆蓋篡改,
;那么將無法得到正確的___security_cookie
0125F316 | jne <cpp.failure> ;根據判斷結果進行跳轉
0125F318 | ret ;
0125F31A | jmp cpp.12526CC ;跳轉到__report_gsfailure函數繼續執行
如果強行進入__report_gsfailure函數執行,最終會停止在異常處理上。
0126BD00 | push ebp ; 0126BD01 | mov ebp, esp ; 0126BD03 | sub esp, 0x324 ; 0126BD09 | push 0x17 ; 0126BD0B | call cpp.1253473 ;_IsProcessorFeaturePresent 0126BD10 | test eax, eax ; 0126BD12 | je cpp.126BD1B ; 0126BD14 | mov ecx, 0x2 ; 0126BD19 | int 0x29 ;

圖-異常緩沖區溢出
查看0x29號中斷對應內容。可以看到函數調用處為0x00000000地址處。
1: kd> !idt 0x29 Dumping IDT: 29: 00000000
關閉GS保護,觀察生成的匯編代碼:
;func函數的匯編代碼 00846AE0 | push ebp 00846AE1 | mov ebp, esp 00846AE3 | sub esp, 0xE8 00846AE9 | push ebx 00846AEA | push esi 00846AEB | push edi 00846AEC | lea edi, dword ptr ss:[ebp - 0xE8] 00846AF2 | mov ecx, 0x3A 00846AF7 | mov eax, 0xCCCCCCCC 00846AFC | rep stosd 00846AFE | mov byte ptr ss:[ebp - 0x24], 0x0 00846B02 | xor eax, eax 00846B04 | mov dword ptr ss:[ebp - 0x23], eax 00846B07 | mov dword ptr ss:[ebp - 0x1F], eax 00846B0A | mov dword ptr ss:[ebp - 0x1B], eax 00846B0D | mov dword ptr ss:[ebp - 0x17], eax 00846B10 | mov dword ptr ss:[ebp - 0x13], eax 00846B13 | mov dword ptr ss:[ebp - 0xF], eax 00846B16 | mov dword ptr ss:[ebp - 0xB], eax 00846B19 | mov word ptr ss:[ebp - 0x7], ax 00846B1D | mov byte ptr ss:[ebp - 0x5], al 00846B20 | push <cpp."func"> 00846B25 | lea eax, dword ptr ss:[ebp - 0x24] 00846B28 | push eax 00846B29 | call cpp.8435F9 00846B2E | add esp, 0x8 00846B31 | lea eax, dword ptr ss:[ebp - 0x24] 00846B34 | push eax 00846B35 | push <cpp."%s\n"> 00846B3A | call cpp.8433A6 00846B3F | add esp, 0x8 00846B42 | push edx 00846B43 | mov ecx, ebp 00846B45 | push eax 00846B46 | lea edx, dword ptr ds:[<>] 00846B4C | call cpp.842668 00846B51 | pop eax 00846B52 | pop edx 00846B53 | pop edi 00846B54 | pop esi 00846B55 | pop ebx 00846B56 | add esp, 0xE8 00846B5C | cmp ebp, esp 00846B5E | call cpp.842F69 00846B63 | mov esp, ebp 00846B65 | pop ebp 00846B66 | ret
其中缺少了cookie的異或、插入、檢驗等操作。
RELRO (ReLocation Read-Only)
程序加載到內存中時,會解析當前程序結構,并將當前程序所需的動態庫加載到內存中。最終修復程序與動態庫之間的地址關聯關系,形成一種調用、被調用的關系。
其中Linux程序依賴的就是GOT表,效果類似windows上的IAT表(導入地址表)。
同理,Linux上為了程序的運行效率,可能不會一次性修復所有函數地址。因此,提出了PLT表,用來延遲修復所需全局函數/全局變量地址,效果等同PE文件的延遲導入表。
如果是手工解析ELF/PE文件,能在解析修復對應表項時,對所需函數進行hook操作。但是通過系統加載解析的話,就無法及時的對指定函數hook操作。
但是延遲導入表的作用也在此刻凸顯,該表項是根據需求進行修復,那么嘗試修改該表項內容,再將其修復到程序中,是否就能達到hook等操作的效果。
- 該保護也是為了防止有人篡改延遲導入表內容。取消了延遲導入表。
- 將程序全部全局函數、變量都放置在GOT/IAT表中,在程序一開始加載就修復所有地址信息,防止后期被二次修改。
關于vs的延遲導入表的設置可以參考官網聲明:
https://learn.microsoft.com/zh-cn/cpp/build/reference/delay-delay-load-import-settings?view=msvc-170
不當之處,敬請斧正。
安全圈
嘶吼專業版
安全牛
合天網安實驗室
看雪學苑
GoUpSec
安全圈
E安全
安全圈
CNCERT國家工程研究中心
HACK學習呀
0x00實驗室