翟繼強, 陳攀, 徐曉, 楊海陸
(哈爾濱理工大學(xué) 計算機科學(xué)與技術(shù)學(xué)院, 黑龍江 哈爾濱 150080)
近年來,黑客經(jīng)常通過網(wǎng)絡(luò)傳播惡意程序,當(dāng)計算機染上惡意程序時,計算機內(nèi)存會留下惡意活動痕跡[1-2],這時通過內(nèi)存取證技術(shù)就可以捕獲到這些痕跡并落實網(wǎng)絡(luò)犯罪的數(shù)字證據(jù)。段堆中含有進程運行時生成的重要信息,提取出段堆中的信息可以了解進程的運行情況,因此對段堆的內(nèi)存取證研究在信息安全的防護領(lǐng)域意義重大。
目前,對堆的內(nèi)存取證研究根據(jù)操作系統(tǒng)的不同可分為基于Linux系統(tǒng)的堆取證研究、基于安卓環(huán)境的堆取證研究、基于Windows 7系統(tǒng)的堆取證研究。在Linux系統(tǒng)中, Block研究了glibc庫創(chuàng)建的堆結(jié)構(gòu),使用了堆結(jié)構(gòu)定位及關(guān)鍵字段偏移法復(fù)現(xiàn)堆內(nèi)部信息,解決了標簽搜索不準確問題[3]。在安卓系統(tǒng)中,張俊芙研究出了3種提取堆信息的方法,分別為:把目標值做為搜索對象進行搜索;使用相關(guān)對象定位引用對象和同類對象;使用目標數(shù)據(jù)猜測法搜索目標數(shù)據(jù)[4]。在Windows 7系統(tǒng)中, Cohen研究了NT堆中低碎片化堆的創(chuàng)建并且使用硬件PTE解析算法,復(fù)現(xiàn)NT堆中無效頁面信息[5]。
然而,上述的堆取證研究并沒有針對Windows 10系統(tǒng)中的段堆,通過現(xiàn)有的內(nèi)存取證技術(shù)不能重現(xiàn)段堆信息,進而不能獲取針對段堆的堆溢出攻擊的取證信息。同時段堆尚未在MSDN文檔上公開而且目前對段堆結(jié)構(gòu)的研究還并不充分,因此需要進一步研究段堆結(jié)構(gòu)。為了彌補這些缺陷,本文研究了多個版本的Windows 10段堆并且提出一種利用池掃描技術(shù)識別內(nèi)核對象,再結(jié)合字段信息偏移定位的方法,提取段堆內(nèi)部信息。經(jīng)過測試,該方法能成功復(fù)現(xiàn)段堆內(nèi)部信息,這些信息能幫助調(diào)查人員獲取堆溢出攻擊的數(shù)字證據(jù)。本文研究的主要內(nèi)容如下:
1) 研究了多個Windows 10版本的段堆及其組件結(jié)構(gòu)中字段的作用;
2) 根據(jù)段堆特征值定位段堆的位置,并提取出段堆內(nèi)部信息;
3) 提取出大塊分配組件分配堆塊的元數(shù)據(jù)信息;
4) 定位可變大小分配組件結(jié)構(gòu)和低碎片化堆分配組件結(jié)構(gòu)的位置并且提取出它們分配的內(nèi)存信息;
5) 使用本文研發(fā)出的插件檢測堆溢出攻擊。
當(dāng)創(chuàng)建新的進程時,Windows內(nèi)核會在內(nèi)核內(nèi)存的非分頁池中創(chuàng)建-EPROCESS結(jié)構(gòu),通過該結(jié)構(gòu)的內(nèi)部信息可以定位用戶模式下進程環(huán)境塊的位置。進程環(huán)境塊存放的是進程信息,其中就包括進程堆信息、當(dāng)前的工作目錄、環(huán)境變量、命令行參數(shù)等[6]。
Windows程序管理器創(chuàng)建進程時,內(nèi)核會對進程分配4GB的虛擬內(nèi)存,并創(chuàng)建和初始化堆管理器,不同的堆管理器對應(yīng)著不同的特征值。Windows 10內(nèi)核分配堆塊時,根據(jù)堆的特征值進行相應(yīng)堆塊的分配。Windows 10內(nèi)核有較好的安全機制,分配堆塊的過程中使用臨界區(qū)和原子操作函數(shù)實現(xiàn)線程同步,確保堆塊分配成功。堆塊的分配依賴于內(nèi)部的4個組件,分配的過程中根據(jù)堆塊大小選擇合適的組件進行分配。
段堆是特殊的內(nèi)存管理器,它只存在于Windows 10系統(tǒng)中。段堆及其組件每次在分配內(nèi)存之前會預(yù)先申請一塊內(nèi)存區(qū)域,然后再從內(nèi)存區(qū)域中分配進程請求的內(nèi)存。在可變大小分配組件分配的內(nèi)存中已申請的內(nèi)存大部分已被分配,只留有少數(shù)空閑內(nèi)存未被使用,有些段堆中可能不含有該組件分配的子段。低碎片化堆組件分配的內(nèi)存相比其他組件分配的內(nèi)存要小很多,而且基本上不會產(chǎn)生空閑塊。大塊分配組件中未使用的內(nèi)存相比其他組件要大很多,因此為了減少內(nèi)存浪費,大塊分配組件在段堆中很少使用。
段堆的結(jié)構(gòu)和Windows系統(tǒng)中其他堆的結(jié)構(gòu)類似,都是由字段偏移量、字段、字段數(shù)據(jù)類型組成。Windows 10有許多的版本,不同版本的Windows 10系統(tǒng)含有不同的段堆結(jié)構(gòu)。隨著段堆結(jié)構(gòu)的更新,段堆的安全機制在不斷完善并且內(nèi)存分配效率也在不斷提高。圖1顯示了段堆的結(jié)構(gòu)信息(17134版本),這些信息記錄了組件的偏移量、不同內(nèi)存狀態(tài)的頁面數(shù)量等。段堆同內(nèi)核對象一樣,在內(nèi)存中存在獨屬的結(jié)構(gòu)體,結(jié)構(gòu)體中含有段堆的信息。
由于進程地址空間的對齊粒度為64 kB,因此內(nèi)存管理器分配的最小內(nèi)存為64 kB,當(dāng)要分配小于64 kB的內(nèi)存時,會產(chǎn)生較大的內(nèi)存碎片,而堆管理器分配的內(nèi)存可以小于64 kB,因此可以減少內(nèi)存浪費,提高內(nèi)存利用率。進程開始運行時,會創(chuàng)建一個默認堆,使用HeapCreate函數(shù)可以創(chuàng)建額外的私有堆,在Windows 10系統(tǒng)中,私有堆包括段堆和NT堆。經(jīng)分析發(fā)現(xiàn),它們之間存在著較大的差異,段堆和NT堆之間存在的差異如下所示:
1) 在內(nèi)存分配頻繁的情況下,NT堆分配內(nèi)存的速度要比段堆快,因為段堆在分配內(nèi)存時需要經(jīng)過更多的操作步驟;
2) 段堆具有更好的安全機制,在段堆中,對元數(shù)據(jù)的訪問是互斥進行的,在同一時刻,只允許一個線程對其進行操作,而實際數(shù)據(jù)不是,因此段堆中的元數(shù)據(jù)獨立于實際數(shù)據(jù)。然而在NT堆中,元數(shù)據(jù)不是互斥訪問,于是元數(shù)據(jù)和實際數(shù)據(jù)混在一起;
3) 段堆使用4個組件對堆塊進行分配,而NT堆只依賴于2個組件對堆塊進行分配。段堆細化了堆塊的分配范圍,因此具有更高的內(nèi)存利用率;
4) NT堆具有更完善的內(nèi)存管理機制,它能夠更全面地定位跟蹤NT堆中內(nèi)存釋放與分配情況,因此NT堆結(jié)構(gòu)中含有更全面的信息;
5) 段堆是新出現(xiàn)的堆管理器,內(nèi)部的管理機制需要不斷完善,段堆的結(jié)構(gòu)會隨著Windows 10系統(tǒng)的更新而升級。由于NT堆的內(nèi)存管理機制已趨于成熟,那么在后期,NT堆將不再更新;
6) 段堆是Windows 10出現(xiàn)后引進的,因此段堆只出現(xiàn)在Windows 10系統(tǒng)中,而NT堆存在于所有的Windows系統(tǒng)中。
段堆對內(nèi)存的管理依賴于內(nèi)部的4個組件,組件主要負責(zé)對內(nèi)存的釋放與分配[7]。不同的組件分配不同的內(nèi)存大小:低碎片化堆分配組件分配不大于16 kB的內(nèi)存區(qū)域;可變大小分配組件從段堆中請求分配的內(nèi)存大小不大于128 kB;后端分配組件分配內(nèi)存塊的大小介于128 kB到508 kB之間;大塊分配組件分配內(nèi)存塊的大小大于508 kB[7]。經(jīng)分析發(fā)現(xiàn),隨著Windows 10系統(tǒng)的更新升級,段堆的組件結(jié)構(gòu)也相應(yīng)地發(fā)生了變化,這些變化讓系統(tǒng)更準確地檢測內(nèi)存分布情況,從而更合理地分配堆內(nèi)存,提高內(nèi)存利用率。Mark研究了14295版本的段堆[7],但他分析的結(jié)構(gòu)字段并不全面,本文補充分析了14295版本的段堆。在深入研究15063版本、16299版本和17134版本的段堆之后發(fā)現(xiàn)這3個版本相比于14295版本具有更好的安全性并且字段的位置及數(shù)量也發(fā)生了改變。
在段堆分配堆塊時,優(yōu)先給低碎片堆組件分配,若能進行分配,則遍歷Buckets數(shù)組找到大小合適并處于激活狀態(tài)的Bucket,未能找到則分配新的Bucket。該組件分配的堆塊具有最高的內(nèi)存利用率,幾乎不產(chǎn)生內(nèi)存碎片。堆塊是從子段中進行分配,低碎片化堆中的子段以鏈表的形式串連在一起。在-HEAP-LFH-CONTEXT結(jié)構(gòu)中,BucketStats字段記錄了低碎片化堆子段在子段鏈表中的位置及每個子段中擁有處于激活狀態(tài)的Bucket數(shù)量,系統(tǒng)通過該字段定位子段的位置,判斷子段內(nèi)存中空閑內(nèi)存與已分配內(nèi)存的情況,據(jù)此對子段中的內(nèi)存進行釋放與分配。MemStats字段記錄了進程運行時,低碎片化堆中處于不同狀態(tài)的內(nèi)存大小,其中包括已申請內(nèi)存大小、已分配內(nèi)存大小、空閑內(nèi)存大小,進程運行時,該字段可以讓系統(tǒng)了解低碎片化堆內(nèi)部的內(nèi)存狀態(tài)。圖2顯示了低碎片化堆分配組件的結(jié)構(gòu)信息。
當(dāng)?shù)退槠褵o法分配堆塊時,段堆管理器會再次判斷堆塊的大小,如果在可變大小分配組件分配的內(nèi)存范圍時,則在可變大小分配組件分配的子段中分配堆塊,若堆塊分配的大小超過了子段的空閑內(nèi)存范圍,則會新建子段進行堆塊分配。在-HEAP-VS-CON TEXT結(jié)構(gòu)中,F(xiàn)reeCommittedUnits字段記錄了已分配內(nèi)存中已釋放的內(nèi)存大小。TotalCommittedUnits字段記錄了進程運行時,可變大小分配組件分配的內(nèi)存中處于已分配狀態(tài)的內(nèi)存大小。Lock字段是一個內(nèi)存結(jié)構(gòu),標記了已分配內(nèi)存的訪問請求狀態(tài),其中包括鎖住狀態(tài)、等待狀態(tài)、喚醒狀態(tài)、共享狀態(tài)等,該字段能夠讓系統(tǒng)知道內(nèi)存狀態(tài),從而限制線程對已分配內(nèi)存的操作。LockType字段只有3種取值:當(dāng)值為0時,表示系統(tǒng)以頁為單位鎖住內(nèi)存;當(dāng)值為1時,表示系統(tǒng)不是按頁為單位鎖住內(nèi)存;當(dāng)值為2時,表示系統(tǒng)鎖住整個可變大小分配的內(nèi)存。圖3顯示了可變大小分配組件的結(jié)構(gòu)信息。
在14295版本的段堆中,SegmentCount、SegmentListHead、FreePageRanges字段記錄了后端分配組件分配的內(nèi)存信息,而在15063版本、16299版本和17134版本的段堆中,只有SegContexts字段記錄了后端分配組件分配的內(nèi)存信息。在-HEAP-SEG-CONTEXT結(jié)構(gòu)中,F(xiàn)reePageRanges字段是一個紅黑樹結(jié)構(gòu),空閑內(nèi)存之間通過指針相互連接形成紅黑樹結(jié)構(gòu)。SegmentLock字段標記了已分配子段的訪問請求狀態(tài),其中包括鎖住狀態(tài)、等待狀態(tài)、喚醒狀態(tài)、共享狀態(tài)等。LfhContext字段和VsContext字段為結(jié)構(gòu)體指針,分別指向低碎片化堆組件和可變大小分配組件結(jié)構(gòu)。MaxAllocationSize字段的值是一個子段分配的最大內(nèi)存大小,如果分配的內(nèi)存超過這個值,就會再分配一個子段。圖4顯示了后端分配組件的結(jié)構(gòu)信息。
圖4 后端分配組件結(jié)構(gòu)信息
大塊分配組件分配的堆塊會產(chǎn)生較大的內(nèi)存碎片,在段堆中,為了提高內(nèi)存利用率,該組件很少使用。在圖1中,段堆結(jié)構(gòu)中有4個字段記錄的是大塊分配組件相關(guān)的信息。LargeReservedPages字段記錄的是大塊分配組件中申請的內(nèi)存大小,LargeCommittedPages字段記錄的是大塊分配組件分配內(nèi)存的大小,大塊分配組件分配堆塊的單元數(shù)據(jù)相互連接在一起形成紅黑樹結(jié)構(gòu),LargeAllocMetadata記錄的是單元數(shù)據(jù)紅黑樹的根地址[7]。
系統(tǒng)內(nèi)存池分布了很多內(nèi)核對象,其中就包括進程對象,池掃描技術(shù)可以定位內(nèi)核對象[8]。每個內(nèi)核對象頭部結(jié)構(gòu)都是-POOL-HEADER結(jié)構(gòu),該結(jié)構(gòu)中含有四字節(jié)標簽,對該標簽掃描可以定位需要分析的內(nèi)核對象[9-10]。池掃描技術(shù)是研發(fā)本文5個插件的前提技術(shù),當(dāng)定位進程對象時,就可以定位進程環(huán)境塊結(jié)構(gòu)中的ProcessHeaps字段[6]。
本文研發(fā)的功能插件,都是基于內(nèi)存取證框架實現(xiàn)的。內(nèi)存取證框架是內(nèi)存取證工具,也是取證技術(shù)的載體,它可以提取進程中的信息[11]。當(dāng)網(wǎng)絡(luò)犯罪發(fā)生時,它能獲取電腦、手機等設(shè)備的數(shù)字證據(jù)[12]。內(nèi)存取證框架可以從轉(zhuǎn)儲文件和硬件磁盤鏡像中解析休眠文件與頁面文件信息,通過這2個文件信息的對比能獲取隱藏進程的證據(jù)[13]而且還可以使用池掃描技術(shù)定位pico進程并解析pico進程內(nèi)部信息[14],使用可執(zhí)行頁面檢測算法遍歷內(nèi)存頁并恢復(fù)可執(zhí)行頁面,幫助調(diào)查人員識別代碼注入[15]等。
Volatility框架中含有各個Windows 10系統(tǒng)版本的配置文件,配置文件里面組合了許多vtype 描述信息,用來生成與單個統(tǒng)一編譯單元一致的信息。在對內(nèi)存對象進行分析的時候,這些信息可以讓Volatility框架對轉(zhuǎn)儲文件中的數(shù)據(jù)進行解析[16]。在15063版本、16299版本、17134版本(操作系統(tǒng)內(nèi)部版本)的配置文件中,沒有段堆及其組件的vtype描述信息,本文提取出段堆及其組件結(jié)構(gòu)信息后導(dǎo)入到配置文件中。
heapscan插件是基于池掃描技術(shù)實現(xiàn)的,當(dāng)識別出取證文件為Windows 10系統(tǒng)的轉(zhuǎn)儲文件時,使用池掃描技術(shù)掃描內(nèi)核空間并定位需要分析的進程,接著掃描進程堆空間,使用段堆的特征值定位段堆的位置。heapscan插件可以重現(xiàn)進程運行時,段堆的內(nèi)部信息。該插件可以輸出進程中所有段堆的子段數(shù)量、不同狀態(tài)內(nèi)存的大小和不同類型堆的數(shù)量。heapscan插件解析段堆時,執(zhí)行的步驟如下:
步驟1讀取配置文件信息和pid信息,確定轉(zhuǎn)儲文件的結(jié)構(gòu)定義和解析語言,加載地址空間;
步驟2使用池掃描技術(shù)掃描轉(zhuǎn)儲文件的物理地址空間,識別地址空間硬編碼,找到含有"proc"標記的位置,根據(jù)字段信息確定進程pid對應(yīng)的內(nèi)核對象;
步驟3根據(jù)進程內(nèi)部信息,定位PEB結(jié)構(gòu)位置;
現(xiàn)代木結(jié)構(gòu)建筑設(shè)計應(yīng)遵循模數(shù)協(xié)調(diào)原則,建立標準化結(jié)構(gòu)體系,優(yōu)化建筑空間尺寸[13]。項目建筑設(shè)計未嚴格遵循選材的模數(shù)要求,在項目圍護體系制作過程中,材料出現(xiàn)多次裁剪,造成了一定的浪費。通過項目實踐深切體會到,模數(shù)化是建筑工業(yè)化的基礎(chǔ),實現(xiàn)預(yù)制構(gòu)件和內(nèi)裝部品的標準化、系列化和通用化[9]13,有利于組織生產(chǎn)、提高效率、降低成本。
步驟4根據(jù)PEB對應(yīng)的vtype描述信息,定位到進程堆空間;
步驟5掃描進程堆,根據(jù)特征值區(qū)分NT堆和段堆,進而定位段堆的位置;
步驟6根據(jù)段堆的vtype描述信息,提取出段堆信息并顯示。
根據(jù)以上步驟,整理出如下heapscan插件實現(xiàn)的流程圖:
圖5 heapscan插件實現(xiàn)的流程圖
heapscan插件實現(xiàn)的偽代碼如下:
if profile is Windows10:
LoadAddressSpace()
if PoolScan(Address) is vaild:
Peb<-getPeb(proc)
AllHeap<-getHeaps()
forheapin AllHeap:
ifheapis SegmentHeap:
yield (0,[Address(heap),
str(Signature),
int(getTotalCommittedPages()),
int(getTotalReservedPages()),
str("Segmentheap"),
int(getSegmentCount())])
使用可變大小分配組件分配堆塊時,-HEAP-VS-CONTEXT結(jié)構(gòu)會時時跟蹤可變大小組件對堆塊的分配與釋放情況,那么showvscontext插件可以對內(nèi)存中-HEAP-VS-CONTEXT結(jié)構(gòu)進行定位并對內(nèi)部信息進行解析,該插件輸出的信息有子段的數(shù)量、空閑塊數(shù)量等。showvscontext插件解析可變大小分配組件時,執(zhí)行的步驟如下:
步驟1基于段堆結(jié)構(gòu)對應(yīng)的vtype描述信息,定位-HEAP-VS-CONTEXT結(jié)構(gòu)位置;
步驟2提取空閑塊根結(jié)點地址,掃描可變大小分配內(nèi)存中的空閑塊,統(tǒng)計空閑塊數(shù)量;
步驟3定位子段的位置,掃描所有的子段并定位子段的頭部結(jié)構(gòu),解析子段頭部信息,統(tǒng)計子段大小和子段數(shù)量,輸出解析后的信息。
實現(xiàn)showvscontext插件的偽代碼如下:
SegmentHeap<-getSegmentHeap()
VSContext<-getVSContext()
SubSeglist <-getSubsegmentList()
FreeChunkTreeRoot<-getFreeChunkTreeRoot()
FreeChunkNum<-getTotalFreeChunkNum()
forsegin SubSeglist:
Add(SubNum, getSubnum())
Add(Subsize,getSize())
yield (0,[Address(BackendCtx), int(getTotalCommittedUnits()),
int(getFreeCommittedUnits()), int(getSubsize()),
int(getSubnum()),int(FreeChunkNum)])
在段堆中,-HEAP-LFH-CONTEXT結(jié)構(gòu)含有低碎片堆內(nèi)部信息,該插件可以復(fù)現(xiàn)低碎片堆分配內(nèi)存的情況。showlfhcontext插件解析低碎片堆內(nèi)部信息時,執(zhí)行的步驟如下:
步驟1解析段堆結(jié)構(gòu),定位低碎片堆位置;
步驟2定位并掃描Buckets數(shù)組,判斷Bucket狀態(tài),提取出處于激活狀態(tài)的Bucket;
步驟3定位每個處于激活狀態(tài)的-HEAP-LFH-BUCKET結(jié)構(gòu),統(tǒng)計堆塊的數(shù)量、子段數(shù)量、處于激活狀態(tài)的Bucket數(shù)量;
步驟4通過-HEAP-LFH-AFFINITY-SLOT結(jié)構(gòu)定位到-HEAP-LFH-SUBSEGMENT-OWNER結(jié)構(gòu),統(tǒng)計低碎片化堆子段的數(shù)量,顯示提取后的信息。
實現(xiàn)showlfhcontext插件的偽代碼如下:
SegmentHeap<-getSegmentHeap()
fortaskin SegmentHeap:
buckets<-getBuckets()
forbin buckets:
ifb.Invalid exists:
Add(ActiviatedBucketsNum,getActiviatedBucketsNum)
Add(totalblock,getTotalBlockCount())
Add(totalsubseg,getTotalSubsegmentCount())
affslot<-getAffinitySlots()
foraffsin affslot:
AddSubsegmentCount()
大塊分配組件分配的內(nèi)存塊中存在著大塊單元數(shù)據(jù),大塊單元數(shù)據(jù)在內(nèi)存中呈現(xiàn)紅黑樹結(jié)構(gòu)。該插件以遍歷紅黑樹的方式定位大塊單元數(shù)據(jù)結(jié)構(gòu)中TreeNode字段,進而掃描所有的大塊分配組件分配的單元數(shù)據(jù)結(jié)構(gòu)。showlargeblockinfo插件解析大塊分配組件時,執(zhí)行的步驟如下:
步驟1獲取heapscan插件傳送過來的段堆對象,根據(jù)段堆結(jié)構(gòu)在內(nèi)存中的信息分布規(guī)律,定位大塊分配組件分配堆塊的根結(jié)點位置;
步驟2使用遍歷紅黑樹的方法,遍歷所有大塊的單元數(shù)據(jù),統(tǒng)計未被使用的內(nèi)存大小和已分配的內(nèi)存大??;
步驟3提取并輸出大塊單元數(shù)據(jù)信息。
showlargeblockinfo插件實現(xiàn)的偽代碼如下:
SegmentHeap<-getSegmentHeap()
fortaskin SegmentHeap:
for LargeAllocMeta in TraverseMetadata():
yield(0,[Address(getVirtualAddress()),
Address(getTreeNode().Left),
Address(getTreeNode().Right),
str(getUnusedBytes()),
str(getAllocatedPages())])
創(chuàng)建段堆后,在段堆中通過2個-HEAP-SEG-C ONTEXT結(jié)構(gòu)記錄后端分配組件分配內(nèi)存的情況,通過-SEGMENT-HEAP結(jié)構(gòu)中的SegContexts字段可以提取后端分配組件的內(nèi)部信息。showsegcontext插件解析后端分配組件時,執(zhí)行的步驟如下:
步驟1使用vtype描述信息解析段堆內(nèi)存對象,定位_HEAP_SEG_CONTEXT結(jié)構(gòu)位置;
步驟2使用_HEAP_SEG_CONTEXT結(jié)構(gòu)的vtype描述信息解析后端分配組件內(nèi)存對象;
步驟3定位子段位置,掃描各個子段,統(tǒng)計子段數(shù)量和子段中已分配內(nèi)存頁的大??;
步驟4定位頁范圍描述結(jié)構(gòu)起始位置,使用掃描紅黑樹的方法掃描-HEAP-PAGE-SEGMENT結(jié)構(gòu),統(tǒng)計空閑頁面數(shù)量,顯示解析結(jié)果。
showsegcontext插件實現(xiàn)的偽代碼如下:
SegmentHeap<-getSegmentHeap()
VSContext<-getSegContext()
SegList <-getSegmentList()
FreePageRanges<-getFreePageRangesTreeRoot()
forsegin SegList:
Add(SubNum, getSubNum())
Add(CommPageCount, getCommPageCount())
for FreeTree in Traverse(FreePageRanges):
Add(PageCount,getPageCount())
Add(UnusedBytes,getUnusedBytes())
yield (0,[Address(getHeap()), int(SubNum()),
int(CommittedPageCount()), int(PageCount),
int(UnusedBytes)])
測試分為信息提取測試和堆溢出檢測測試兩部分,信息提取測試是為了驗證插件能否提取出轉(zhuǎn)儲文件中段堆的信息,堆溢出測試是為了驗證插件是否能檢測出堆溢出攻擊。實驗環(huán)境如下:
主機操作系統(tǒng)為Windows 10 version 1903 64位,CPU為2.20 GHz,內(nèi)存大小8 G,硬盤容量2 T。
選取calculator進程和svchost進程作為實驗對象,calculator進程為系統(tǒng)自帶程序且屬于用戶進程,svchost進程是服務(wù)主程序,該程序在系統(tǒng)運行中起到非常重要的作用。
在15063版本、16299版本、17134版本的Windows 10系統(tǒng)中運行calculator程序,隨后分別對系統(tǒng)內(nèi)存進行轉(zhuǎn)儲生成轉(zhuǎn)儲文件,使用本文研發(fā)好的5個插件分別提取calculator進程和svchost進程中段堆的信息。
4.1.1 heapscan插件測試
本文研發(fā)的5個插件中,heapscan插件最為關(guān)鍵,它可以定位段堆的位置并為其他插件傳遞段堆內(nèi)存對象。heapscan插件根據(jù)段堆結(jié)構(gòu)的vtype描述信息解析段堆內(nèi)部數(shù)據(jù)。在程序中,使用HeapAlloc、HeapCreate等函數(shù)可以向內(nèi)存中申請連續(xù)的內(nèi)存區(qū)域,當(dāng)對這塊內(nèi)存區(qū)域進行初始化并使用時,這塊內(nèi)存就處于已分配狀態(tài)。表1記錄了heapscan插件提取的數(shù)據(jù)。
表1 heapscan插件提取的數(shù)據(jù)
4.1.2 showvscontext插件測試
該插件根據(jù)段堆結(jié)構(gòu)信息中的VsContext字段定位可變大小組件結(jié)構(gòu)。當(dāng)可變大小分配組件釋放堆塊時,釋放的堆塊會被視為結(jié)點插入到由空閑塊組成的紅黑樹中,遍歷空閑塊紅黑樹可以定位每個空閑塊,進而判斷空閑堆塊有沒有發(fā)生堆溢出。當(dāng)定位到可變大小分配組件子段的位置時,加上偏移就可以定位處于分配狀態(tài)的堆塊,再把堆塊的大小作為偏移就可以定位每個堆塊,進而判斷已分配堆塊有沒有發(fā)生堆溢出。表2記錄了showvscontext插件提取的信息。
表2 showvscontext插件提取的數(shù)據(jù)
4.1.3 showlfhcontext插件測試
低碎片化堆分配組件是把分配的內(nèi)存放到Buckets數(shù)組中,當(dāng)Bucket處于激活狀態(tài)時,表明該Bucket可以分配內(nèi)存。遍歷子段時,通過堆塊的偏移就能定位到已分配堆塊的位置,根據(jù)填充數(shù)據(jù)有沒有被覆蓋可以判斷堆塊有沒有發(fā)生堆溢出。表3記錄了showlfhcontext插件提取的數(shù)據(jù)。
表3 showlfhcontext插件提取的數(shù)據(jù)
4.1.4 showlargeblockinfo插件測試
大塊分配組件不同于其他組件,根據(jù)它的結(jié)構(gòu)定位不到堆塊的位置,只能定位堆塊的單元數(shù)據(jù)結(jié)構(gòu),通過該結(jié)構(gòu),可以統(tǒng)計大塊組件申請的內(nèi)存中處于分配狀態(tài)的內(nèi)存頁數(shù)量和未被進程使用的內(nèi)存頁數(shù)量。表4記錄了showlargeblockinfo插件提取的數(shù)據(jù)。
表4 showlargeblockinfo插件提取的數(shù)據(jù)
4.1.5 showsegcontext插件測試
由于可變大小分配組件和低碎片化堆分配組件都依賴于后端分配組件實現(xiàn)內(nèi)存分配,因此后端分配組件子段數(shù)量為段堆所有的子段數(shù)量。遍歷空閑頁描述符可以知道后端分配組件申請分配的內(nèi)存中未被進程使用的和已被進程釋放的內(nèi)存頁數(shù)量。表5記錄了showsegcontext插件提取的數(shù)據(jù)。
表5 showsegcontext插件提取的數(shù)據(jù)
實驗結(jié)果表明本文研發(fā)的插件能成功地提取出不同Windows 10版本的段堆信息。從表中的信息可以看出,使用段堆及其組件結(jié)構(gòu)的vtype描述信息可以成功解析段堆內(nèi)部數(shù)據(jù)。隨著Windows 10系統(tǒng)的更新,段堆及其組件結(jié)構(gòu)中字段位置發(fā)生了變化,但這并不影響對段堆信息的提取,因為當(dāng)使用vtype描述信息解析段堆時,根據(jù)信息名稱就能進行解析,因此本文研發(fā)的插件具有較強的兼容性。
在段堆內(nèi)部存在較完善的安全機制,我們通過實驗發(fā)現(xiàn),在段堆中不可能通過覆蓋堆塊頭中的前后指針實現(xiàn)DWORD SHOOT攻擊。我們分析發(fā)現(xiàn)通過覆蓋虛表指針或者通過修改堆塊內(nèi)部數(shù)據(jù)分配大小的方式,泄漏虛表指針可以產(chǎn)生堆溢出攻擊。
當(dāng)用插件提取段堆信息時,插件會檢測段堆中是否出現(xiàn)堆溢出攻擊。堆塊頭部結(jié)構(gòu)含有堆塊信息,通過核對頭部信息的方法就可以檢測出異常堆塊。當(dāng)定位到異常堆塊時,使用識別地址的正則表達式檢測堆塊中有無虛表地址,若有則說明發(fā)生了虛表地址覆蓋攻擊,若沒有,則通過識別堆塊中的填充數(shù)據(jù)或堆塊塊頭的方式定位到下個堆塊,檢測下個堆塊中有無虛表地址,有的話,則說明發(fā)生了虛表地址泄漏攻擊。
4.2.1 虛表地址泄漏攻擊檢測測試
堆塊分配后,可以在堆塊中分配標識內(nèi)存大小的數(shù)據(jù)類型,通過堆溢出,修改該數(shù)據(jù)類型大小,就可以泄漏堆塊信息。如果在堆塊頭被覆蓋堆塊的相鄰堆塊中存放了C++對象,那么通過虛表指針泄漏的方式可以調(diào)用惡意虛函數(shù)。以CVE-2020-0787漏洞為例進行測試,該漏洞為任意文件移動漏洞。在所有的Windows 10系統(tǒng)中,利用exploit程序泄漏虛表地址,可以導(dǎo)致惡意虛函數(shù)通過符號鏈接重定向文件移動函數(shù),把惡意目錄中的提權(quán)dll加載進System32文件夾中,當(dāng)加載提權(quán)dll時,就能獲得系統(tǒng)管理員權(quán)限。測試使用的關(guān)鍵exploit程序如下所示:
DoVftableFunc()
{
CreateFileAndWriteFile (SourceFilePath, fileContent......);
CreateGroupAndJob(group, job ,……);
InitFileInfo();
AddFiles(1, &fileInfoArray);
hRes = FindFirstFile(SearchPath, &FindData);
StringCchCat(BitsFileName,x, FindData.cFileN);
oplock =CreateLock(BitsTempFilePath, ......);
CreateSymlink(nullptr, LinkName, LinkTarget);
CompleteJob();
}
void TriggerHeapOverFlow
{
while(i a[i]= HeapAllocAndInit(Hheap,0,size); HeapFree(Hheap,x,a[k]); While(i bStrings[i] = SysAllocString(LongStr); HeapFree(Hheap,x,a[k+1]); While(i vector memcpy(a[k-1],ShellCode,sizeof(ShellCode)); DoVftableFunc= ReadVftable (); DoVftableFunc(); } 運行exploit程序后,對系統(tǒng)內(nèi)存進行轉(zhuǎn)儲并用本文研發(fā)的插件進行信息提取。圖6顯示了段堆中的惡意痕跡信息,從中可以看出可變大小分配組件分配堆塊的頭部和填充數(shù)據(jù)都被溢出數(shù)據(jù)覆蓋,增大了BSTR型變量的數(shù)據(jù)長度,導(dǎo)致了虛表地址泄漏。 圖6 虛表地址泄漏攻擊信息 4.2.2 虛表地址覆蓋攻擊檢測測試 通過堆溢出可以覆蓋C++對象的虛表地址,當(dāng)調(diào)用虛函數(shù)時,會查找偽造的虛表并調(diào)用其中的惡意虛函數(shù)。以CVE-2020-0796漏洞為例進行測試,該漏洞是SMB遠程代碼執(zhí)行漏洞。在1903版本和1909版本的Windows 10系統(tǒng)中,利用exploit程序覆蓋虛表地址并通過惡意虛函數(shù)讓SMB以不合理長度解壓數(shù)據(jù)包,可以導(dǎo)致權(quán)限提升,進而攻擊SMB服務(wù)器執(zhí)行惡意代碼。測試使用的關(guān)鍵exploit程序如下所示: Class Object{ virtual void MaliciousCode() { const uint8-t buf[ ] = { ........ 0xFF, 0xFF, 0xFF, 0xFF, //異常原始未壓縮數(shù)據(jù)長度 0x02, 0x00, //壓縮算法 ........}; send(sock, packet, len, 0)); hProc =getProcessHandleByName(ProcessName); lpMem = VirtualAllocEx(hProc,….); WriteProcessMemory(hProc, lpMem, shellcode,…); CreateRemoteThread(hProc,….); }} void TriggerHeapOverFlow { While(i a[i]=HeapAllocAndInit(Hheap,0,size); HeapFree(Hheap,x,a[k]); While(i vector memcpy(a[k-1],ShellCode,sizeof(ShellCode)); v[0].at(0)->MaliciousCode(); } 同樣使用本文研發(fā)的插件對轉(zhuǎn)儲文件進行信息提取,根據(jù)系統(tǒng)的內(nèi)部版本,我們使用18362版本的Windows 10配置文件解析轉(zhuǎn)儲文件。圖7顯示了段堆中的惡意信息,從中可以看出低碎片堆組件分配堆塊的填充數(shù)據(jù)和堆塊中的虛表地址被溢出數(shù)據(jù)覆蓋。 圖7 虛表地址覆蓋攻擊信息 在上述的測試中,我們使用了相似性匹配的快速檢測方法檢驗虛函數(shù)在內(nèi)存中的shellcode,都發(fā)現(xiàn)了惡意shellcode,說明了泄漏的虛表和覆蓋后偽造的虛表中都有惡意虛函數(shù),驗證了本插件能檢測出針對段堆的堆溢出攻擊。 為了提取出段堆中的信息,本文分析了段堆結(jié)構(gòu)中含有的字段并結(jié)合池掃描技術(shù)和字段在結(jié)構(gòu)信息中的偏移量,設(shè)計出獲取段堆及其組件內(nèi)部信息的算法并在內(nèi)存取證框架中研發(fā)出功能插件,這些插件可以解析Windows 10系統(tǒng)中段堆內(nèi)部含有的信息。實驗結(jié)果表明本文提出的方法可以重現(xiàn)進程運行時段堆及其內(nèi)部組件在內(nèi)存中的分配情況,進而反映進程中段堆內(nèi)存信息,這些信息可以為系統(tǒng)遭到網(wǎng)絡(luò)攻擊或者網(wǎng)絡(luò)犯罪提供取證依據(jù)。5 結(jié) 論