安全圈 | 专注于最新网络信息安全讯息新闻

首页

空指針漏洞防護科技提高篇

作者 strmiska 时间 2020-03-01
all

閱讀:9958

空指針漏洞防護科技提高篇

在《空指針漏洞防護技術-初級篇》中我們介紹了空指針及空指針漏洞的概念,在這次高級篇中介紹空指針利用及相應的防護機制。

作者:孫建坡

目錄

1提高篇:空指針利用

1.1 ZwAllocateVirtualMemory基本介紹

1.2 ZwAllocateVirtualMemory函數知識

1.3零頁記憶體分配之實例win7 vs win8

2提高篇:windows零頁記憶體防護機制

2.1搭建內核調試環境

2.1.1虛擬機器及調試環境

2.1.2配寘啟動windbg

2.2用戶態及內核態跨棧調試

2.2.1內核調試用戶態程式

2.2.2用戶態進入內核態

2.3逆向分析nt!NtAllocateVirtualMemory

2.3.1 NtAllocatevirtualMemory參數確認

2.3.2查找NtAllocatevirtualMemory零頁記憶體安全機制

2.3.3確認NtAllocatevirtualMemory零頁記憶體安全機制

2.3.4查找內核中其他對零頁記憶體保護的函數

3總結

1提高篇之:空指針的利用

前面主要介紹了空指針的一些概念和相關的知識,瞭解了什麼是空指針,對於由野指針導致的空指針漏洞不是今天的重點。接下來主要就針對指向零頁記憶體的空指針漏洞做詳細的介紹。

此類漏洞利用主要集中在兩種方式上:

對於第一種情况可以利用NULL指針來繞過條件判斷或是安全認證。比如X.0rg空指針引用拒絕訪問漏洞(CVE-2008-0153),如下圖對比修改補丁前後的對比:

從程式碼補丁可以看出該漏洞利用NULL指針改變程式流程來觸發漏洞。

針對第二種情况,在某些情况下零頁記憶體也是可以被使用,比如下麵兩種情况:

接下來結合ZwAllocateVirtualMemory API函數的調用直觀感受在win7與win8系統中零頁記憶體分配的差异。

1.1 ZwAllocateVirtualMemory基本介紹

zwAllocateVirtualMemory函數在指定行程的虛擬空間中申請一塊記憶體,那是不是只要在記憶體申請空間就會調用zwAllocateVirtualMemory函數呢?調用zwAllocateVirtualMemory需要根據實際的情况。

堆的分配、使用、回收都是通過微軟的API來管理的,最常見的API是malloc和new。在底層調用是HeapAlloc,同時通過HeapCreate來創建堆。對zwAllocateVirtualMemory函數的調用情况是:

. HeapCreate->RtlCreateHeap->ZwAllocateVirualMemory,這裡會直接申請一大塊記憶體,至於申請多大的記憶體,由行程PEB結構中的欄位决定,HeapSegmentReserve欄位指出要申請多大的虛擬記憶體,HeapSegmentCommit指明要提交多大記憶體。

圖中看出默認申請大小是0x100000,默認提交大小是0x2000。下圖是利用Heapcreate函數調用zwAllocatevirtualMemory時的函數調用棧關係。

圖中展示了函數的調用過程。

c.直接利用zwAllocateVirtualMemory分配記憶體,對於zwAllocateVirtualMemory函數,微軟沒有給出公開的檔案,但是可以通過相關資料或是逆向來瞭解該函數的使用管道。直接利用zwAllocateVirtualMemory函數分配記憶體首先加載函數所在模塊,同時獲取該函數的符號地址。由於該函數提供了比較全面的參數,利用此函數來分配記憶體空間更加靈活多變。

1.2 ZwAllocateVirtualMemory函數知識

zwAllocateVirtualMemory該函數在指定行程的虛擬空間中申請一塊記憶體,該塊記憶體默認將以64kb大小對齊。

NTSYSAPI NTSTATUS NTAPI **ZwAllocateVirtualMemory**(IN HANDLE *ProcessHandle*,IN OUT PVOID **BaseAddress*,IN ULONG *ZeroBits*,IN OUT PULONG *RegionSize*,IN ULONG *AllocationType*,IN ULONG *Protect*);

NTSYSAPI NTSTATUS NTAPI **ZwAllocateVirtualMemory** (
IN HANDLE *ProcessHandle*,
IN OUT PVOID **BaseAddress*,
IN ULONG *ZeroBits*,
IN OUT PULONG *RegionSize*,
IN ULONG *AllocationType*,
IN ULONG *Protect*
);

兩個主要參數:

返回值

如果記憶體空間申請成功會返回0,失敗會返回各種NTSTATUS碼。

從zwAllocateVirtualMemory說明來看,本想利用BaseAddress參數在零頁記憶體中分配空間,但是當BaseAdress指定為0時,系統會尋找第一個未使用的區塊來分配,而不是在零頁記憶體中分配。那麼如何才能分配到零頁記憶體呢?

1.3零頁記憶體分配之實例win7 vs win8

瞭解了zwallocatevirtualmemory的用法,就結合實例來看看如何利用該函數進行零頁記憶體分配。前面介紹將BaseAdress設定為0時,並不能在零頁記憶體中分配空間,就需要利用其它管道在零頁記憶體分配空間。在AllocateType參數中有一個分配類型是MEM_TOP_DOWN,該類型表示記憶體分配從上向下分配記憶體。那麼此時指定BaseAddress為一個低地址,例如1,同時指定分配記憶體的大小大於這個值,例如8192(一個記憶體頁),這樣分配成功後地址範圍就是0xFFFFE001(-8191)到1把0地址包含在內了,此時再去嘗試向NULL指針執行的地址寫數據,會發現程式不會异常了。通過這種管道我們發現在0地址分配記憶體的同時,也會在高地址(內核空間)分配記憶體(當然此時使用高地址肯定會出錯,因為這樣在用戶空間)。

下麵就舉個例子看如何在零頁分配記憶體

瞭解windows程式設計的情况下,對上面的程式碼不難理解,獲取模塊控制碼,獲取zwAllocateVirtualMemory函數地址,並傳遞參數調用該函數,其中baseaddress的值為4,參數類型中包含了MEM_TOP_DOWN,即記憶體由上向下分配。所以如果成功的話,將把零頁記憶體中的地址4-0都會分配出去;函數返回0表示記憶體分配成功。然後再給0地址賦值列印,對0地址重新賦值後再次列印,查看結果。

為了能對比win7與win8系統運行結果的差异,需要準備兩個乾淨的系統win7和win8,這裡採用的都是32比特系統。

在win7系統中直接編譯運行該程式,運行結果如下

從顯示結果來看,利用zwAllocateVirtualMemory函數,確實在零記憶體頁分配了4-0地址的空間,也就是說我們可以利用zwAllocateVirtualMemory在零頁記憶體中成功分配空間。

同時在win8系統中同時編譯該程式,如果F5調試運行結果如下圖:

或是CTRL+F5非調試運行,其結果如下圖:

在win8系統中在調用zwallocatevirtualmemory後,函數返回了非0,返回值為0Xc00000f0。最終沒能在零頁記憶體分配空間。也就是說在win8系統中對零頁記憶體做了安全防護,導致在零頁地址分配記憶體時失敗。

接下來看看win8系統中到底對NULL Pointer也就是零頁記憶體做了哪些防護。

2提高篇:windows零頁記憶體防護機制

win8系統對零頁記憶體防護機制通過搜尋引擎可以查到:**在**** WIN8 ****系統中利用了內核行程結構**** EPROCESS ****中的**** flags ****欄位的**** Vdmallowed *\*標誌來判斷是否允許訪問零頁記憶體。**但是知其然而不知其所以然不是我們追求的目標。我們要做的就是在不依靠其他文獻的條件下通過逆向和動態調試科技來剖析win8對零頁記憶體的防護機制。

2.1搭建內核調試環境

探索Win8的零頁記憶體保護機制在內核中的實現,首先需要搭建一個能調試內核的環境。利用上面給出的例子結合內核調試來分析安全機制。關於如何搭建內核環境在之前的文章中已經介紹過,這裡再累贅一下。

5.1.1虛擬機器及調試環境

搭建內核調試環境,採用雙機調試模式,在虛擬機器中安裝win8系統同時配寘win8調試模式。

Vmware安裝win8系統就不在詳解。系統啟動後:

管理員許可權打開CMD

按照上面的步驟配寘好後,關閉虛擬機器,打開win8虛擬機器配置選項,删除並行埠,添加串列埠,同時配寘串列埠,如下圖:

配寘好vmware後啟動虛擬機器進入調試模式,如下圖:

5.1.2配寘啟動windbg

雙機模式調試內核需要配寘windbg工具,現在主機中安裝windbg;創建一個windbg的快捷方式,並在内容->目標中填入如下內容:“C:\program file\WinDbg(x64)\windbg.exe”-b -k com:pipe,port=\.\pipe\com_1,baud= 115200,resets=0,pipe

"C:\program file\WinDbg(x64)\windbg.exe" -b -k com:pipe,port=\.\pipe\com_1,baud=115200,resets=0,pipe

Windbg路徑根據安裝路徑不同而調整。

啟動windbg,由於我的主機是win7系統如果直接按兩下windbg快捷方式啟動

這是缺少必要的許可權,所以在啟動windbg快捷方式時,需要以管理員的許可權啟動,啟動完成後就可以和虛擬機器中的win8系統進行內核級調試,啟動之後的結果如下圖:

到此內核調試的基本環境創建好了,為了能够更加直觀的查看windows系統函數及調用關係需要為windbg配寘符號錶,這樣windbg可以識別出windows標準的匯出符號,同時為了能够更方便的調試nullpointer,也需要加載該程式的符號錶。

到此內核調試環境搭建完成。

2.2用戶態及內核態跨棧調試

該部分是動態調試的關鍵部分,也是注意事項最多的一節。

5.2.1內核調試用戶態程式

在上面配寘好環境後,在windbg中運行G命令,啟動調試。啟動程式後,同時在windbg中按住CRTL+BREAK斷到調試器中。此時win8系統處於中斷狀態,不會再運行任何指令。

調試器斷下來

我們知道在win7與win8中調用zwallocatevirtualmemory時由於零頁記憶體保護機制的原因,win8系統不能在零頁記憶體分配空間。那麼就從調試nullpointer入手,通過調用zwallocatevirtualmemory調試內核態程式來剖解安全機制。

但是現在windbg處於內核調試狀態,查看所有啟動的行程

需要切換到nullpointer行程中。加載了nullpointer符號錶。查看main函數的反彙編如下圖所示:

5.2.2用戶態進入內核態

跟進該函數,最後進入

進入了sysenter指令之前,對windows系統有所瞭解的話,就知道該函數利用該調用進入快速系統調用也就是說此時將由用戶態進入內核態。

SYSENTER用來快速調用一個0層的系統過程。SYSENTER是SYSEXIT的同伴指令。該指令經過了優化,它可以使將由用戶程式碼(運行在3層)向作業系統或執行程式(運行在0層)發起的系統調用發揮最大的效能。

在調用SYSENTER指令前,軟件必須通過下麵的MSR寄存器,指定0層的程式碼段和程式碼指針,0層的堆棧段和堆棧指針:

2.IA32_SYSENTER_EIP:包含一個32比特的0層的程式碼指針,指向第一條指令。

3.IA32_SYSENTER_ESP:包含一個32比特的0層的堆棧指針。

MSR寄存器可以通過指令RDMSR/WRMSR來進行讀寫。寄存器地址如下表。這些地址值在以後的intel 64和IA32處理器中是固定不變的。

為了能保證我們調試的**** NT!NtAllocatevirtualMemory ****函數剛好是**** nullpointer ****調用的,需要給**** NT!NtAllocatevirtualMemory ****函數下中斷點,意思是只在指定行程調用該函數時才斷下來。**待函數斷下來後,同時查看函數調用棧如下圖**:

從圖中調用棧可知,此時斷下來的NT!NtAllocatevirtualMemory剛好是nullpointer引用的內核函數。此時已經進入內核調試狀態。

5.3逆向分析nt!NtAllocateVirtualMemory

進入win8內核調試,就要結合靜態分析和動態調試來挖掘有用的資訊。首先找到win8內核檔案。**需要注意的是此時分析的是虛擬機器中**** win8 ****系統的內核檔案,不是**** win7 *\*主機的內核檔案。**

5.3.1 NtAllocatevirtualMemory參數確認

在前面的調試中,windbg斷在了NT!NtAllocatevirtualMemory函數的入口處。對比NtAllocatevirtualMemory函數在windbg和IDA反編譯的結果:

可知,在運行完指令call __SEH_prolog4_GS,後EBP+8為第一個參數,EBP+C為第二個參數,那就看看在調用call __SEH_prolog4_GS後EBP的值,如下圖:

第一個參數是行程控制碼,本行程控制碼剛好是-1,第二個參數基地址指針,之前我們在程式中基地址是4,也就是0x00000004,查看基地址指針的值:

剛好是0x00000004,用戶態傳入的第三個參數是0,這裡的第三個參數也剛好是0。其他參數不在一一列舉;也就是是說內核函數NtAllocatevirtualMemory與用戶態函數zwallocatevirtualmemory參數是一致的。

5.3.2查找NtAllocatevirtualMemory零頁記憶體安全機制

到了最重要的時刻,接下來動態調試NtAllocatevirtualMemory,一路單步執行,看到eax=0xc00000f0時停下:

從圖中可知並EAX來自於ESI的賦值。那就繼續往上看,看ESI的值是從哪裡來的

從圖中看出,在執行test[edi+0C4],1000000h指令後,如果相等就執行了mov esi,0xc00000f0。

5.3.3確認NtAllocatevirtualMemory零頁記憶體安全機制

前面已經找到了返回0xc00000f0的地方,接下來就要看看條件判斷的地方,EDI=0x84b2ac80是EPROCESS行程的地址,查看EPROCESS結果可知:

在結構eprocess偏移0xc4的位置剛好是標誌Vdmallowed,該值是0,並且[edi=0xc4]與上1000000後剛好是零。導致ESI=0xc00000f0,進而導致EAX=0xc00000f0。

**到此基本上已經確認了**** NtAllocateVirtualMemory ****中對**** NULLPage ****的安全機制,就是檢查**** EPROCESS ****中的**** VdmAllowed *\*的標誌比特。**

確定條件判斷確定位置

從判斷語句來看V76應該是參數中的基地址,V14應該是EPROCESS的地址。看一下這兩個值的來源

從上圖可知v76就是baseaddress;V14追溯到keGetCurrentThread,在核心模式下FS卻指向KPCR(Kernel’s Processor Control Region)結構。即FS段的起點與KPCR結構對齊。看一下KPCR結構偏移124的位置

圖中可知偏移124的位置剛好的當前線程的結構地址。通過查看函數KeGetCurrentThread的實現也能證實這一點,如下圖:

線程結構地址+128(0x80)的位置剛好是當前行程的內核地址,如下圖所示:

之後NtAllocateVirtualMemory函數在將行程結構地址偏移0xC4值與0x1000000做比較判斷做安全檢查。

到目前為止,NtAllocateVirtualMemory函數對零頁記憶體的保護機制剖析完成。總結一下:

5.3.4查找內核中其他對零頁記憶體保護的函數

通過對NtAllocateVirtualMemory逆向分析和動態跟踪瞭解了win8中對零頁記憶體的保護機制;那麼除了NtAllocateVirtualMemory函數,在內核中是否還有其他的函數也對零頁記憶體進行了安全檢查呢?

通過在整個NT內核檔案中查找零頁記憶體保護機制,又發現了幾個包含零頁記憶體包含的函數,搜索結果如下圖:

也就是說在內核檔案中除了NTAllocateVirtualMemory外,還有四個函數對零頁記憶體做檢測:

這四個函數不都是匯出函數,也就是說在內核程式設計中有可能我們使用的一些匯出函數中內部調用了這四個函數中的某一個,其內部已經做了零頁記憶體檢測。

6總結

本文先是介紹了什麼是空指針漏洞,之後對windows系統的零頁記憶體保護機制做了剖析。

空指針漏洞:

對零頁記憶體的安全防護:

Win8****以後的零頁記憶體防護也只是保證了不會在零頁記憶體分配空間,緩解分配零頁記憶體空間來利用漏洞;從上圖可知,利用零頁記憶體分配導致的空指針漏洞也只是眾多空指針漏洞類型中的一種。通過零頁記憶體保護機制並不能緩解所有的空指針漏洞。