分析及防護:Win10執行流保護繞過問題
Black Hat USA 2015正在進行,在微軟安全響應中心公佈的最新貢獻榜單中,綠盟科技安全研究員張雲海位列第6位,綠盟科技安全團隊(NSFST)位列28位,綠盟科技安全團隊(NSFST)常年致力於發現並解決計算機以及網路系統中存在的各種安全缺陷。這篇《Windows 10執行流保護繞過問題及修復》是團隊在此次大會上分享的主要內容
0x00 Content
- 執行流保護(CFG)
- CFG原理
- 繞過問題
CustomHeap::Heap
物件- 繞過CFG
- 問題修復
HeapPageAllocator::ProtectPages
函式- 修復機制
0x01 內容摘要
Black Hat USA 2015正在進行,在微軟安全響應中心公佈的最新貢獻榜單中,綠盟科技安全研究員張雲海位列第6位,綠盟科技安全團隊(NSFST)位列28位,綠盟科技安全團隊(NSFST)常年致力於發現並解決計算機以及網路系統中存在的各種安全缺陷。這篇《Windows 10執行流保護繞過問題及修復》是團隊在此次大會上分享的主要內容。
1. 1月22日,微軟釋出Windows 10技術預覽版,Build號9926;
2. 2月,綠盟科技安全團隊(NSFST)展開對其安全機制的研究,發現並與微軟一起解決了CFG繞過問題;
3. 3月,微軟釋出補丁修復了CFG繞過問題;
4. 7月21日,在綠盟科技Techworld技術大會上分享了此次研究成果;
5. 8月7日,在Black Hat US 2015上進行演講併發布分析文章。
綠盟科技安全團隊NSFST一直努力發現及修復計算機以及網路系統中存在的各種安全缺陷,如果您需要了解更多資訊,請聯絡:
- 綠盟科技微博
- http://weibo.com/nsfocus
- 綠盟科技微訊號
- 搜尋公眾號綠盟科技
0x02 執行流保護(CFG)
攻擊者常常溢位覆蓋或者直接篡改暫存器EIP的值,篡改間接呼叫的地址,進而控制了程式的執行流程。執行流保護(CFG,Control Flow Guard)
是微軟從Windows 8.1 update 3及Windows 10技術預覽版開始,預設啟用的一項緩解技術。這項技術透過在間接跳轉前插入校驗程式碼,檢查目標地址的有效性,進而可以阻止執行流跳轉到預期之外的地點,最終及時並有效的進行異常處理,避免引發相關的安全問題。
這種思想及技術在業界有了較為成熟的應用,此次Windows 10將其引入,以便提高其安全性。但是綠盟科技安全團隊(NSFST)在分析CFG的實現機制過程中,發現了CFG存在全面繞過的方法,隨即向微軟提報,並在隨後的一段時間內,配合微軟修復了這個問題。
0x03 CFG原理
在編譯啟用了CFG的模組時,編譯器會分析出該模組中所有間接函式呼叫可達的目標地址,並將這一資訊儲存在Guard CF Function Table
中。
:006> dds jscript9!\_load\_config\_used + 48 l5
62b21048 62f043fc jscript9!\_\_guard\_check\_icall\_fptr Guard CF Check Function Pointer
62b2104c 00000000 Reserved
62b21050 62b2105c jscript9!\_\_guard\_fids\_table Guard CF Function Table
62b21054 00001d54 Guard CF Function Count
62b21058 00003500 Guard Flags
同時,編譯器還會在所有間接函式呼叫之前插入一段校驗程式碼,以確保呼叫的目標地址是預期中的地址。這是未啟用CFG的情況:
jscript9!Js::JavascriptOperators::HasItem+0x15:
66ee9558 8b03 mov eax,dword ptr [ebx]
66ee955a 8bcb mov ecx,ebx
66ee955c 56 push esi
66ee955d ff507c call dword ptr [eax+7Ch]
66ee9560 85c0 test eax,eax
66ee9562 750b jne jscript9!Js::JavascriptOperators::HasItem+0x2c (66ee956f)
這是啟用CFG的情況:
ript9!Js::JavascriptOperators::HasItem+0x1b:
62c31e13 8b03 mov eax,dword ptr [ebx]
62c31e15 8bfc mov edi,esp
62c31e17 52 push edx
62c31e18 8b707c mov esi,dword ptr [eax+7Ch]
62c31e1b 8bce mov ecx,esi
62c31e1d ff15fc43f062 call dword ptr [jscript9!\_\_guard\_check\_icall\_fptr (62f043fc)]
62c31e23 8bcb mov ecx,ebx
62c31e25 ffd6 call esi
62c31e27 3bfc cmp edi,esp
62c31e29 0f8514400c00 jne jscript9!Js::JavascriptOperators::HasItem+0x33 (62cf5e43)
作業系統在建立支援CFG的程式時,將CFG Bitmap對映到其地址空間中,並將其基址儲存在ntdll!LdrSystemDllInitBlock+0x60
中。
CFG Bitmap是記錄了所有有效的間接函式呼叫目標地址的點陣圖,出於效率方面的考慮,平均每1位對應8個地址(偶數位對應1個0x10對齊的地址,奇數位對應剩下的15個非0x10對齊的地址)。
提取目標地址對應位的過程如下: * 取目標地址的高24位作為索引i; * 將CFG Bitmap當作32位整數的陣列,用索引i取出一個32位整數bits; * 取目標地址的第4至8位作為偏移量n; * 如果目標地址不是0x10對齊的,則設定n的最低位; * 取32位整數bits的第n位即為目標地址的對應位。
作業系統在載入支援CFG的模組時,根據其Guard CF Function Table
來更新CFG Bitmap中該模組所對應的位。同時,將函式指標\_guard\_check\_icall\_fptr
初始化為指向ntdll!LdrpValidateUserCallTarget
。
ntdll!LdrpValidateUserCallTarget
從CFG Bitmap中取出目標地址所對應的位,根據該位是否設定來判斷目標地址是否有效。若目標地址有效,則該函式返回進而執行間接函式呼叫;否則,該函式將丟擲異常而終止當前程式。
ll!LdrpValidateUserCallTarget:
774bd970 8b1570e15377 mov edx,dword ptr [ntdll!LdrSystemDllInitBlock+0x60 (7753e170)]
774bd976 8bc1 mov eax,ecx
774bd978 c1e808 shr eax,8
774bd97b 8b1482 mov edx,dword ptr [edx+eax\*4]
774bd97e 8bc1 mov eax,ecx
774bd980 c1e803 shr eax,3
774bd983 f6c10f test cl,0Fh
774bd986 7506 jne ntdll!LdrpValidateUserCallTargetBitMapRet+0x1 (774bd98e)
ntdll!LdrpValidateUserCallTargetBitMapCheck+0xd:
774bd988 0fa3c2 bt edx,eax
774bd98b 730a jae ntdll!LdrpValidateUserCallTargetBitMapRet+0xa (774bd997)
ntdll!LdrpValidateUserCallTargetBitMapRet:
774bd98d c3 ret
ntdll!LdrpValidateUserCallTargetBitMapRet+0x1:
774bd98e 83c801 or eax,1
774bd991 0fa3c2 bt edx,eax
774bd994 7301 jae ntdll!LdrpValidateUserCallTargetBitMapRet+0xa (774bd997)
ntdll!LdrpValidateUserCallTargetBitMapRet+0x9:
774bd996 c3 ret
0x04 繞過問題
透過上面的原理分析,我們發現CFG的實現中存在一個隱患,校驗函式ntdll!LdrpValidateUserCallTarget
是透過函式指標\_guard\_check\_icall\_fptr
來呼叫的。
如果我們修改\_guard\_check\_icall\_fptr
,將其指向一個合適的函式,就可以使任意目標地址透過校驗,從而全面的繞過CFG。通常情況下,\_guard\_check\_icall\_fptr
是隻讀的:
06> x jscript9!___guard_check_icall_fptr
62f043fc jscript9!__guard_check_icall_fptr = <no type information>
0:006> !address 62f043fc
Usage: Image
Base Address: 62f04000
End Address: 62f06000
Region Size: 00002000
State: 00001000 MEM_COMMIT
Protect: 00000002 PAGE_READONLY
Type: 01000000 MEM_IMAGE
Allocation Base: 62b20000
Allocation Protect: 00000080 (null)
Image Path: C:\Windows\System32\jscript9.dll
Module Name: jscript9
Loaded Image Name: C:\Windows\System32\jscript9.dll
Mapped Image Name:
但如果利用jscript9中的CustomHeap::Heap
物件將其變成可讀寫的,那麼就會出現問題了。
0x05 CustomHeap::Heap物件
CustomHeap::Heap
是jscript9中用於管理私有堆的類,其結構如下:
stomHeap::Heap
+0x000 HeapPageAllocator : PageAllocator
+0x060 HeapArenaAllocator : Ptr32 ArenaAllocator
+0x064 PartialPageBuckets : [7] DListBase<CustomHeap::Page>
+0x09c FullPageBuckets : [7] DListBase<CustomHeap::Page>
+0x0d4 LargeObjects : DListBase<CustomHeap::Page>
+0x0dc DecommittedBuckets : DListBase<CustomHeap::Page>
+0x0e4 DecommittedLargeObjects : DListBase<CustomHeap::Page>
+0x0ec CriticalSection : LPCRITICAL_SECTION
當CustomHeap::Heap
物件析構時,其解構函式會呼叫CustomHeap::Heap::FreeAll
來釋放所有分配的記憶體。
__thiscall CustomHeap::Heap::~Heap(CustomHeap::Heap *this)
{
CustomHeap::Heap *v1; // [email protected]
v1 = this;
CustomHeap::Heap::FreeAll(this);
DeleteCriticalSection((LPCRITICAL_SECTION)((char *)v1 + 0xEC));
\'eh vector destructor iterator\'((int)((char *)v1 + 0x9C), 8u, 7, sub_10010390);
\'eh vector destructor iterator\'((int)((char *)v1 + 0x64), 8u, 7, sub_10010390);
return PageAllocator::~PageAllocator(v1);
}
CustomHeap::Heap::FreeAll
為每個Bucket物件呼叫CustomHeap::Heap::FreeBucket
。
d __thiscall CustomHeap::Heap::FreeAll(CustomHeap::Heap *this)
{
CustomHeap::Heap *v1; // [email protected]
signed int v2; // [email protected]
int v3; // [email protected]
int v4; // [email protected]
v1 = this;
v2 = 7;
v3 = (int)((char *)this + 0x9C);
do
{
CustomHeap::Heap::FreeBucket(v1, v3 - 0x38, (int)this);
CustomHeap::Heap::FreeBucket(v1, v3, v4);
v3 += 8;
--v2;
}
while ( v2 );
CustomHeap::Heap::FreeLargeObject<1>(this);
CustomHeap::Heap::FreeDecommittedBuckets(v1);
CustomHeap::Heap::FreeDecommittedLargeObjects(v1);
}
CustomHeap::Heap::FreeBucket
遍歷Bucket的雙向連結串列,為每個節點的CustomHeap::Page 物件呼叫CustomHeap::Heap::EnsurePageReadWrite<1,4>
。
__thiscall CustomHeap::Heap::FreeBucket(PageAllocator *this, int a2, int a3)
{
PageAllocator *v3; // [email protected]
int result; // [email protected]
int v5; // [email protected]
int v6; // [sp+8h] [bp-8h]@1
int v7; // [sp+Ch] [bp-4h]@1
v3 = this;
v6 = a2;
v7 = a2;
while ( 1 )
{
result = SListBase<Bucket<AddPropertyCacheBucket>,FakeCount>::Iterator::Next(&v6);
if ( !(_BYTE)result )
break;
v5 = v7 + 8;
CustomHeap::Heap::EnsurePageReadWrite<1,4>(v7 + 8);
PageAllocator::ReleasePages(v3, *(void **)(v5 + 0xc), 1u, *(struct PageSegment **)(v5 + 4));
DListBase<CustomHeap::Page>::EditingIterator::RemoveCurrent<ArenaAllocator>(*((ArenaAllocator **)v3 + 0x18));
}
return result;
}
CustomHeap::Heap::EnsurePageReadWrite<1,4>
用以下引數呼叫VirtualProtect:
- lpAddress: CustomHeap::Page 物件的成員變數address
- dwSize: 0x1000
flNewProtect: PAGE_READWRITE
RD __stdcall CustomHeap::Heap::EnsurePageReadWrite<1,4>(int a1) { DWORD result; // [email protected] DWORD flOldProtect; // [sp+4h] [bp-4h]@3 if ( (_BYTE *)(a1 + 1) || *(_BYTE *)a1 ) { result = 0; } else { flOldProtect = 0; VirtualProtect((LPVOID *)(a1 + 0xC), 0x1000u, 4u, &flOldProtect); result = flOldProtect; *(_BYTE *)(a1 + 1) = 1; } return result; }
將記憶體頁面標記為PAGE_READWRITE, 這正是出現問題的關鍵地方。
0x06 繞過CFG
透過修改CustomHeap::Heap
物件,我們可以將一個只讀頁面變成可讀寫的,從而可以改寫函式指標\_guard\_check\_icall\_fpt
r的值。觀察ntdll!LdrpValidateUserCallTarget
在目標地址有效時執行的指令:
mov eax,ecx
shr eax,8
mov edx,dword ptr [edx+eax*4]
mov eax,ecx
shr eax,3
test cl,0Fh
jne ntdll!LdrpValidateUserCallTargetBitMapRet+0x1 (774bd98e)
bt edx,eax
jae ntdll!LdrpValidateUserCallTargetBitMapRet+0xa (774bd997)
ret
從呼叫者的角度來看,上述指令與單條ret指令之間並沒有本質區別。因此,將函式指標\_guard\_check\_icall\_fptr
改寫為指向ret指令,就可以使任意的目標地址透過校驗,從而全面的繞過CFG。
0x07 問題修復
綠盟科技安全團隊(NSFST)發現這一問題後,立即向微軟報告了相關情況。微軟很快修復了這一問題,並在2015年3月釋出了相關的補丁。在該補丁中,微軟引入了一個新的函式HeapPageAllocator::ProtectPages
。
0x08 HeapPageAllocator::ProtectPages函式
__thiscall HeapPageAllocator::ProtectPages(HeapPageAllocator *this, LPCVOID lpAddress, unsigned int a3, struct Segment *a4, DWORD flNewProtect, unsigned __int32 *a6, unsigned __int32 a7)
{
unsigned __int32 v7; // [email protected]
unsigned int v8; // [email protected]
int result; // [email protected]
struct _MEMORY_BASIC_INFORMATION Buffer; // [sp+Ch] [bp-20h]@4
DWORD flOldProtect; // [sp+28h] [bp-4h]@7
v7 = (unsigned __int32)this;
if ( (unsigned __int16)lpAddress & 0xFFF
|| (v8 = *((_DWORD *)a4 + 2), (unsigned int)lpAddress < v8)
|| (unsigned int)((char *)lpAddress - v8) > (*((_DWORD *)a4 + 3) - a3) << 12
|| !VirtualQuery(lpAddress, &Buffer, 0x1Cu)
|| Buffer.RegionSize < a3 << 12
|| a7 != Buffer.Protect )
{
CustomHeap_BadPageState_fatal_error(v7);
result = 0;
}
else
{
*a6 = Buffer.Protect;
result = VirtualProtect((LPVOID)lpAddress, a3 << 12, flNewProtect, &flOldProtect);
}
return result;
}
這個函式是VirtualProtect的一個封裝,在呼叫VirtualProtect之前對引數進行校驗,如下:
- 檢查lpAddress是否是0x1000對齊的;
- 檢查lpAddress是否大於Segment的基址;
- 檢查lpAddress加上dwSize是否小於Segment的基址加上Segment的大小;
- 檢查dwSize是否小於Region的大小;
- 檢查目標記憶體的訪問許可權是否等於指定的(透過引數)訪問許可權;
任何一個檢查項未透過,都會呼叫CustomHeap_BadPageState_fatal_error
丟擲異常而終止程式。
0x09 修復機制
CustomHeap::Heap::EnsurePageReadWrite<1,4>
改為呼叫HeapPageAllocator::ProtectPages
而不再直接呼叫VirtualProtect。
1 unsigned __int32 __thiscall CustomHeap::Heap::EnsurePageReadWrite<1,4>(HeapPageAllocator *this, int a2)
2 {
3 unsigned __int32 result; // [email protected]
4 unsigned __int32 v3; // [sp+4h] [bp-4h]@5
5
6 if ( *(_BYTE *)(a2 + 1) || *(_BYTE *)a2 )
7 {
8 result = 0;
9 }
10 else
11 {
12 v3 = 0;
13 HeapPageAllocator::ProtectPages(this, *(LPCVOID *)(a2 + 12), 1u, *(struct Segment **)(a2 + 4), 4u, &v3, 0x10u);
14 result = v3;
15 *(_BYTE *)(a2 + 1) = 1;
16 }
17 return result;
18 }
這裡引數中指定的訪問許可權是PAGE_EXECUTE
,從而防止了利用CustomHeap::Heap
將只讀記憶體頁面變成可讀寫記憶體頁面。
0x10 參考文獻
1 MJ0011. Windows 10 Control Flow Guard Internals
http://www.powerofcommunity.net/poc2014/mj0011.pdf
[2] Jack Tang. Exploring Control Flow Guard in Windows 10
http://sjc1-te-ftp.trendmicro.com/assets/wp/exploring-control-flow-guard-in-windows10.pdf
[3] Francisco Falcón. Exploiting CVE-2015-0311, Part II: Bypassing Control Flow Guard on Windows 8.1 Update 3
[4] Yuki Chen. The Birth of a Complete IE11 Exploit under the New Exploit Mitigations
相關文章
- Win10安全特性之執行流保護2020-08-19Win10
- Kernel Stack棧溢位攻擊及保護繞過2024-09-23
- 中轉Webshell繞過流量檢測防護2024-03-08Webshell
- 360護心鏡指令碼分析及N種繞過方式2020-08-19指令碼
- 關於重複發包的防護與繞過2020-08-19
- ZipperDown漏洞簡單分析及防護2018-05-18
- win10怎麼啟用DEP資料執行保護_win10系統資料執行保護怎麼開啟2019-12-20Win10
- 資料執行保護講解2024-08-28
- java synchronized 保護執行緒安全2024-06-21Javasynchronized執行緒
- 車聯網安全威脅分析及防護思路,幾維安全為智慧汽車保駕護航2020-07-02
- 關於“等保保護”最常見問題解答!2023-02-02
- 流的破壞與保護2018-11-25
- 檔案上傳之WAF繞過及相安全防護2021-08-12
- [原創]利用SEH異常處理機制繞過GS保護2018-04-20
- 轉載 利用SEH異常處理機制繞過GS保護2018-04-19
- 2021 SDC 議題早班車 | AirTag位置資訊偽造和韌體讀保護繞過2021-10-12AI
- 移動App安全等級保護測評防護要點2020-05-26APP
- 研究人員發現macOS隱私保護重大漏洞 攻擊者可繞過蘋果隱私保護核心機制2021-08-09Mac蘋果
- 智慧汽車安全風險及防護技術分析2020-08-13
- win10 保護眼睛的方法 win10 保護眼睛怎麼設定2020-10-05Win10
- win10如何關閉防毒防護 win10病毒防護徹底關閉2022-03-15Win10防毒
- win10如何禁用增強保護_win10怎麼禁用系統保護2020-08-30Win10
- 一位元控制所有:透過一位元繞過Windows 10保護2020-08-19Windows
- win10怎麼關閉防毒防護 win10關閉防毒防護的方法2022-04-19Win10防毒
- 12V/20V安全供電新選擇:PW1558過流限壓保護晶片,帶短路保護2024-04-06晶片
- 為什麼win10的“實時防護”關不掉_win10實時保護關不掉如何解決2020-07-22Win10
- 5G還未到來,可以繞過保護措施的首批漏洞已抵達2019-02-26
- 守護執行緒2024-05-29執行緒
- 雲吞鋪子:CC防護分析2019-05-16
- 過載保護原理與實戰2020-12-12
- EOCRFMZ2-WRCUWZ施耐德電流保護器2021-01-01CRF
- 《個人資訊保護法》今天透過,企業應該注意哪些問題?2021-08-20
- win10實時保護關閉方法_WIN10的實時保護如何關閉2020-07-22Win10
- 安全專家成功山寨AirTag 向蘋果證明可繞過追蹤保護功能2022-02-28AI蘋果
- 保護模式2024-03-11模式
- 保護期限2024-03-20
- 防護DDoS問題你知道更好的緩解流程嗎?2020-12-02
- 繞過PowerShell執行策略方法2020-06-20