翟繼強, 陳攀, 徐曉, 楊海陸
(哈爾濱理工大學 計算機科學與技術學院, 黑龍江 哈爾濱 150080)
近年來,黑客經常通過網絡傳播惡意程序,當計算機染上惡意程序時,計算機內存會留下惡意活動痕跡[1-2],這時通過內存取證技術就可以捕獲到這些痕跡并落實網絡犯罪的數字證據。段堆中含有進程運行時生成的重要信息,提取出段堆中的信息可以了解進程的運行情況,因此對段堆的內存取證研究在信息安全的防護領域意義重大。
目前,對堆的內存取證研究根據操作系統的不同可分為基于Linux系統的堆取證研究、基于安卓環境的堆取證研究、基于Windows 7系統的堆取證研究。在Linux系統中, Block研究了glibc庫創建的堆結構,使用了堆結構定位及關鍵字段偏移法復現堆內部信息,解決了標簽搜索不準確問題[3]。在安卓系統中,張俊芙研究出了3種提取堆信息的方法,分別為:把目標值做為搜索對象進行搜索;使用相關對象定位引用對象和同類對象;使用目標數據猜測法搜索目標數據[4]。在Windows 7系統中, Cohen研究了NT堆中低碎片化堆的創建并且使用硬件PTE解析算法,復現NT堆中無效頁面信息[5]。
然而,上述的堆取證研究并沒有針對Windows 10系統中的段堆,通過現有的內存取證技術不能重現段堆信息,進而不能獲取針對段堆的堆溢出攻擊的取證信息。同時段堆尚未在MSDN文檔上公開而且目前對段堆結構的研究還并不充分,因此需要進一步研究段堆結構。為了彌補這些缺陷,本文研究了多個版本的Windows 10段堆并且提出一種利用池掃描技術識別內核對象,再結合字段信息偏移定位的方法,提取段堆內部信息。經過測試,該方法能成功復現段堆內部信息,這些信息能幫助調查人員獲取堆溢出攻擊的數字證據。本文研究的主要內容如下:
1) 研究了多個Windows 10版本的段堆及其組件結構中字段的作用;
2) 根據段堆特征值定位段堆的位置,并提取出段堆內部信息;
3) 提取出大塊分配組件分配堆塊的元數據信息;
4) 定位可變大小分配組件結構和低碎片化堆分配組件結構的位置并且提取出它們分配的內存信息;
5) 使用本文研發出的插件檢測堆溢出攻擊。
當創建新的進程時,Windows內核會在內核內存的非分頁池中創建-EPROCESS結構,通過該結構的內部信息可以定位用戶模式下進程環境塊的位置。進程環境塊存放的是進程信息,其中就包括進程堆信息、當前的工作目錄、環境變量、命令行參數等[6]。
Windows程序管理器創建進程時,內核會對進程分配4GB的虛擬內存,并創建和初始化堆管理器,不同的堆管理器對應著不同的特征值。Windows 10內核分配堆塊時,根據堆的特征值進行相應堆塊的分配。Windows 10內核有較好的安全機制,分配堆塊的過程中使用臨界區和原子操作函數實現線程同步,確保堆塊分配成功。堆塊的分配依賴于內部的4個組件,分配的過程中根據堆塊大小選擇合適的組件進行分配。
段堆是特殊的內存管理器,它只存在于Windows 10系統中。段堆及其組件每次在分配內存之前會預先申請一塊內存區域,然后再從內存區域中分配進程請求的內存。在可變大小分配組件分配的內存中已申請的內存大部分已被分配,只留有少數空閑內存未被使用,有些段堆中可能不含有該組件分配的子段。低碎片化堆組件分配的內存相比其他組件分配的內存要小很多,而且基本上不會產生空閑塊。大塊分配組件中未使用的內存相比其他組件要大很多,因此為了減少內存浪費,大塊分配組件在段堆中很少使用。
段堆的結構和Windows系統中其他堆的結構類似,都是由字段偏移量、字段、字段數據類型組成。Windows 10有許多的版本,不同版本的Windows 10系統含有不同的段堆結構。隨著段堆結構的更新,段堆的安全機制在不斷完善并且內存分配效率也在不斷提高。圖1顯示了段堆的結構信息(17134版本),這些信息記錄了組件的偏移量、不同內存狀態的頁面數量等。段堆同內核對象一樣,在內存中存在獨屬的結構體,結構體中含有段堆的信息。
由于進程地址空間的對齊粒度為64 kB,因此內存管理器分配的最小內存為64 kB,當要分配小于64 kB的內存時,會產生較大的內存碎片,而堆管理器分配的內存可以小于64 kB,因此可以減少內存浪費,提高內存利用率。進程開始運行時,會創建一個默認堆,使用HeapCreate函數可以創建額外的私有堆,在Windows 10系統中,私有堆包括段堆和NT堆。經分析發現,它們之間存在著較大的差異,段堆和NT堆之間存在的差異如下所示:
1) 在內存分配頻繁的情況下,NT堆分配內存的速度要比段堆快,因為段堆在分配內存時需要經過更多的操作步驟;
2) 段堆具有更好的安全機制,在段堆中,對元數據的訪問是互斥進行的,在同一時刻,只允許一個線程對其進行操作,而實際數據不是,因此段堆中的元數據獨立于實際數據。然而在NT堆中,元數據不是互斥訪問,于是元數據和實際數據混在一起;
3) 段堆使用4個組件對堆塊進行分配,而NT堆只依賴于2個組件對堆塊進行分配。段堆細化了堆塊的分配范圍,因此具有更高的內存利用率;
4) NT堆具有更完善的內存管理機制,它能夠更全面地定位跟蹤NT堆中內存釋放與分配情況,因此NT堆結構中含有更全面的信息;
5) 段堆是新出現的堆管理器,內部的管理機制需要不斷完善,段堆的結構會隨著Windows 10系統的更新而升級。由于NT堆的內存管理機制已趨于成熟,那么在后期,NT堆將不再更新;
6) 段堆是Windows 10出現后引進的,因此段堆只出現在Windows 10系統中,而NT堆存在于所有的Windows系統中。
段堆對內存的管理依賴于內部的4個組件,組件主要負責對內存的釋放與分配[7]。不同的組件分配不同的內存大小:低碎片化堆分配組件分配不大于16 kB的內存區域;可變大小分配組件從段堆中請求分配的內存大小不大于128 kB;后端分配組件分配內存塊的大小介于128 kB到508 kB之間;大塊分配組件分配內存塊的大小大于508 kB[7]。經分析發現,隨著Windows 10系統的更新升級,段堆的組件結構也相應地發生了變化,這些變化讓系統更準確地檢測內存分布情況,從而更合理地分配堆內存,提高內存利用率。Mark研究了14295版本的段堆[7],但他分析的結構字段并不全面,本文補充分析了14295版本的段堆。在深入研究15063版本、16299版本和17134版本的段堆之后發現這3個版本相比于14295版本具有更好的安全性并且字段的位置及數量也發生了改變。
在段堆分配堆塊時,優先給低碎片堆組件分配,若能進行分配,則遍歷Buckets數組找到大小合適并處于激活狀態的Bucket,未能找到則分配新的Bucket。該組件分配的堆塊具有最高的內存利用率,幾乎不產生內存碎片。堆塊是從子段中進行分配,低碎片化堆中的子段以鏈表的形式串連在一起。在-HEAP-LFH-CONTEXT結構中,BucketStats字段記錄了低碎片化堆子段在子段鏈表中的位置及每個子段中擁有處于激活狀態的Bucket數量,系統通過該字段定位子段的位置,判斷子段內存中空閑內存與已分配內存的情況,據此對子段中的內存進行釋放與分配。MemStats字段記錄了進程運行時,低碎片化堆中處于不同狀態的內存大小,其中包括已申請內存大小、已分配內存大小、空閑內存大小,進程運行時,該字段可以讓系統了解低碎片化堆內部的內存狀態。圖2顯示了低碎片化堆分配組件的結構信息。
當低碎片化堆無法分配堆塊時,段堆管理器會再次判斷堆塊的大小,如果在可變大小分配組件分配的內存范圍時,則在可變大小分配組件分配的子段中分配堆塊,若堆塊分配的大小超過了子段的空閑內存范圍,則會新建子段進行堆塊分配。在-HEAP-VS-CON TEXT結構中,FreeCommittedUnits字段記錄了已分配內存中已釋放的內存大小。TotalCommittedUnits字段記錄了進程運行時,可變大小分配組件分配的內存中處于已分配狀態的內存大小。Lock字段是一個內存結構,標記了已分配內存的訪問請求狀態,其中包括鎖住狀態、等待狀態、喚醒狀態、共享狀態等,該字段能夠讓系統知道內存狀態,從而限制線程對已分配內存的操作。LockType字段只有3種取值:當值為0時,表示系統以頁為單位鎖住內存;當值為1時,表示系統不是按頁為單位鎖住內存;當值為2時,表示系統鎖住整個可變大小分配的內存。圖3顯示了可變大小分配組件的結構信息。
在14295版本的段堆中,SegmentCount、SegmentListHead、FreePageRanges字段記錄了后端分配組件分配的內存信息,而在15063版本、16299版本和17134版本的段堆中,只有SegContexts字段記錄了后端分配組件分配的內存信息。在-HEAP-SEG-CONTEXT結構中,FreePageRanges字段是一個紅黑樹結構,空閑內存之間通過指針相互連接形成紅黑樹結構。SegmentLock字段標記了已分配子段的訪問請求狀態,其中包括鎖住狀態、等待狀態、喚醒狀態、共享狀態等。LfhContext字段和VsContext字段為結構體指針,分別指向低碎片化堆組件和可變大小分配組件結構。MaxAllocationSize字段的值是一個子段分配的最大內存大小,如果分配的內存超過這個值,就會再分配一個子段。圖4顯示了后端分配組件的結構信息。

圖4 后端分配組件結構信息
大塊分配組件分配的堆塊會產生較大的內存碎片,在段堆中,為了提高內存利用率,該組件很少使用。在圖1中,段堆結構中有4個字段記錄的是大塊分配組件相關的信息。LargeReservedPages字段記錄的是大塊分配組件中申請的內存大小,LargeCommittedPages字段記錄的是大塊分配組件分配內存的大小,大塊分配組件分配堆塊的單元數據相互連接在一起形成紅黑樹結構,LargeAllocMetadata記錄的是單元數據紅黑樹的根地址[7]。
系統內存池分布了很多內核對象,其中就包括進程對象,池掃描技術可以定位內核對象[8]。每個內核對象頭部結構都是-POOL-HEADER結構,該結構中含有四字節標簽,對該標簽掃描可以定位需要分析的內核對象[9-10]。池掃描技術是研發本文5個插件的前提技術,當定位進程對象時,就可以定位進程環境塊結構中的ProcessHeaps字段[6]。
本文研發的功能插件,都是基于內存取證框架實現的。內存取證框架是內存取證工具,也是取證技術的載體,它可以提取進程中的信息[11]。當網絡犯罪發生時,它能獲取電腦、手機等設備的數字證據[12]。內存取證框架可以從轉儲文件和硬件磁盤鏡像中解析休眠文件與頁面文件信息,通過這2個文件信息的對比能獲取隱藏進程的證據[13]而且還可以使用池掃描技術定位pico進程并解析pico進程內部信息[14],使用可執行頁面檢測算法遍歷內存頁并恢復可執行頁面,幫助調查人員識別代碼注入[15]等。
Volatility框架中含有各個Windows 10系統版本的配置文件,配置文件里面組合了許多vtype 描述信息,用來生成與單個統一編譯單元一致的信息。在對內存對象進行分析的時候,這些信息可以讓Volatility框架對轉儲文件中的數據進行解析[16]。在15063版本、16299版本、17134版本(操作系統內部版本)的配置文件中,沒有段堆及其組件的vtype描述信息,本文提取出段堆及其組件結構信息后導入到配置文件中。
heapscan插件是基于池掃描技術實現的,當識別出取證文件為Windows 10系統的轉儲文件時,使用池掃描技術掃描內核空間并定位需要分析的進程,接著掃描進程堆空間,使用段堆的特征值定位段堆的位置。heapscan插件可以重現進程運行時,段堆的內部信息。該插件可以輸出進程中所有段堆的子段數量、不同狀態內存的大小和不同類型堆的數量。heapscan插件解析段堆時,執行的步驟如下:
步驟1讀取配置文件信息和pid信息,確定轉儲文件的結構定義和解析語言,加載地址空間;
步驟2使用池掃描技術掃描轉儲文件的物理地址空間,識別地址空間硬編碼,找到含有"proc"標記的位置,根據字段信息確定進程pid對應的內核對象;
步驟3根據進程內部信息,定位PEB結構位置;
現代木結構建筑設計應遵循模數協調原則,建立標準化結構體系,優化建筑空間尺寸[13]。項目建筑設計未嚴格遵循選材的模數要求,在項目圍護體系制作過程中,材料出現多次裁剪,造成了一定的浪費。通過項目實踐深切體會到,模數化是建筑工業化的基礎,實現預制構件和內裝部品的標準化、系列化和通用化[9]13,有利于組織生產、提高效率、降低成本。
步驟4根據PEB對應的vtype描述信息,定位到進程堆空間;
步驟5掃描進程堆,根據特征值區分NT堆和段堆,進而定位段堆的位置;
步驟6根據段堆的vtype描述信息,提取出段堆信息并顯示。
根據以上步驟,整理出如下heapscan插件實現的流程圖:

圖5 heapscan插件實現的流程圖
heapscan插件實現的偽代碼如下:
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結構會時時跟蹤可變大小組件對堆塊的分配與釋放情況,那么showvscontext插件可以對內存中-HEAP-VS-CONTEXT結構進行定位并對內部信息進行解析,該插件輸出的信息有子段的數量、空閑塊數量等。showvscontext插件解析可變大小分配組件時,執行的步驟如下:
步驟1基于段堆結構對應的vtype描述信息,定位-HEAP-VS-CONTEXT結構位置;
步驟2提取空閑塊根結點地址,掃描可變大小分配內存中的空閑塊,統計空閑塊數量;
步驟3定位子段的位置,掃描所有的子段并定位子段的頭部結構,解析子段頭部信息,統計子段大小和子段數量,輸出解析后的信息。
實現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結構含有低碎片堆內部信息,該插件可以復現低碎片堆分配內存的情況。showlfhcontext插件解析低碎片堆內部信息時,執行的步驟如下:
步驟1解析段堆結構,定位低碎片堆位置;
步驟2定位并掃描Buckets數組,判斷Bucket狀態,提取出處于激活狀態的Bucket;
步驟3定位每個處于激活狀態的-HEAP-LFH-BUCKET結構,統計堆塊的數量、子段數量、處于激活狀態的Bucket數量;
步驟4通過-HEAP-LFH-AFFINITY-SLOT結構定位到-HEAP-LFH-SUBSEGMENT-OWNER結構,統計低碎片化堆子段的數量,顯示提取后的信息。
實現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()
大塊分配組件分配的內存塊中存在著大塊單元數據,大塊單元數據在內存中呈現紅黑樹結構。該插件以遍歷紅黑樹的方式定位大塊單元數據結構中TreeNode字段,進而掃描所有的大塊分配組件分配的單元數據結構。showlargeblockinfo插件解析大塊分配組件時,執行的步驟如下:
步驟1獲取heapscan插件傳送過來的段堆對象,根據段堆結構在內存中的信息分布規律,定位大塊分配組件分配堆塊的根結點位置;
步驟2使用遍歷紅黑樹的方法,遍歷所有大塊的單元數據,統計未被使用的內存大小和已分配的內存大小;
步驟3提取并輸出大塊單元數據信息。
showlargeblockinfo插件實現的偽代碼如下:
SegmentHeap<-getSegmentHeap()
fortaskin SegmentHeap:
for LargeAllocMeta in TraverseMetadata():
yield(0,[Address(getVirtualAddress()),
Address(getTreeNode().Left),
Address(getTreeNode().Right),
str(getUnusedBytes()),
str(getAllocatedPages())])
創建段堆后,在段堆中通過2個-HEAP-SEG-C ONTEXT結構記錄后端分配組件分配內存的情況,通過-SEGMENT-HEAP結構中的SegContexts字段可以提取后端分配組件的內部信息。showsegcontext插件解析后端分配組件時,執行的步驟如下:
步驟1使用vtype描述信息解析段堆內存對象,定位_HEAP_SEG_CONTEXT結構位置;
步驟2使用_HEAP_SEG_CONTEXT結構的vtype描述信息解析后端分配組件內存對象;
步驟3定位子段位置,掃描各個子段,統計子段數量和子段中已分配內存頁的大小;
步驟4定位頁范圍描述結構起始位置,使用掃描紅黑樹的方法掃描-HEAP-PAGE-SEGMENT結構,統計空閑頁面數量,顯示解析結果。
showsegcontext插件實現的偽代碼如下:
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)])
測試分為信息提取測試和堆溢出檢測測試兩部分,信息提取測試是為了驗證插件能否提取出轉儲文件中段堆的信息,堆溢出測試是為了驗證插件是否能檢測出堆溢出攻擊。實驗環境如下:
主機操作系統為Windows 10 version 1903 64位,CPU為2.20 GHz,內存大小8 G,硬盤容量2 T。
選取calculator進程和svchost進程作為實驗對象,calculator進程為系統自帶程序且屬于用戶進程,svchost進程是服務主程序,該程序在系統運行中起到非常重要的作用。
在15063版本、16299版本、17134版本的Windows 10系統中運行calculator程序,隨后分別對系統內存進行轉儲生成轉儲文件,使用本文研發好的5個插件分別提取calculator進程和svchost進程中段堆的信息。
4.1.1 heapscan插件測試
本文研發的5個插件中,heapscan插件最為關鍵,它可以定位段堆的位置并為其他插件傳遞段堆內存對象。heapscan插件根據段堆結構的vtype描述信息解析段堆內部數據。在程序中,使用HeapAlloc、HeapCreate等函數可以向內存中申請連續的內存區域,當對這塊內存區域進行初始化并使用時,這塊內存就處于已分配狀態。表1記錄了heapscan插件提取的數據。

表1 heapscan插件提取的數據
4.1.2 showvscontext插件測試
該插件根據段堆結構信息中的VsContext字段定位可變大小組件結構。當可變大小分配組件釋放堆塊時,釋放的堆塊會被視為結點插入到由空閑塊組成的紅黑樹中,遍歷空閑塊紅黑樹可以定位每個空閑塊,進而判斷空閑堆塊有沒有發生堆溢出。當定位到可變大小分配組件子段的位置時,加上偏移就可以定位處于分配狀態的堆塊,再把堆塊的大小作為偏移就可以定位每個堆塊,進而判斷已分配堆塊有沒有發生堆溢出。表2記錄了showvscontext插件提取的信息。

表2 showvscontext插件提取的數據
4.1.3 showlfhcontext插件測試
低碎片化堆分配組件是把分配的內存放到Buckets數組中,當Bucket處于激活狀態時,表明該Bucket可以分配內存。遍歷子段時,通過堆塊的偏移就能定位到已分配堆塊的位置,根據填充數據有沒有被覆蓋可以判斷堆塊有沒有發生堆溢出。表3記錄了showlfhcontext插件提取的數據。

表3 showlfhcontext插件提取的數據
4.1.4 showlargeblockinfo插件測試
大塊分配組件不同于其他組件,根據它的結構定位不到堆塊的位置,只能定位堆塊的單元數據結構,通過該結構,可以統計大塊組件申請的內存中處于分配狀態的內存頁數量和未被進程使用的內存頁數量。表4記錄了showlargeblockinfo插件提取的數據。

表4 showlargeblockinfo插件提取的數據
4.1.5 showsegcontext插件測試
由于可變大小分配組件和低碎片化堆分配組件都依賴于后端分配組件實現內存分配,因此后端分配組件子段數量為段堆所有的子段數量。遍歷空閑頁描述符可以知道后端分配組件申請分配的內存中未被進程使用的和已被進程釋放的內存頁數量。表5記錄了showsegcontext插件提取的數據。

表5 showsegcontext插件提取的數據
實驗結果表明本文研發的插件能成功地提取出不同Windows 10版本的段堆信息。從表中的信息可以看出,使用段堆及其組件結構的vtype描述信息可以成功解析段堆內部數據。隨著Windows 10系統的更新,段堆及其組件結構中字段位置發生了變化,但這并不影響對段堆信息的提取,因為當使用vtype描述信息解析段堆時,根據信息名稱就能進行解析,因此本文研發的插件具有較強的兼容性。
在段堆內部存在較完善的安全機制,我們通過實驗發現,在段堆中不可能通過覆蓋堆塊頭中的前后指針實現DWORD SHOOT攻擊。我們分析發現通過覆蓋虛表指針或者通過修改堆塊內部數據分配大小的方式,泄漏虛表指針可以產生堆溢出攻擊。
當用插件提取段堆信息時,插件會檢測段堆中是否出現堆溢出攻擊。堆塊頭部結構含有堆塊信息,通過核對頭部信息的方法就可以檢測出異常堆塊。當定位到異常堆塊時,使用識別地址的正則表達式檢測堆塊中有無虛表地址,若有則說明發生了虛表地址覆蓋攻擊,若沒有,則通過識別堆塊中的填充數據或堆塊塊頭的方式定位到下個堆塊,檢測下個堆塊中有無虛表地址,有的話,則說明發生了虛表地址泄漏攻擊。
4.2.1 虛表地址泄漏攻擊檢測測試
堆塊分配后,可以在堆塊中分配標識內存大小的數據類型,通過堆溢出,修改該數據類型大小,就可以泄漏堆塊信息。如果在堆塊頭被覆蓋堆塊的相鄰堆塊中存放了C++對象,那么通過虛表指針泄漏的方式可以調用惡意虛函數。以CVE-2020-0787漏洞為例進行測試,該漏洞為任意文件移動漏洞。在所有的Windows 10系統中,利用exploit程序泄漏虛表地址,可以導致惡意虛函數通過符號鏈接重定向文件移動函數,把惡意目錄中的提權dll加載進System32文件夾中,當加載提權dll時,就能獲得系統管理員權限。測試使用的關鍵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程序后,對系統內存進行轉儲并用本文研發的插件進行信息提取。圖6顯示了段堆中的惡意痕跡信息,從中可以看出可變大小分配組件分配堆塊的頭部和填充數據都被溢出數據覆蓋,增大了BSTR型變量的數據長度,導致了虛表地址泄漏。 圖6 虛表地址泄漏攻擊信息 4.2.2 虛表地址覆蓋攻擊檢測測試 通過堆溢出可以覆蓋C++對象的虛表地址,當調用虛函數時,會查找偽造的虛表并調用其中的惡意虛函數。以CVE-2020-0796漏洞為例進行測試,該漏洞是SMB遠程代碼執行漏洞。在1903版本和1909版本的Windows 10系統中,利用exploit程序覆蓋虛表地址并通過惡意虛函數讓SMB以不合理長度解壓數據包,可以導致權限提升,進而攻擊SMB服務器執行惡意代碼。測試使用的關鍵exploit程序如下所示: Class Object{ virtual void MaliciousCode() { const uint8-t buf[ ] = { ........ 0xFF, 0xFF, 0xFF, 0xFF, //異常原始未壓縮數據長度 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(); } 同樣使用本文研發的插件對轉儲文件進行信息提取,根據系統的內部版本,我們使用18362版本的Windows 10配置文件解析轉儲文件。圖7顯示了段堆中的惡意信息,從中可以看出低碎片堆組件分配堆塊的填充數據和堆塊中的虛表地址被溢出數據覆蓋。 圖7 虛表地址覆蓋攻擊信息 在上述的測試中,我們使用了相似性匹配的快速檢測方法檢驗虛函數在內存中的shellcode,都發現了惡意shellcode,說明了泄漏的虛表和覆蓋后偽造的虛表中都有惡意虛函數,驗證了本插件能檢測出針對段堆的堆溢出攻擊。 為了提取出段堆中的信息,本文分析了段堆結構中含有的字段并結合池掃描技術和字段在結構信息中的偏移量,設計出獲取段堆及其組件內部信息的算法并在內存取證框架中研發出功能插件,這些插件可以解析Windows 10系統中段堆內部含有的信息。實驗結果表明本文提出的方法可以重現進程運行時段堆及其內部組件在內存中的分配情況,進而反映進程中段堆內存信息,這些信息可以為系統遭到網絡攻擊或者網絡犯罪提供取證依據。

5 結 論