如何在啟用虛擬安全模式(VSM)的系統上進行記憶體採集
概述
本文主要是我最近對於開源記憶體獲取實用程式WinPmem的漏洞發現與分析成果。讀者可以在Velocidex/c-aff4釋出頁面(ofollow,noindex" target="_blank">https://github.com/Velocidex/c-aff4 )上找到最新版本的WinPmem,需要注意的是近期該軟體已經從Rekall框架遷移到了AFF4框架。
這個故事一開始起源於有人詢問我如何在受虛擬安全模式(VSM)保護的計算機上實現記憶體採集。現在,想要進行記憶體採集,有很多可以選擇的工具,但我們有一個最愛,那就是WinPmem。WinPmem是AFAIK唯一的開源記憶體採集工具。該工具在幾年前釋出,並且直到目前沒有更新過。但是,我們已經發現,這一工具會在啟用了虛擬安全模式的Windows系統上崩潰。
2017年,Jason Hale在DFRWS上發表了精彩的演講(https://www.dfrws.org/conferences/dfrws-usa-2017/sessions/virtualization-based-security-forensics-perspective ),在演講中他提出了大量的記憶體採集工具,並發現大多數工具在啟用虛擬安全模式的系統上都會出現藍屏(https://df-stream.com/2017/08/memory-acquisition-and-virtual-secure/ )。隨後,其中的一些工具進行了修復,但我沒有找到任何關於它們具體修復方式的資訊。目前,WinPmem是仍然還在崩潰的工具之一。
列印實體記憶體範圍
在我聽到這些情況之後,我立刻吹了吹我Windows主機上面的灰塵,並啟用了虛擬安全模式。然後,我嘗試執行已釋出版本的WinPmem(V3.0.rc3)。果不其然,我立刻就遇到了BSOD,真是糟糕。
起初,我認為實體記憶體範圍檢測是問題所在。也許在虛擬安全模式下,我們用WinPmem檢測到的記憶體範圍是錯誤的,這樣一來使得我們讀入了無效的實體記憶體範圍。大多數工具會使用未公開的核心函式MmGetPhysicalMemoryRanges()來獲取實體記憶體範圍,但它只能在核心模式下訪問。
為了確認,我在WinPmem中只需要使用-L標誌,打印出從該函式獲得的範圍。這個標誌表示WinPmem只會載入驅動程式、報告記憶體範圍並退出,不會實際讀取任何記憶體,所以我推測應該也不會崩潰。
這些記憶體範圍實際上看起來有點奇怪,它們與沒有啟用虛擬安全模式情況下同一臺主機的記憶體情況看起來有明顯不同。
現在,我嘗試從另外一個途徑來檢查實體記憶體範圍。我編寫了一個Velociraptor工具來收集註冊表中硬體管理器所顯示的實體記憶體範圍。由於無法從使用者空間訪問MmGetPhysicalMemoryRanges()(http://www.reactos.org/pipermail/ros-diffs/2009-June/031391.html ),因此係統會在每次啟動時將記憶體範圍寫入登錄檔中。在這裡,我們其實還可以使用Alex Ionescu的meminfo(http://www.alex-ionescu.com/?p=51 )從記憶體空間中轉儲(Dump)出記憶體範圍。
LG_OfTAiWnaAGw2ReyBI-z1716PAG1PkX9EnD0ukr86YCEpzp2rKt-Oz_m5crXnU2OcxhZtK-JokalU2iofPm4VaDD1XxFctKP2LP_R13qCMr4CUYbE_n"/>
結果表明,其範圍似乎與驅動程式報告的內容一致。
最後,我試圖用DumpIt(https://www.comae.com/ )取得記憶體映象,這是一個優秀的記憶體映象取證工具,它執行時也沒有發生崩潰:
Qtx9LJ3c0omfchk3857kV-UBspGL_HrWTf1Jg8Wis2S_1QHZfjmkQW2CgGGxlEkZDIZyg_KAL6CgX__vG9aB7rsOOIgqRY"/>
DumpIt程式產生了一個Windows崩潰轉儲映象格式。令我們激動的是,DumpIt顯示的CR3值與WinPmem上顯示的一致。儘管DumpIt沒有明確顯示出其使用的實體記憶體範圍,但由於它使用了crashdump格式檔案,我們可以藉助Rekall看到編碼到崩潰轉儲(Crashdump)中的範圍:
因此,在這裡我們能夠確認,這一問題並不是由於WinPmem在有效實體記憶體範圍之外進行讀取而產生的,DumpIt也對相同的範圍進行了讀取,但它並不會崩潰。
顯然,要修復任何錯誤,都會涉及到更新驅動程式元件,因為所有映象取證工具的使用者控制元件元件都是讀取相同的範圍。我進行了多年的核心開發,而我的程式碼簽名證書已經過期好幾年了。由於這些程式碼簽名證書需要EV,因此它們非常貴。我比較擔心不知情的使用者會嘗試在支援虛擬安全模式的系統上使用WinPmem並導致藍屏,這種系統正在變得越來越普遍,由此就帶來了非常大的風險。
我更新了DFIR列表,並公佈了我發現的需要修復核心驅動程式的問題,由於我的程式碼簽名證書過期,我將無法對其進行修復。我覺得目前來說,唯一負責任的做法就是給予使用者充分的提示,並建議使用者考慮使用其他同類工具。
接下來發生的事情令我感到驚訝,我收到了DFIR社群的積極迴應,收到了許多電子郵件,大家慷慨地為我贊助了程式碼簽名證書,並且對WinPmem出現的問題表示遺憾。最後,來自Binalyze的Emre Tinaztepe提出將會簽署新的驅動,並分享到社群。能看到開源社群的大家如此團結,這感覺真是不錯。
問題修復
至此,我開始研究WinPmem崩潰的原因。要進行核心開發,我們需要使用Drive Development Kit(DDK)安裝Visual Studio。這個過程需要很長的時間,並且需要下載很多個GB的軟體。
經過一段時間後,我成功部署了開發環境,檢查了WinPmem程式碼,並嘗試使用新構建的驅動程式對崩潰進行復現。但令我驚訝的是,崩潰這次沒有出現,並且工具完全正常執行。這一點令我非常困惑。
事實證明,這一問題在2016年已經得到了修復,但我完全忘記了這件事。然後,我尋找了我的郵件,並發現在2016年10月與Jason Hale的對話。我的程式碼簽名證書是在2016年8月到期的,因此雖然修復程式已經開發完成,但我沒能將其重新編譯到驅動程式中併發布。
我們仔細研究問題修復的過程,關鍵在於這段程式碼:
char *source = extension->pte_mmapper->rogue_page.value + page_offset; try { // Be extra careful here to not produce a BSOD. We would rather // return a page of zeros than BSOD. RtlCopyMemory(buf, source, to_read); } except(EXCEPTION_EXECUTE_HANDLER) { WinDbgPrint("Unable to read from %p", source); RtlZeroMemory(buf, to_read); }
這段程式碼嘗試讀取對映頁,但如果發生了段錯誤(Segfault),則會捕獲錯誤,並用零填充頁,不會導致BSOD。當時,我認為這種修復方式有點逃避問題,因為我們完全不知道為什麼會發生崩潰。但是,如果真的發生了崩潰,那我們還是更希望零填充(Zero Pad),而不是BSOD。但是,為什麼從物理RAM中讀取頁會產生記憶體錯誤呢?
要理解這一點,我們首先需要了解什麼是基於虛擬化的安全性。Microsoft Windows是通過虛擬安全模式(VSM)實現的:
VSM是利用CPU片上的虛擬化擴充套件來對關鍵程序和記憶體實現隔離,從而防止惡意篡改。這些保護是由硬體輔助的,管理程式會請求硬體以不同的方式處理這些記憶體頁。
在實際中,這就意味著,屬於敏感程序的某些頁實際上在正常的作業系統上是不可讀的。如果嘗試讀取這些頁,就會產生一個段錯誤。當然,在正常的執行過程中,VSM容器永遠不會訪問這些物理頁,因為這些物理頁不屬於它。但是,虛擬機器管理程式會額外進行基於硬體的檢查,以確保無法訪問這些頁。上面的修復方法之所以有效,是因為捕獲到了段錯誤,並使WinPmem移動到下一頁,從而變了BSOD。
所以,我修改了WinPmem,並報告其無法讀取的所有PFN。現在,WinPmem會在查詢結束後,打印出所有不可讀頁的列表:
IThTm24sRKE5eUq3qsJx8"/>
大家可以看到,受管理程式保護的頁數量其實不多,總計約25MB大小。這些頁不包含在特定區域中,它們被噴射在所有實體記憶體周圍。這實際上非常有道理,但我們還需要認真確認一下。
於是,我們運行了核心偵錯程式(Windbg),並看看它是否可以讀取WinPmem無法讀取的物理頁。實際上,Windbg有!db命令來讀取實體記憶體。
將這裡的輸出與之前的螢幕截圖進行比較,我們可以發現,WinPmem無法讀取PFN 0x2C7和0x2C9,但它可以讀取0x2C8。Windbg完全相同,它也無法讀取那些可能受到VSM保護的頁。
嘗試讀取不可讀的頁
實際上,這是一個有趣的開發過程。磁碟映像很長時間以來都有一個扇區能夠讀取錯誤,並且磁碟映像格式已經對這種可能性進行了證明。當某個扇區不可讀時,我們可以對該扇區進行零填充,大多數磁碟映像取證工具都有方法來指示扇區的不可讀狀態。但針對記憶體來說,這樣的情況在記憶體映象中從未發生過,因為我們即使有正確的許可權,也無法從RAM中讀取。
WinPmem預設使用AFF4格式,而AFF4專門設計了“診斷映象” (Forensic Imaging)功能。目前,AFF4庫使用“UNREADABLE”字串來填充不可讀的頁,以指示該頁已受到保護。
請注意,如果映象格式不支援這種情況(例如RAW映象或故障轉儲),那麼我們就只能對不可讀的頁進行零填充,我們並不知道這些頁原本就是零,還是不可讀。
PTE重對映 VS MmMapIoSpace
對於熟悉WinPmem的使用者來說,可能都知道WinPmem使用了一種名為PTE重對映的技術。所有記憶體獲取工具都必須將實體記憶體對映到核心的虛擬地址空間,所有軟體只能讀取虛擬地址空間,並且無法直接訪問實體記憶體。
通常,有4種技術可以將實體記憶體對映到虛擬記憶體:
1、使用.PhysicalMemory裝置,這是一種將實體記憶體匯出到使用者控制元件的老方法,並且已經被Windows禁用了很長時間。
2、使用未公開的MmMapMemoryDumpMdl()函式,將任意物理頁對映到核心空間。
3、使用MmMapIoSpace()函式,該函式通常用於將PCI裝置DMA緩衝區對映到核心空間。
4、直接進行PTE重對映,這也是WinPmem所使用的技術。
從作業系統穩定性的角度來看,獲取記憶體在定義上是不正確的操作。例如,在驅動程式驗證程式執行WinPmem的MmMapIoSpace()模式,將會產生程式碼為0x1233的0x1a錯誤檢查,錯誤資訊為:“有一個驅動程式試圖對映未鎖定的實體記憶體頁。由於頁的內容或屬性可能隨時發生變化,因此該操作非法”。
但在技術上來說,這是正確的,我們將系統實體記憶體的一些隨機頁對映到虛擬記憶體空間,但我們實際上並不是這些頁的所有者,所以任何人都可以隨時繼續使用這些頁。顯然,如果我們想要編寫一個核心驅動程式,對映實際上並非我們擁有的實體記憶體不是一個好的行為,但這正是記憶體採集的意義所在。
另一個有趣的問題是快取屬性。大多數人可能都認為直接PTE操作不太可靠,因為它會繞過作業系統,直接對映物理頁面,而忽略記憶體快取屬性。但實際上,作業系統其實執行了許多額外的檢查,目的就是為了捕獲到錯誤的API使用。例如,如果快取屬性不正確,那麼對MmMapIoSpace()函式的請求可能就會失敗。如果我們想讀取並寫入記憶體,那麼需要考慮快取的一致性,但在這裡我們只想獲取記憶體,因此在這裡是沒關係的。不管無論如何,我們都只會對映象進行操作,快取是不可能以任何方式影響到映象的。
WinPmem同時具有PTE重對映和MmMapIoSpace()模式。MmMapIoSpace()模式通常情況下無法收集更多頁。下圖分別是PTE重對映方法和MmMapIoSpace()方法所獲得的輸出結果。
PTE重對映方法:
MmMapIoSpace()方法:
如我們所見,MmMapIoSpace()方法無法讀取更多的頁,並且核心偵錯程式證明了有一些它無法讀取的頁仍然可以訪問。
總結
本文是我在進行虛擬安全模式系統記憶體採集工作原理研究過程中的記錄。如果讀者發現其中存在任何錯誤,歡迎在DFIR/Rekall/Winpmem郵件列表中發表評論。在我看來,WinPmem無法讀取受保護的頁面是有道理的,甚至核心偵錯程式也無法讀取相同的頁面。但是,我檢查了DumpIt生成的映象,它居然包含了本應不可讀的這些頁面的資料(並不是全零)。另一個有趣的事情時,DumpIt會報告沒有讀取頁面的錯誤(如上文截圖所示),因此該工具會聲稱已經讀取了所有頁面(包括那些受VSM保護的頁面)。但實際上,DumpIt是否以其他的方法實現了讀取,或者它是否存在諸如未初始化緩衝區的問題從而導致讀取失敗的頁返回了隨機垃圾資料?我們還需要進一步進行測試,才能對上述問題有一個準確的回答。
致謝
感謝Binalyze的Emre Tinaztepe進行的測試工作,同時感謝Matt Suiche的反饋以及進行的深入研究和工具製作。