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

不當之處,敬請斧正。