命名空间不能直接包含字段或方法之类的成员_Linux内存取证:解析用户空间进程堆(中)...
上文我們對解析用戶空間進程堆的動機和歷史,做了一個簡要的概述。另外,我們Glibc堆的3層結構也做了一些概述,這些結構是解析用戶空間進程堆的關鍵。至于每個結構所起的作用,請看本文的分析。
內存視圖
本節(jié)描述如何以及在何處將前一節(jié)中描述的結構存儲在內存中,以用于運行中的Linux用戶空間進程。
內存中的塊
塊的結構名為malloc_chunk,包含以下字段:
· prev_size:如果上一個塊已經被釋放了,那該字段將包含上一個塊的大小;
·?size:從當前塊的開頭到下一個塊的距離(以字節(jié)為單位);
·?fd:正向鏈接(Forward link),指向下一個被釋放的塊;
·?bk:反向鏈接(Backward Links),指向之前被釋放的塊;
·?fd_nextsize:指向下一個更大的塊;
·?bk_nextsize:指向下一個更小的塊;
此結構位于每個塊的開頭之處,但并非每個塊都要用到所有字段。對于已分配的塊,主要使用size字段來實現(xiàn)其預期目的。只有當前面的塊是釋放的塊時,才使用prev_size字段。正如Ferguson已經解釋的那樣,size字段不僅包含了大小信息,還使用了其低層級的3位作為特殊標志。最低位(PREV_INUSE標志)表示前一個塊是否是釋放的塊,第二個最低位(IS_MMAPPED標志)表示設置的MMAPPED塊,第三個最低位(NON_MAIN_ARENA標志)表示代的塊不屬于main_arena。
malloc_chunk結構的所有其他字段都會被用于用戶或進程數(shù)據(jù),并且不包含任何元信息。如圖2所示,其中顯示了用戶數(shù)據(jù)塊分配的塊(頂部)從fd構建到下一個塊的 size字段的起始處,中間的所有字段都被填充為灰色(標記為數(shù)據(jù)),其中就包括第二個塊的prev_size字段。
內存中分配的塊
每個塊的大小和地址是一致的,這意味著它可以分別在x86和x64架構上分別被8和16整除。在MMAPPED塊的示例下,大小和地址能夠被頁面大小均勻地整除。這是由于mmap API函數(shù)返回了請求塊的內存空間,它不僅返回大小,還返回頁面大小邊界上的地址。此外,盡管每個MMAPPED塊都是由單獨的mmap調用產生的,但是多個MMAPPED塊最終可能會出現(xiàn)在一個vm_area_struct結構描述的內存區(qū)域中,因為內核可以很容易地擴展一個區(qū)域。另一方面,如果釋放了位于同一區(qū)域的兩個或多個MMAPPED塊之間的MMAPPED塊(其相關頁面被返回到操作系統(tǒng)),則連續(xù)內存區(qū)域可以被分割成兩個獨立的區(qū)域。與正常分配的塊類似,用戶或進程數(shù)據(jù)塊在size字段之后立即被分配,但不包括下一個塊的prev_size字段,因為它不能保證MMAPPED塊后面緊跟著另一個塊。
釋放的塊會以如上所述的方式鏈接在一起,最終形成一個bin。bin可以被視為一個容器,用于釋放總是屬于特定arena的塊。不同類型的容器,其主要區(qū)別在于它們所包含的塊的大小不同,比如fastbin塊(通常在×86體系結構上大小不超過80字節(jié)的塊),small bin(在×86架構上通常不大于512字節(jié))和large bin(small bin以上的所有內容)。不同類型的bin,鏈接的方式也不同,會直接影響malloc_chunk結構使用的字段,從而決定覆蓋多少字節(jié)的用戶或進程數(shù)據(jù)。 Fastbin塊僅使用fd字段,small bin和large bin使用的是fd和bk字段,用于構建循環(huán)鏈表,而large bin塊也會使用fd_nextsize和bk_nextsize字段,以確保在每種情況下,相應的用戶數(shù)據(jù)被指針覆蓋。對于prev_size字段也是如此,當釋放的塊放入small bin或large bin時,該字段就會被設置。例如對于釋放的large bin,未覆蓋的用戶數(shù)據(jù)從bk_nextsize字段之后一直延伸到下一個塊的prev_size字段的起始處。關于prev_size字段的唯一例外是fastbin塊,所有這些塊的標志之間都不相互受影響,例如NON_MAIN_ARENA標志一直處于設置狀態(tài),但是下一個塊的PREV_INUSE標志沒有被設置為free,導致prev_size字段中沒有覆蓋用戶或進程數(shù)據(jù)。
頂層塊雖然不使用任何指針,但卻非常重要,數(shù)據(jù)部分的起始處就像在fd構建之后分配的塊一樣,因為它是arena中的最后一個塊并且大部分時間都在內存區(qū)域邊界處結束,所以沒有可以用于塊的prev_size字段。
存在內存中的arena和堆信息結構
main heap是一個連續(xù)的內存區(qū)域,包含main_arena的所有塊。雖然區(qū)域是連續(xù)的,但它可以分成多個連續(xù)的內存區(qū)域。它描述的malloc_state結構存儲在映射的Glibc庫的bss部分中,如前所述,沒有heap_info結構用于main_arena。
然而,線程arena的malloc_state結構與塊一起存儲在同一內存區(qū)域中。從圖3中可以看出,它位于第一個heap_info結構之后和該arena的第一個塊之前。通常,可以在屬于線程arena的每個映射的內存區(qū)域的開頭找到heap_info 結構。除此之外,多個heap_info stuct可能最終出現(xiàn)在同一個映射的內存區(qū)域中。這些heap_info結構雖不一定都屬于同一個arena,但也可以與不同的arena相關。圖3的內存區(qū)域可以說明了這一點,其中較低的heap_info結構屬于另一個arena,而較高的heap_info結構屬于所描繪的malloc_state結構。
內存中的 malloc_state 和heap_info 結構
堆和棧通常各自在部分向上發(fā)展,并彼此靠攏,事實上,只要堆只包含沒有任何線程arena或MMAPPED塊的main_arena,就不會和棧靠攏。但是,只要其中任何一個被引入,這種嚴格的分離就會被打破。與MMAPPED塊類似,屬于線程arena的內存區(qū)域通常與包含特定線程堆棧框架的內存區(qū)域混合在一起。
插件實現(xiàn)
《Rekall內存取證醫(yī)框架》一文提供了一組用于分析內存轉儲的插件。該方法最初是2013年從Volatility代碼庫中衍生出來的,后來經過重新編寫和擴展,Rekall版本已經有了1.5.1和1.5.2.post1版本。在撰寫本文時,這些版本在×86和×64架構上至少支持Glibc的2.20,2.21,2.22,2.23和2.24版本。Rekall的核心組件是Python類HeapAnalysis,它可以將之前描述的所有分析結果都呈現(xiàn)出來。
目前已經開發(fā)出的插件有4個:
·?Heapinfo:該插件提供了一個關于arena數(shù)量、塊和大小的簡要概述;
·?heapdump:該插件會將所有已分配和已釋放的塊轉儲到單獨的文件中以供進一步分析;
·?heapsearch:該插件會在所有塊中搜索給定的字符串、正則表達式或指針;
·?heaprefs:該插件會檢測給定塊的數(shù)據(jù)部分是否引用了其他塊;
雖然這些插件最適合使用正在使用的Glibc版本的調試信息,但HeapAnalysis類提供了在沒有調試符號可用時分析進程的功能。
main_arena的獲取
HeapAnalysis類在初始化期間執(zhí)行的首要任務之一是定位main_arena。除了保存arena大小和bin指針等重要信息外,它還會顯示在映射的Glibc庫中的位置,表明在搜索進程中檢測的信息是否是正確的。
如果有調試信息,或者更準確地說,有main_arena符號的常量偏移量,則是獲得main_arena的最可靠方法。否則,不能直接確定main_arena,而是通過使用兩種不同的技術進行搜索。第一個假設有多個線程,因此也有多個arena。由于只有main_arena結構存儲在映射的Glibc庫中,所有其他結構通常位于內存區(qū)域的開頭,緊跟在heap_info結構之后。因此,對于vm_area_struct結構描述的每個內存區(qū)域(除了映射文件區(qū)域或堆棧框架之類的例外情況),開頭都是heap_info結構。如果其ar_ptr字段指向其自身并且其prev字段為null(第一個heap_info結構指向沒有其他heap_info實例,并且如果它位于arena之前,它是第一個實例),則ar_ptr指向的arena將被暫時保存。如果最后可以識別至少一個arena,則就會測試出其下一個構建是否會出現(xiàn)在映射的Glibc庫的內存區(qū)域中。如果是這種情況,指向Glibc的地址將被視為main_arena。
如果不能識別更多的arena,則意味著只有main heap和一些可能的MMAPPED塊,這時就要使用第二種技術,即利用每個bin塊包含的循環(huán)雙鏈表。具體的進程就是順著bin塊的bk指針,找到main_arena。為了獲得這樣的bin塊,我們必須要檢測main heap上的每個塊的PREV_INUSE位。如果此標志在任何塊中都未被設置,則前一個塊很可能是已釋放的bin塊。而后一個塊則緊跟上一個塊的bk字段,以便最終在main_arena中結束。如果在某個時刻,bk字段指向映射的Glibc庫,則代表我們很可能找到了main_arena。
不過此時仍然存在一個問題,bk字段指向arena的中間而不是開頭。雖然通過檢測塊的大小可以準確地確定該主要區(qū)域內的位置,但是這種方法并不總是完全可靠,因為不同的bin大小不同(容器大小在編譯時是可以更改的)。所以找到main_arena開頭的方法便是搜索頂部塊指針,這么做有以下兩個原因:
1. 對于一個特定的Glibc實例,malloc_state中的最大塊指針的偏移量是固定的,因此是可靠的。因此,如果找到了該字段的虛擬地址,那么到arena起點的距離就可以很容易地計算出來。
2.需要有一個可靠的進程來將預期字段與當前值關聯(lián)起來,頂部字段提供了這樣一種關聯(lián),即它指向的塊應該一直能擴展到內存區(qū)域的末尾(頂部塊的偏移量加上它的大小)。
到達頂部字段的進程現(xiàn)在是從當前bin返回,將每個指針大小的值視為指針,并檢測它是否指向main heap內以及滿足頂部塊的要求。一旦找到頂部字段,就通過從找到的頂部字段的內存地址中減去malloc_state結構中頂部字段的偏移量來計算出主arena的地址。
MMAPPED區(qū)域
雖然屬于arena的內存區(qū)域的標識在大多數(shù)情況下非常可靠,但是包含在MMAPPED塊的區(qū)域的標識則不是這樣的,因為:
1.其中缺乏獨特的結構或可靠的指針;
2.任何未命名的映射內存區(qū)域可能包含MMAPPED塊,它們可以位于進程空間中的任何位置;
似乎指向這些塊的指針最有可能保存在處理這些塊的函數(shù)的堆棧框架中,而不是保存在任何相關的簿記結構中。因此,一種識別它們的方法就是將堆棧段(來自所有線程)中的指針大小的所有字節(jié)解釋為指針,跟蹤它們并驗證駐留在該位置的信息。然而,這種方法卻隱含著兩個問題。
1.它非常容易出錯,并且將任意數(shù)據(jù)解釋為指針,并且,根據(jù)堆棧大小的不同,耗時多少也各不相同,因為必須讀取、跟蹤每個指針,然后,將它指向的數(shù)據(jù)作為塊對象啟動并測試其值。
2.它可能會遺漏一些MMAPPED塊在,這種情況下,MMAPPED 塊指針不再被使用,它的值可能會被更新的堆棧框架覆蓋,但是如果塊從未被釋放,則它仍然存在于內存空間中,從而提供可能有價值的信息。
由于缺乏替代方案,當前確定某個內存區(qū)域是否包含MMAPPED塊的方法就是執(zhí)行可信檢驗。因為僅包含MMAPPED塊的內存區(qū)域也以MMAPPED塊開頭,如果是這樣的話,可信檢驗將出現(xiàn)以下特征:
1.prev_size字段的值必須為0;
2.塊的大小(沒有任何標志size字段的值)必須至少與頁面大小一樣大;
3.塊的大小必須可以被頁面大小整除;
4.塊的偏移量加上其大小不得超過包含內存區(qū)域的邊界;
5.塊的位置必須可以被頁面大小整除(主要用于后續(xù)和隱藏的MMAPPED塊);
6.必須取消設置PREV_INUSE和NON_MAIN_AREA位并設置IS_MMAPPED位;
只有在所有這些屬性都滿足的情況下,相應的內存區(qū)域才被認為包含MMAPPED塊。除此之外,同一內存區(qū)域的任何后續(xù)數(shù)據(jù)都將會被逐一檢測。
雖然MMAPPED塊通常位于獨占映射的內存區(qū)域內,但可能會發(fā)生這些塊位于映射內存區(qū)域的底部,包含不同的數(shù)據(jù),如堆棧段(參見圖4)。當開始在內存區(qū)域開頭的堆棧場景中搜索MMAPPED塊時可能會導致誤報(將堆棧數(shù)據(jù)解釋為塊)。所以正確的方法是使用EBP方法,擴展基址指針寄存器(extended base pointer, EBP)其內存放一個指針,該指針指向系統(tǒng)棧最上面一個棧幀的底部。基本進程就是根據(jù)所有保存的EBP指針,從pt_regs結構收集的基指針開始(用于在上下文更改時保存寄存器值)。當每個EBP值指向下一個保存的EBP時,只需跟蹤這些指針就會導出第一個保存的EBP,它可能位于堆棧段的開頭處,該進程如圖4所示。
隱藏的MMAPPED塊——EBP展開
一旦第一個保存的EBP的偏移量被識別出來,下一步就是順著向后搜索第一個MMAPPED塊。由于MMAPPED塊僅位于可被4096(最小頁面大小)整除的地址,因此僅檢測此類地址。在EBP值未指向堆棧段中保存的EBP的情況下(用于承載不同的數(shù)據(jù)),該方法保持基本相同,只不過它沒有EBP展開,這也意味著它也不會在內存區(qū)域向后搜索。
如果丟失的MMAPPED塊不在堆棧段之后,而是隱藏在其他地方,則必須擴展搜索范圍,同時增加誤報的風險。唯一可以排除的區(qū)域是已經檢測過的堆棧和堆段以及那些包含映射文件內容的區(qū)域(vm_area_struct結構引用文件對象),因為他們不能確定是否包含其他數(shù)據(jù)(因為這些區(qū)域代表文件的內容)。此示例的搜索進程保持不變,但沒有EBP展開。在內存區(qū)域中第一次命中之后(無論是在堆棧之后還是其他地方),第一個MMAPPED塊都將被用于遍歷潛在的后續(xù)塊。
應該注意的是,為了防止誤報,只有當關于MMAPPED塊的當前信息看起來不正確時,HeapAnalysis類才會開始搜索隱藏的MMAPPED塊,這將在下面的章節(jié)中進行解釋。
總結
以上是生活随笔為你收集整理的命名空间不能直接包含字段或方法之类的成员_Linux内存取证:解析用户空间进程堆(中)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python正则表达regex_Pyth
- 下一篇: 二级python 刷题就能过吗_Pyth