Linux內(nèi)存取證:解析用戶空間進程堆
前言
在取證分析中,對內(nèi)存的分析通常是還原事件的重要步驟。以前對內(nèi)存工作的分析主要集中于那些駐留在內(nèi)核空間中的信息,如進程列表、網(wǎng)絡(luò)連接等,特別是在Microsoft Windows操作系統(tǒng)上,但是這項工作主要關(guān)注的是Linux用戶空間進程,因為它們可能還包含有價值的調(diào)查信息。由于許多進程數(shù)據(jù)位于堆中,所以這項工作首先集中在對Glibc堆實現(xiàn)的分析,以及如何將堆相關(guān)信息存儲在使用此實現(xiàn)的Linux進程的虛擬內(nèi)存中。
到目前為止,從內(nèi)存取證的角度來看,堆通常被認為是一個大的內(nèi)聚內(nèi)存區(qū)域,這使得在內(nèi)部識別相關(guān)信息變得相當困難。我們?yōu)榛诜治鼋Y(jié)果的內(nèi)存分析框架Rekall引入了Python類,并允許訪問堆中包含的所有塊及其元信息。
此外,基于這個類,我們已經(jīng)開發(fā)了6個插件,支持研究人員分析用戶空間進程,其中有4個插件提供了常用的分析功能,比如在塊中查找信息或引用,并將塊轉(zhuǎn)儲到單獨的文件中以進行進一步的調(diào)查。目前這些插件已被用于對用戶空間進程中的堆內(nèi)數(shù)據(jù)結(jié)構(gòu)進行逆向工程,你可以點此處,查看這些插件如何簡化整個分析進程。其余兩個插件則是這些用戶空間進程分析的結(jié)果,而且還負責提取zsh shell的命令歷史記錄和密碼管理器KeePassX的密碼輸入信息。
由于內(nèi)存表示運行中的系統(tǒng)的當前狀態(tài),它包含有關(guān)瀏覽器歷史記錄、輸入命令、運行中的網(wǎng)絡(luò)連接、加載的驅(qū)動程序以及活動進程的信息,因此我們才可以對以前的活動進行詳細的分析。雖然這些信息大部分位于內(nèi)核空間,可以使用Rekall和Volatility等各種現(xiàn)有解決框架進行取證,但也有很多信息位于用戶空間,例如,用戶空間進程的堆通常是各種數(shù)據(jù)的豐富來源,而根據(jù)具體應(yīng)用程序的不同,它可能包含憑證、IP地址、DNS名稱或命令歷史。但是,在Linux環(huán)境中,這些信息還不是容易提取的。
為了有效和可靠的識別和提取這些信息,研究人員需要查看與該進程相同或至少類似的堆的視圖,以此來了解數(shù)據(jù)所在的位置,比如什么樣的數(shù)據(jù)類型會存儲在什么位置、哪些數(shù)據(jù)占用多少內(nèi)存量。否則,取證人員對數(shù)據(jù)結(jié)構(gòu)不理解,只能摸著石頭過河,因為所有信息都位于堆內(nèi),只能將堆作為一個大內(nèi)存區(qū)域來處理,這樣效率就太低了。
為什么解析用戶空間進程堆
在Linux進程的上下文中,以前對用戶空間分析的重點僅限于在整個進程內(nèi)存或整個堆或堆棧中搜索特定模式。例如,為了重建Linux bash shell的命令歷史記錄,Rekall的bash 7插件在堆中搜索一個主題標簽,后跟一個字符串格式的Unix時間戳(例如#1471572423)。但是,如果感興趣的信息沒有以易于檢測的方式進行標記,則簡單的模式匹配將失敗。
當研究者在內(nèi)存中標識某個字符串并試圖標識對該字符串的引用時,即使存在間接引用,這種模式匹配搜索也可能失敗。如果字符串是結(jié)構(gòu)或?qū)ο蟮囊徊糠?,并且不在開頭,而感興趣的指針直接引用結(jié)構(gòu)而不是字符串,則可能出現(xiàn)這種情況。為了能夠找到這些引用,你就必須知道該結(jié)構(gòu)的開頭,這就需要了解其字段的大小以及字符串在結(jié)構(gòu)中的位置。但是,這些細節(jié)通常在"黑箱分析"中不可用。
在本文中,我們實現(xiàn)解析用戶空間進程堆的步驟分為5步:
1.通過分析Glibc堆的實現(xiàn),總結(jié)一些可以讓調(diào)查人員能夠執(zhí)行手動堆分析的信息,其中重要的信息是關(guān)于堆結(jié)構(gòu)的排列方式以及它們通常位于內(nèi)存中的位置;
2.證明一些塊可能隱藏在內(nèi)存中的某個位置,為此我們提出了一種檢索它們的算法;
3.這些算法已被用于開發(fā)一個名為HeapAnalysis的Python類,它可用于實現(xiàn)特定的堆分析插件?;谶@個類,我們開發(fā)了支持取證人員分析堆及其塊的插件;
4.我們會解釋如何通過應(yīng)用這些插件收集相關(guān)信息來分析用戶空間進程的數(shù)據(jù),類似于我們對Windows用戶空間進程的分析;
5.對結(jié)果的進一步分析是兩個插件,***個插件從zsh shell(版本5.2)進程的堆中收集所有已執(zhí)行的命令,第二個插件從堆中提取所有可檢索密碼條目的標題、用戶名、URL和注釋字段的密碼管理器KeePassX(版本0.4.3)。
HeapAnalysis類和所有提到的插件都支持×86和×64架構(gòu)。
除了本文之外,我們還發(fā)布了一份技術(shù)報告,其中包含更多技術(shù)細節(jié)和代碼列表。
本文的文章目錄結(jié)構(gòu):
1.Glibc分析章節(jié)詳細介紹了使用Glibc堆實現(xiàn)的用戶空間進程的堆;
2.在Plugin實現(xiàn)章節(jié)中,我們對開發(fā)的堆分析插件進行了概述;
3.插件的評估章節(jié);
4.實踐應(yīng)用章節(jié)提供了詳細的應(yīng)用分析;
5.總結(jié)章節(jié);
對用戶空間進程堆進行解析的研究歷史
到目前為止,對用戶空間應(yīng)用的分析還沒有得到足夠的重視,這正是我們此次研究的動機,另外關(guān)于該主題的文獻目前還非常少,Linux內(nèi)存取證arena的現(xiàn)有文獻主要涉及內(nèi)核主題,如Urrea, 2006, Case et al., 2010 和 Ligh et al. (2014). 。
少數(shù)例外是Leppert(2012) 和Macht(2013)的研究,他們都專注于基于Linux的移動設(shè)備的Android操作系統(tǒng),并分析應(yīng)用程序和它們的堆數(shù)據(jù)。但是,他們的分析主要集中在堆中包含的序列化Java對象上,而不是堆對象的管理方式上。除此之外,還有對插件cmdscan 和bash 的研究,它們分別從Windows的cmd和Linux的bash shell中提取命令歷史記錄。然而,這些插件的使用都是基于以下的事實:在這些情況下,只需將堆視為一個大內(nèi)存區(qū)域就可以識別信息了,另一項相關(guān)工作是對記事本堆的分析。據(jù)我們所知,這是唯一使用任何堆細節(jié)的示例,而且與大多數(shù)先前的工作一樣,它也與Windows相關(guān)。
如果不考慮取證,僅對堆進行的基礎(chǔ)研究,特別是對Linux進程堆及其管理方式的研究,僅有Ferguson(2007) 對Glibc的堆實現(xiàn)進行了研究。然而,以前的研究更多的關(guān)注于如何利用堆,因此目前還沒有足夠的信息來讓我們在內(nèi)存取證場景中從堆中收集所有相關(guān)信息。
Cohen(2015)的研究是***個通過一組Windows操作系統(tǒng)分析工具來解決這一問題的項目。雖然Windows的堆實現(xiàn)與來自Glibc的堆實現(xiàn)之間存在一些相似之處,例如,在兩種情況下,分配的塊前面都有一個至少包含塊大小的結(jié)構(gòu),但它們在細節(jié)上有所不同。
Glibc分析
在本章節(jié)中,我們從內(nèi)存取證的角度介紹了我們對Glibc堆實現(xiàn)的最重要分析結(jié)果,更詳細的信息可以從我們的技術(shù)報告中獲得。
不同的堆實現(xiàn)
我們的堆實現(xiàn)對象是Glibc 2.23版,它基于Wolfram Gloger的ptmalloc2 ,不過除此之外還有其他各種堆實現(xiàn),其中大多數(shù)都是在某個操作系統(tǒng)或應(yīng)用程序的上下文中使用的。應(yīng)用程序開發(fā)人員還可以決定是實現(xiàn)自己的堆還是使用其他現(xiàn)有堆實現(xiàn),例如Firefox等Mozilla產(chǎn)品。然而,這樣的進程可能無法使用本文介紹的信息或工具進行分析。
Glibc堆概述
本節(jié)會介紹Glibc堆實現(xiàn)中使用到的最重要的對象和結(jié)構(gòu)。圖1顯示了一個正在運行的進程的堆布局,重點關(guān)注一下各個元素之間的引用。從***層和最重要的層級開始,塊包含實際的用戶或進程數(shù)據(jù)。這些數(shù)據(jù)是通過malloc調(diào)用顯式分配的,或者通過類的實例化上下文中的新調(diào)用隱式分配的。這些塊位于特定的內(nèi)存區(qū)域,除了分配表示當前正在使用的塊之外,還會分配一些正在使用但將來不再使用的釋放塊。當一個塊被釋放時,塊本身或至少其數(shù)據(jù)在大多數(shù)場景中仍保持與之前相同的位置,并且其數(shù)據(jù)(除了稍后要解釋的一些修改之外)也不會被刪除或覆蓋。
Glibc堆概述
***層級是arena,Ptmalloc2通過幾種數(shù)據(jù)結(jié)構(gòu)來進行管理,主要有arena,heap,chunk三種層級。
Arena
arena對于32位系統(tǒng),數(shù)量最多為核心數(shù)量2倍,64位則最多為核心數(shù)量8倍,可以用來保證多線程的堆空間分配的高效性。主要存儲了較高層次的一些信息。有一個main_arena,是由主線程創(chuàng)建的,thread_arena則為各線程創(chuàng)建的,當arena滿了之后就不再創(chuàng)建而是與其他arena共享一個arena,方法為依次給各個arena上鎖(查看是否有其他線程正在使用該arena),如果上鎖成功(沒有其他線程正在使用),則使用該arena,之后一直使用這個arena,如果無法使用則阻塞等待。簡而言之,arena是由malloc_state結(jié)構(gòu)描述的,從本質(zhì)上講,它是屬于一個或多個線程的堆空間,而每個arena都有自己的內(nèi)存區(qū)域,其中包含來自相關(guān)線程的已分配和釋放的塊。包含在Glibc庫中的arena被稱為main_arena,因為它被***個或主要線程使用。雖然arena沒有直接鏈接到每個內(nèi)存區(qū)域或分配的塊(參見圖1),但還有其他連接,如指向釋放塊的指針,下一個arena和頂部塊。這個頂部塊表示給定arena的剩余可用空間,用于創(chuàng)建新的塊并位于arena的末端。
heap_info
arena下面的一層是heap_info結(jié)構(gòu),heap的等級就比arena要低一些了,一個arena可以有多個heap,也是存儲了堆相關(guān)的信息。盡管它們的名稱類似,但它們并不描述進程的整個堆或與線程關(guān)聯(lián)的堆,而只描述它們所屬的映射內(nèi)存區(qū)域(由vm_area_struct結(jié)構(gòu)描述)的那部分。更具體地說,每個映射到arena(main_arena除外)的內(nèi)存區(qū)域至少在內(nèi)存區(qū)域的開頭都包含heap_info結(jié)構(gòu)的一個實例,它保存了該內(nèi)存區(qū)域中當前堆部分的大小。
除了大小之外,每個heap_info結(jié)構(gòu)都包含一個指向相關(guān)arena(malloc_state結(jié)構(gòu))的指針和一個指向同一arena內(nèi)的前一個heap_info結(jié)構(gòu)的指針(請參見圖3)。這樣,所有這些都鏈接在一起。arena指針存儲在于ar_ptr構(gòu)建以及對prev構(gòu)建中的前一個heap_info的引用中。與arena相比,heap_info結(jié)構(gòu)不是循環(huán)鏈接,因為***個heap_info的prev字段是null。
不包括arena和heap_info區(qū)域的是mmapping塊,如圖1所示,沒有從MMAPPED塊到任何其他結(jié)構(gòu)或從堆結(jié)構(gòu)到它們的鏈接。這些塊通常是在分配請求超過給定閾值(通常為128*1024字節(jié))時創(chuàng)建的,在這種情況下,塊不包括在main heap或?qū)儆诹硪粋€arena的任何內(nèi)存區(qū)域中,但是要求操作系統(tǒng)僅為該塊(通過mmap API調(diào)用)提供專用內(nèi)存區(qū)域,以在此區(qū)域中放置塊。當mmap API調(diào)用以頁面的形式返回內(nèi)存空間時, MMAPPED塊的最小大小為一個頁面(至少為4096字節(jié)或其倍數(shù)),并且可以被一個頁面大小整除。釋放MMAPPED塊時,它正在分配的整個內(nèi)存空間將從進程空間中刪除并返回給操作系統(tǒng)。
Chunk(塊)
chunk為分配給用戶的內(nèi)存的一個單位,每當我們分配一段內(nèi)存的時候其實就是分配得到了一個chunk,我們就可以在chunk當中進行一定的操作了。不過為了進行動態(tài)分配,chunk本身也有一些數(shù)據(jù)(元數(shù)據(jù)),是用來表示其分配等等的數(shù)據(jù)。
本文我們對解析用戶空間進程堆的動機和歷史,做了一個簡要的概述。另外,我們Glibc堆的3層結(jié)構(gòu)也做了一些概述,這些結(jié)構(gòu)是解析用戶空間進程堆的關(guān)鍵。