CVE-2023-35359 analysis

渗透技巧 8个月前 admin
318 0 0

Tale of my first cve
我的第一个 cve 的故事

Introduction 介绍

In a previous post regarding filesystem bugs, I mentioned that I found an interesting behaviour pertaining to the dispatchment of unhandled exceptions.
在之前一篇关于文件系统错误的文章中,我提到我发现了一个与调度未经处理的异常有关的有趣行为。

With the bug being fixed in August’s patch tuesday as CVE-2023-35359, I can finally share about it publicly.
随着该错误在 8 月的补丁星期二作为 CVE-2023-35359 修复,我终于可以公开分享它了。

The bug itself is pretty useless and almost always non-exploitable(afaik), but the patch is huge and worth a blog post 🙂
该错误本身非常无用,几乎总是不可利用的(afaik),但是该补丁很大,值得:)博客文章

I’ll be writing about the bug itself, how I found it, my expected patch and the actual patch by Microsoft.
我将写关于错误本身,我是如何找到它的,我预期的补丁和Microsoft的实际补丁。

Executive Summary 摘要

When an unhandled exception occurs on Windows, the program will involuntarily attempt to awake the Windows Error Reporting(WER) service for logging and analysis.
当 Windows 上发生未经处理的异常时,程序将非自愿地尝试唤醒 Windows 错误报告( WER ) 服务以进行日志记录和分析。

In the case where the awake call fails, for example when the service is explicitly marked as Disabled, the faulting program will create a WerFault.exe child to collect program specific statistics.
在唤醒调用失败的情况下,例如当服务被显式标记为 Disabled 时,错误程序将创建一个 WerFault.exe 子级来收集特定于程序的统计信息。

If by any chance the faulting program is a privileged process impersonating our current user, we will be able to hijack the process creation using a spoofed DOS device map and execute arbitrary code as high integrity.
如果错误程序是冒充我们当前用户的特权进程,我们将能够使用欺骗性的DOS设备映射劫持进程创建,并以高完整性执行任意代码。

The conditions are: 条件是:

  • WER service marked as disabled
    标记为已禁用的 WER 服务
  • Privileged process P impersonating medium IL user
    特权进程 P 模拟中型 IL 用户
  • Unhandled exception in P while under impersonation
    模拟时未 P 处理的异常

The first condition is pretty common due to privacy and storage concerns, but the other two are really difficult to satisfy.
由于隐私和存储问题,第一个条件很常见,但另外两个条件确实很难满足。

In the next section I’ll go into the technical details and root cause analysis of the bug.
在下一节中,我将介绍该错误的技术细节和根本原因分析。

Impersonated device map technique will not be discussed since I’ve previously written about it.
模拟设备映射技术将不讨论,因为我之前已经写过它。

Root Cause Analysis 根本原因分析

Here’s a sample program to simulate an unhandled exception:
下面是一个模拟未经处理的异常的示例程序:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(int argc, char **argv)
{
    getchar();
    *(int *)0 = 0;

    return 0;
}

Programmers are expected to handle their own exceptions using SEH(try-except) if using C or C++ Exception Handling(try-catch) if using C++.
如果使用 C 语言,程序员应该使用 (try-except) 来处理自己的异常, C++ Exception Handling 如果使用 C++则使用 SEH (try-catch)。

Unhandled exceptions on Windows fall back to the default exception handler KernelBase!UnhandledExceptionFilter.
Windows 上未经处理的异常回退到默认的异常处理程序 KernelBase!UnhandledExceptionFilter 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
LONG __stdcall UnhandledExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
  ...

  if ( v3->ExceptionCode == -1073740791
    && ((unsigned int)BasepIsKernelDebuggerPresent() || (unsigned int)BasepIsDebugPortPresent()) )
  {
    DbgPrint_0("\r\nSTATUS_STACK_BUFFER_OVERRUN encountered\r\n");
    __debugbreak();
  }

  ...

    if ( !v14 || v14 == 126 )
    {
      v8 = BasepReportFault(ExceptionInfo, 1i64);
      v28 = v8;
      v29 = 1;
    }
  }
  
  ...

}

This function checks if a debugger is attached, and breaks into it if possible.
此函数检查是否附加了调试器,并在可能的情况下中断调试器。

Otherwise, execution is passed to Kernel32!BasepReportFault to report the crash, which is a wrapper around Kernel32!WerpReportFault and eventually calls into Kernel32!WerpReportFaultInternal.
否则,将执行传递给 以 Kernel32!BasepReportFault 报告崩溃,这是一个包装器 Kernel32!WerpReportFault 并最终调用 Kernel32!WerpReportFaultInternal 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
__int64 __fastcall WerpReportFaultInternal(__int64 a1)
{
  ...

  status = RtlWerpReportException(v17, v39, v46, v32, 0, &v33);

  ...

  if ( status >= 0 )
  {
    v20 = 1769483;
    if ( status == 1769483 )
    {
      DbgPrintEx(
        0x96u,
        0,
        "WER/ReportFault:%u: ERROR RtlWerpReportException failed: too many concurrent workers\n",
        952i64);
      goto LABEL_49;
    }
    goto LABEL_26;
  }

  ...
  
    v21 = StartCrashVertical(v6, v46, v32, &v33);
    
  ...

LABEL_26:

  ...
   
  if ( v34 )
    NtClose(v34);
  if ( TargetHandle )
    NtClose(TargetHandle);
  if ( v5 )
    NtClose(v5);
  if ( v4 )
    NtClose(v4);
  if ( v1 )
    UnmapViewOfFile(v1);
  if ( v6 )
    NtClose(v6);
  if ( v33 )
    NtClose(v33);
  return MapReturnCode(v20);
}

This function calls into ntdll!RtlWerpReportException, which is responsible for awaking the WER service(via an ETW trigger) and sending messages to it(via ALPC as mentioned here).
此函数调用 ntdll!RtlWerpReportException ,它负责唤醒 WER 服务(通过 ETW 触发器)并向其发送消息(通过此处提到的 ALPC)。

If the call succeeds, the service routine WerSvc!CWerService::SvcReportCrash will be invoked to organize crash statistics.
如果调用成功,将调用服务例程 WerSvc!CWerService::SvcReportCrash 来组织崩溃统计信息。

Finally, before the faulting process terminates, a thread is created to launch WerFault.exe as a child process using an auxiliary dll export Faultrep!CreateCrashVerticalProcess which eventually calls CreateProcessAsUserW.
最后,在错误进程终止之前,将创建一个线程,以使用最终调用 CreateProcessAsUserW 的辅助 dll 导出 Faultrep!CreateCrashVerticalProcess 作为子进程 WerFault.exe 启动。

CVE-2023-35359 analysis

In the event where the service is explicitly disabled, ntdll!RtlWerpReportException can’t wake it up and fails with an error status.
如果服务被显式禁用, ntdll!RtlWerpReportException 则无法唤醒它并失败并显示错误状态。

As shown in the pseudocode above, Kernel32!WerpReportFaultInternal takes things into its own hands and calls its own version of CreateCrashKernel32!StartCrashVertical
如上面的伪代码所示,将事情掌握在自己手中并 Kernel32!WerpReportFaultInternal 调用自己的 CreateCrash 版本, Kernel32!StartCrashVertical

CVE-2023-35359 analysis

This function interestingly calls CreateProcessW instead.
有趣的是,此函数会改为调用 CreateProcessW 。

CVE-2023-35359 analysis

As we’ve previously discussed, CreateProcessW is vulnerable to the impersonated device map attack because it uses its impersonation token while trying to look up the process image, but equips the eventual process with its primary token which can be of high privilege.
如前所述,容易受到模拟设备映射攻击,因为它在尝试查找进程映像时使用其模拟令牌, CreateProcessW 但为最终进程配备其主令牌,该令牌可能具有高特权。

CreateProcessAsUserW on the other hand already takes a token handle as an argument.
CreateProcessAsUserW 另一方面,已经将令牌句柄作为参数。

In our case, the primary token of the faulting process is passed to the function, which does not follow our spoofed device map if it’s of high privilege, and thus cannot be exploited.
在我们的例子中,错误过程的主要令牌被传递给函数,如果它具有高特权,则函数不会遵循我们的欺骗设备映射,因此无法被利用。

Expected Patch 预期补丁

I thought the bug probably arised because the developer working on Kernel32!StartCrashVertical is unaware that a similar function exists in Faultrep.dll.
我认为该错误可能是因为正在处理 Kernel32!StartCrashVertical 的开发人员不知道 . Faultrep.dll

My patch would be to either call into Faultrep.dll or replace the CreateProcessW call with a call to CreateProcessAsUserW.
我的补丁是调用 Faultrep.dll 或用调用 CreateProcessAsUserW 替换 CreateProcessW 呼叫。

Microsoft’s eventual patch is much more drastic.
Microsoft的最终补丁要激烈得多。

The Patch 补丁

After receiving news of the patch, I launched procmon to view the stack traces like shown above.
收到补丁的消息后,我启动了 procmon 查看堆栈跟踪,如上所示。

To my surprise, the stack traces are exactly the same as before, which meant that the fix was on a kernel level.
令我惊讶的是,堆栈跟踪与以前完全相同,这意味着修复是在内核级别。

I diffed ntoskrnl.exe before and after the patch and found some additional functions and modifications.
我在补丁 ntoskrnl.exe 之前和之后进行了不同,发现了一些额外的功能和修改。

CVE-2023-35359 analysis CVE-2023-35359 analysis

ObpUseSystemDeviceMap sounds really interesting as an added function, and ObpLookupObjectName is called when loading a process image from disk so the modification to it is likely Microsoft’s patch.
ObpUseSystemDeviceMap 作为一个附加功能听起来非常有趣,并且在 ObpLookupObjectName 从磁盘加载进程映像时调用,因此对它的修改很可能是Microsoft的补丁。

1
2
if ( objectType == IoFileObjectType && ObpUseSystemDeviceMap(ObjectNameRef, ObjectNameLength, a9, v14) )
  finalAttribute = initialAttribute | OBJ_IGNORE_IMPERSONATED_DEVICEMAP;

The patch is applied to ObpLookupObjectName to ignore the device map from the impersonation token if the object to be looked up is a file object and the call to ObpUseSystemDeviceMap succeeds.
如果要查找的对象是文件对象并且调用 ObpUseSystemDeviceMap 成功,则应用修补程序 ObpLookupObjectName 以忽略模拟令牌中的设备映射。

ObpUseSystemDeviceMap ObpUse系统设备地图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool __fastcall ObpUseSystemDeviceMap(PUNICODE_STRING ObjectName, ULONG Length, ULONG64 x, ULONG64 y)
{
  WCHAR *v5; // rax
  WCHAR v6; // di
  WCHAR v7; // si
  WCHAR *v8; // rcx
  bool result; // al

  result = 0;
  if ( (*(_DWORD *)(&KeGetCurrentThread()[1].SwapListEntry + 1) & 8) != 0 && ObjectName->Length >= 0xEu )
  {
    v5 = (WCHAR *)RtlGetNtSystemRoot();
    v6 = RtlUpcaseUnicodeChar(*v5);
    v7 = RtlUpcaseUnicodeChar(ObjectName->Buffer[4]);
    if ( (unsigned int)Feature_MSRC79577_ObpDriveRemappingMitigation__private_IsEnabled() )
    {
      v8 = ObjectName->Buffer;
      if ( v8[5] == ':' && v8[6] == '\\' && v6 == v7 )
        result = 1;
    }
  }
  return result;
}

NT Paths start with \??\ so this just checks if the relative path starts with C:\, assuming C drive as system root.
NT 路径以 开头 \??\ ,因此仅检查相对路径是否以 开头 C:\ ,假设 C 驱动器为系统根目录。

In essence it means that file operations will no longer follow the device map of the impersonated token if the destination lies on a system root drive.
实质上,这意味着如果目标位于系统根驱动器上,则文件操作将不再遵循模拟令牌的设备映射。

In my opinion this successfully eradicates the impersonated device map bug class that’s public for 8 years since James Forshaw’s discovery, how amazing is that!
在我看来,这成功地消除了自 James Forshaw 发现以来公开了 8 年的模拟设备映射错误类,这是多么神奇!

The first publicly documented bug using this technique should be CVE-2015-1644 by James Forshaw.
使用这种技术的第一个公开记录的错误应该是James Forshaw的CVE-2015-1644。

During that period attacks were prevalent on redirecting LoadLibrary calls to load arbitrary DLLs.
在此期间,攻击在重定向 LoadLibrary 调用以加载任意 DLL 时很普遍。

Microsoft subsequently introduced the OBJ_IGNORE_IMPERSONATED_DEVICEMAP flag but only applied it to PE loading operations.
Microsoft随后引入了该标志, OBJ_IGNORE_IMPERSONATED_DEVICEMAP 但仅将其应用于 PE 加载操作。

Bugs from CreateProcessW and even read/write operations existed for many years, with the most recent one being CVE-2023-36874.
来自甚至读/写操作的错误 CreateProcessW 已经存在多年,最近的一次是 CVE-2023-36874。

This bug is patched a month before mine and also concerns WER.
这个错误比我早一个月修补,也涉及 WER。

Instead of coercing a process creation, it exploits a triggerable CreateProcessW call under impersonation to execute arbitrary binaries.
它不是强制创建进程,而是利用模拟下的可 CreateProcessW 触发调用来执行任意二进制文件。

I believe the July patch simply disabled this code path
我相信七月补丁只是禁用了此代码路径

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall CWerComReport::SubmitReport(CWerComReport *this, unsigned __int16 *a2, unsigned int a3, struct IWerReportSubmitCallback *a4, unsigned __int16 **a5, unsigned int *a6)
{
   
   ...

  if ( (unsigned __int8)wil::details::FeatureImpl<__WilFeatureTraits_Feature_MSRC80633_DisableWerCplSupport>::__private_IsEnabled(&`wil::Feature<__WilFeatureTraits_Feature_MSRC80633_DisableWerCplSupport>::GetImpl'::`2'::impl) )
    return 0x80004001i64;

  v11 = CAutoImpersonate::ImpersonateUserHighestPrivs((CAutoImpersonate *)v12);

  ...

}

Not sure what made them pull the trigger this time.
不知道是什么让他们这次扣动了扳机。

Bug Discovery 错误发现

How did I discover this bug?
我是如何发现这个错误的?

Well it’s anti-climatic but I simply rebooted my machine, launched procmon and spoofed my device map while waiting for services to initialize.
好吧,这是反气候的,但我只是重新启动了我的机器,启动了procmon并在等待服务初始化时欺骗了我的设备地图。

One very popular(you could argue inbuilt) third party service threw an exception while on impersonation because it couldn’t find one of its libraries, and I caught it on procmon trying to locate WerFault.exe in my spoofed directory.
一个非常流行(你可以说是内置的)第三方服务在模拟时抛出了一个异常,因为它找不到它的库之一,我在 procmon 上发现了它,试图在我的欺骗目录中找到 WerFault.exe 它。

Nevertheless I’m very grateful to play a part in the mitigation of a bug class and receive my first CVE.
尽管如此,我非常感谢在缓解错误类方面发挥了作用,并收到了我的第一个 CVE。

1337 bugs shall come.
1337个虫子会来。

原文始发于cp:CVE-2023-35359 analysis

版权声明:admin 发表于 2023年9月11日 下午9:42。
转载请注明:CVE-2023-35359 analysis | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...