关于CVE-2020-17087漏洞的研究之旅
2021-02-25 11:45:00 Author: www.4hou.com(查看原文) 阅读量:203 收藏

2020年10月22日,谷歌的安全研究人员在bug.chromium.org上发布了一个有趣的安全报告,标题为“Issue 2104: Windows Kernel cng.sys pool-based buffer overflow”,大致意思是在CNG.sys中发现了一个安全漏洞,而CNG.sys是一个Windows驱动程序,其IOCTL 0x390400导致一个函数容易受到16位整数溢出的影响。实际上,出现安全问题函数是cng!CfgAdtpFormatPropertyBlock,并且,该函数并未被导出。这引起了我的极大兴趣,原因如下所示:

· 该漏洞存在于微软的一个名为CNG.sys的驱动程序

· 影响所有版本的Windows

· 具有WHQL数字签名

· 报告指出“我们有证据表明,这个漏洞正在被用于野外。”

最后一点告诉我,这可以用来实现沙箱逃逸,并将权限升级到System级别。接下来,让我们深入研究一下这个漏洞。

漏洞分析

这个漏洞是由j00ru发布的,并提供了许多有用的信息,我们可以利用这些信息进一步追查这个漏洞。具体来说,他为我们提供的信息包括:调用堆栈、代码片段,以及部分的!analyze命令。下面显示的就是我们将要使用的调用堆栈信息:

cng!CngDispatch
cng!CngDeviceControl
cng!ConfigIoHandler_Safeguarded
cng!ConfigFunctionIoHandler
cng!_ConfigurationFunctionIoHandler
cng!BCryptSetContextFunctionProperty
cng!CfgAdtReportFunctionPropertyOperation
cng!CfgAdtpFormatPropertyBlock

他还发布了一个PoC,具体地址为https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=472684。我的计划是进行相应的动态分析,以确定PoC中各种值的具体含义,并更好地理解其运行机制,这样的话,当我开始修改或添加一些东西时,我就不会破坏任何东西,或改变通往易受攻击的函数的路径了。为了调试CNG,请执行以下命令:

verifier /flags 1 /driver cng.sys

首先,我们可以为cng!CngDispatch设置一个断点,这样当我到达cng!CfgAdtpFormatPropertyBlock时,我就知道它需要的参数是什么。这对于本文后面介绍的针对该漏洞的防御方法时,将特别有用。

需要注意的是,这是Windows的下一代加密驱动,所以系统中的所有东西都会用到它。因此,在为这个驱动程序设置断点后,就会经常触发这个断点,因此请确保仅在PoC上下文中设置断点,否则就会与其他系统线程发生竞争:

ba e1 /p eprocess cng!CngDispatch

一旦断点被触发,并且当前位于CngDispatch中的话,我们就以利用跟踪调用技术,停靠到cng!CngDeviceControl上,这是调用栈的下一项。实际上,CngDispatch的作用是根据IRP_MAJOR_FUNCTION来解析IOCTL信息。由于我们使用的是一个IRP_MJ_DEVICE_CONTROL,所以,我们只关注这部分特殊的代码即可。下面是相关的代码,我们已经使用IDA对其进行了相应的处理,使之更容易理解,更容易操作:

case IRP_MJ_DEVICE_CONTROL:
    ULONG ulIoControlCode = currentStackLocation->Parameters.Read.ByteOffset.LowPart;
    if (METHOD_FROM_CTL_CODE(ulIoControlCode) == METHOD_OUT_DIRECT &&
        currentStackLocation->Parameters.Read.Length)
    {
        --snipped because not important--
    }
    else
    {
        MappedSystemVa = Irp->AssociatedIrp.MasterIrp;
        lpInBuffer = MappedSystemVa;
 
        NumberOfBytes = currentStackLocation->Parameters.Read.Length;
    }
 
    Irp->IoStatus.Status = CngDeviceControl(
        lpInBuffer,
        currentStackLocation->Parameters.Create.Options,
        MappedSystemVa,
        &nOutBufferSize,
        dwIoControlCode,
        Irp->RequestorMode
    );
    Irp->IoStatus.Information = nOutBufferSize;
    break;

简单来说,上面的代码的作用是检查来自控制代码的方法是否是METHOD_OUT_DIRECT类型,并且验证来自用户模式的变量长度是否为NULL。存在安全问题的IOCTL是0x390400,它使用的是METHOD_BUFFERED方法,所以该IF语句里面的所有内容对我们都不适用,我们可以直接跳转到ELSE语句里面的代码。

现在,我们开始跟踪POC代码。准确来说,是考察第159行之后的代码:

if ( dwIoControlCode == 0x390400 )
    return ConfigIoHandler_Safeguarded(a1, a2, a3);
goto LABEL_58;

由于需要查看a1、a2和a3的值,为此,我们可以借助于调试器:

kd> tc
cng!CngDeviceControl+0x8a:
fffff801`4dc361da e85d0a0000      call    cng!ConfigIoHandler_Safeguarded (fffff801`4dc36c3c)
kd> r rcx, rdx, r8
rcx=ffff8909a52d2000 rdx=0000000000003aab r8=ffff8909a52d2000
kd> db ffff8909a52d2000
ffff8909`a52d2000  4d 3c 2b 1a 00 04 01 00-01 00 00 00 00 00 00 00  M<+.............
ffff8909`a52d2010  00 01 00 00 00 00 00 00-03 00 00 00 00 00 00 00  ................
ffff8909`a52d2020  00 02 00 00 00 00 00 00-00 03 00 00 00 00 00 00  ................
ffff8909`a52d2030  00 04 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8909`a52d2040  00 05 00 00 00 00 00 00-00 06 00 00 00 00 00 00  ................
ffff8909`a52d2050  ab 2a 00 00 00 00 00 00-00 10 00 00 00 00 00 00  .*..............

现在,我们知道a1和a3是指向输入缓冲区的指针,a2似乎是表示变量长度,其值为0x2aab+0x1000。

同时,cng!ConfigIoHandler_Safeguarded还会进行一系列的检查,并用BCryptAlloc分配了两块内存,并将输入缓冲区的内容复制到新分配的一块内存中。

BCryptAlloc是CNG内部的一个函数,它是ExAllocatePoolWithTag/SkAllocatePool的封装器,使用的标签为“bgnC”。

然后,它继续将第二个内存块初始化为零,但我感兴趣的是后面调用的函数,即IoUnpack_SG_ParamBlock_Header:

__int64 __fastcall IoUnpack_SG_ParamBlock_Header(PDWORD a1, unsigned int a2, PDWORD a3, _QWORD *a4)
{
--snipped--
  if ( a1 + 2 > (a1 + a2) || *a1 != 0x1A2B3C4D )
    return 1i64;
--snipped--
}

我从中得到的重要部分是0x1a2b3c4d这个值,这一定是某个魔术值。如果不是这个值,这个函数就会失败,从而无法到达易受攻击的代码路径。

接下来是cng!ConfigFunctionIoHandler函数,它接收了六个参数,我可以通过调试器识别它们:

kd> tc
cng!ConfigIoHandler_Safeguarded+0xd4:
fffff801`4dc36d10 e81b010000      call    cng!ConfigFunctionIoHandler (fffff801`4dc36e30)
kd> r rcx, rdx, r8, r9
rcx=0000000000010400 rdx=ffff8909a5919000 r8=0000000000003aab r9=ffff8909a52d2000
kd> db ffff8909a5919000
ffff8909`a5919000  4d 3c 2b 1a 00 04 01 00-01 00 00 00 00 00 00 00  M db ffff8909a52d2000
ffff8909`a52d2000  4d 3c 2b 1a 00 04 01 00-01 00 00 00 00 00 00 00  M dqs rsp + 20 l2
ffffd904`f7bbf0f0  ffffd904`f7bbf150
ffffd904`f7bbf0f8  ffff8909`a5846000

switchstatement.png

其中,RCX被设置为该PoC的第二个DWORD,R8被设置为size+0x1000,RDX和R9是分配的缓冲区,里面存放的是我们的数据。对于另外两个值,我无需关心,并继续向下跟踪,即switch语句。

这里需要注意的是,必须确保我们进入的路径是cng!_ConfigurationFunctionIoHandler函数。您可能已经注意到了,它取的是RCX的高位字,并据此跳转到相应的case x分支。RCX存放的是PoC的第二个DWORD,即0x10400。如果这个寄存器的值不是0x10400,路径就会发生变化,我们就无法到达这个代码路径。另外,switch语句中的其他函数,对于我们来说用处不大,至少从漏洞利用的角度来看是这样。下面,我们开始进入BCrypt*函数。

在ConfigurationFunctionIoHandler中,其实有很多与加密设置相关的事情要做,比如创建上下文、删除上下文、设置上下文、配置等等,但是,当loword等于0x10400或者0x400时,就可以直接把我们带到我们感兴趣的地方:

if ( a1 == 0x400 )
    return BCryptSetContextFunctionProperty(
            dwTable,
            pszContext,
            dwInterface,
            pszFunction,
            pszProperty,
            cbValue,
            pbValue
        );

对于这个函数,稍后还会对其进行探讨。下一个函数用于处理加密的事情,但我们在击中了第192行的cng!CfgAdtReportFunctionPropertyOperation。从现在开始,真正有趣的东西就开始涌现了!

if ( a1 && u16Length && usDestination )
{
    v7 = 6 * u16Length;
    v8 = BCryptAlloc((6 * u16Length));
    v9 = v8;
    if ( v8 )
    {
--snipped--

这里就是j00ru所说的漏洞的发源地。对于uint16,并没有进行任何类型的输入验证,以验证变量是否被溢出。当需要接受用户的输入时,最好进行必要的安全检查;然而,微软并没有这样做!总之,这里只要将用户定义的值乘以6,就很可能会令这种数据类型发生溢出。例如,0x2aab * 6是0x10002,但在内存中被看作是“2”。

上面就是在安全报告中指出的易受攻击的函数,下面,我们不妨试一下PoC,看看有没有新的发现。安全研究人员提供的PoC会触发一个PAGE_FAULT_IN_NONPAGED_AREA错误。据我所知,PoC中输入缓冲区的前两个参数是不能修改的,否则会进入不同的代码路径。我还发现,通过修改其他的一些值,也可以进入不同的代码路径,或者直接失败。我虽然知道输入的内容将会存储到缓冲区中,但不知道具体的位置,但尝试不同的输入长度之后,发现能够触发缓冲区溢出。无论如何,我仍然无法用受控的数据触发崩溃,所以我想到了一个问题:“我真的需要借助于PoC吗?有没有其他方法来触发这个漏洞?”。

意外的发现

于是,我开始检查CNG的导出函数,看看是否有什么东西可以为我所用,结果找到了BCryptSetProperty函数;但是,我没有在导出函数中发现BCryptSetContextFunctionProperty。之后,我开始在MSDN中查找BCryptSetProperty方面的资料,但我看到了更好的东西……具体来说,就是位于同一个调用栈中的另一个函数:BCryptSetContextFunctionProperty,其原型如下所示:

NTSTATUS BCryptSetContextFunctionProperty(
  ULONG   dwTable,
  LPCWSTR pszContext,
  ULONG   dwInterface,
  LPCWSTR pszFunction,
  LPCWSTR pszProperty,
  ULONG   cbValue,
  PUCHAR  pbValue
);

其中,我最感兴趣的是cbValue和pbValue:

· cbValue:表示pbValue缓冲区的大小(以字节为单位),也就是将要存储的数据的确切字节数。如果该属性值为字符串,则应将长度增加一个字符,以在需要时存储终止符null。

· pbValue:表示存放新属性值的缓冲区的地址。

我要做的就是插入API所需的值,并尝试通过将cbValue设置为一个“不怀好意的”长度来使系统发生崩溃。 

bluescreen.png

*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************
 
SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: fffff8041d2d17c0, Address of the instruction which caused the bugcheck
Arg3: fffff80422182920, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.
 
Debugging Details:
------------------
--snipped--
 
READ_ADDRESS:  ffff810b6dcf5000 Special pool
 
MM_INTERNAL_CODE:  2
 
IMAGE_NAME:  cng.sys
 
MODULE_NAME: cng
 
FAULTING_MODULE: fffff8035ea30000 cng
 
PROCESS_NAME:  CVE-2020-17087.exe
 
TRAP_FRAME:  ffffbb8cdd49e770 -- (.trap 0xffffbb8cdd49e770)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=0000000000000020 rbx=0000000000000000 rcx=ffff810b6dcf5000
rdx=ffff810b6dcf4ff0 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8035ea92503 rsp=ffffbb8cdd49e900 rbp=0000000000002aab
 r8=0000000000002aa9  r9=0000000000000002 r10=fffff8035eac7e70
r11=ffffbb8cdd49e850 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei ng nz ac po nc
cng!CfgAdtpFormatPropertyBlock+0xa7:
fffff803`5ea92503 668901          mov     word ptr [rcx],ax ds:ffff810b`6dcf5000=????
Resetting default scope

太好了,机器果然蓝屏了,但我很快就发现调用栈与我所期望的不同:

cng!CfgAdtpFormatPropertyBlock+0xa7
cng!CfgAdtReportFunctionPropertyOperation+0x22d
cng!BCryptSetContextFunctionProperty+0x3a5
cng!_ConfigurationFunctionIoHandler+0x3f34a
cng!ConfigFunctionIoHandler+0x4f
cng!ConfigIoHandler_Safeguarded+0xd9
cng!CngDeviceControl+0x8f
ksecdd!KsecDispatch+0x220
nt!IofCallDriver+0x55
nt!IopSynchronousServiceTail+0x1a8
nt!IopXxxControlFile+0x5e5
nt!NtDeviceIoControlFile+0x56
nt!KiSystemServiceCopyEnd+0x25
ntdll!NtDeviceIoControlFile+0x14
bcrypt!IoCallKernelDriver+0xb5
bcrypt!BCryptSetContextFunctionProperty+0x1a6
CVE_2020_17087+0x23eb

下面看一下执行流程:从BCryptSetContextFunctionsProperty开始,控制权传递给NtDeviceIoControlFile中的内核代码,跳入KsecDispatch,然后传递给Cng,然后在易受攻击的函数处崩溃,只不过,这一次是我的数据导致崩溃的:

0: kd> db ffff810b`6dcf5000 - 0n16
ffff810b`6dcf4ff0  34 00 31 00 20 00 34 00-31 00 20 00 34 00 31 00  4.1. .4.1. .4.1.

或者试图从一个不存在但指向我的数据的地址处执行读取操作时触发崩溃:

CONTEXT:  fffff80422182920 -- (.cxr 0xfffff80422182920)
rax=0031003400200031 rbx=0000000000000001 rcx=00000000f27e7ffd
rdx=0031003400200031 rsi=ffffae0feebecfe0 rdi=ffffae0feebe7000
rip=fffff8041d2d17c0 rsp=ffff85045597ee70 rbp=ffffae0fe6602280
 r8=0000000004060002  r9=412fae0e1b5fe029 r10=0000000000000000
r11=ffffae0fe6602290 r12=0000000000000000 r13=0000000000000000
r14=ffffae0feebece10 r15=0000000000000003
iopl=0         nv up ei pl nz na pe nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00050202
nt!RtlpHpVsFreeChunkInsert+0x170:
fffff804`1d2d17c0 8b4af8          mov     ecx,dword ptr [rdx-8] ds:002b:00310034`00200029=????????
Resetting default scope
 
PROCESS_NAME:  CVE-2020-17087.exe

无论哪种情况,我们都能取得控制权!

最后,在尝试了不同的长度值之后,我触发了以下错误信息:

KERNEL_SECURITY_CHECK_FAILURE (139)
A kernel component has corrupted a critical data structure.  The corruption
could potentially allow a malicious user to gain control of this machine.
Arguments:
Arg1: 000000000000001d, Type of memory safety violation
Arg2: fffffe8e45e3ec30, Address of the trap frame for the exception that caused the bugcheck
Arg3: fffffe8e45e3eb88, Address of the exception record for the exception that caused the bugcheck
Arg4: 0000000000000000, Reserved
--snipped--
rax=0031003400200030 rbx=0000000000000000 rcx=000000000000001d
--snipped--
PROCESS_NAME:  CVE-2020-17087.exe
ERROR_CODE: (NTSTATUS) 0xc0000409 - The system detected an overrun of a stack-based buffer in this application. This overrun could potentially allow a malicious user to gain control of this application.
--snipped--

太好了。

遗憾的是,除此之外,在这个漏洞的利用方面,我没有取得任何进展。说老实话,这个漏洞真是把我难住了!不过,既然我无法利用它,那么,是否能够找到防止这个漏洞被利用的方法呢?

防御措施

这个漏洞是在10月22日被披露的,其中一条评论提到,直到11月10日,也就是位于当月的第二个星期二,才能得到修复,但如果您想保护自己组织免受该漏洞的侵扰的话,该怎么做呢?如果您的用户喜欢点击来路不明的链接,尽管进行了多次安全意识培训也没有效果的话,那该咋整?无论如何,您自己必须提供一个解决方案……

行动计划

我们知道,这个漏洞的根本原因是整数溢出,那么有什么办法进行防御呢?下面是我马上想到的几个思路:

· 钩住CNG的IRP_MJ_DEVICE_CONTROL表

· 通过内联钩子钩住易受攻击的函数本身

如果钩住调度例程函数,则需要重新实现整个函数。就钩子技术而言,这将是最简单的路线,但只要搞砸了哪怕一次,结局也会非常糟糕,因为这会扰乱系统的加密功能。除此之外,钩住CNG的调度表只能防御Project Zero提供的PoC,因为这需要解析DeviceIoControl参数并提取其长度。虽然这种方法能够完美地防御上面提到的PoC,但攻击者使用BCrypt函数触发漏洞时,效果就不理想了,特别是当攻击者使用BCrypt函数时,由于所采取的代码路径不同,这种方法将彻底失效。

如果我在易受攻击的函数上放置一个内联钩子,就会大大降低我搞砸的几率,因为我是在给函数本身打补丁,即添加了一个针对长度检查。这样做的缺点是,CfgAdtpFormatPropertyBlock并非导出函数,因此如果我想动态解析它,则必须为其创建签名。您可以将偏移量添加到基址上,但我个人不喜欢静态的东西。

最终我选择了内联方式钩住CfgAdtpFormatPropertyBlock函数,因为这样的话,就无需关心PoC选择了哪条代码路径,更为重要的是,这种方法出错的机率要更小。

现在的行动计划是创建一个驱动程序,以完成下列任务:

1.劫持CNG.sys的设备对象,以方便解析其基址。

2.为CfgAdtpFormatPropertyBlock创建一个签名。

3.创建钩子存根。

4.动态解析CfgAdtpFormatPropertyBlock。

5.打补丁。

6.如果驱动程序被卸载,则解除该函数的钩子。

我创建的签名是基于20h2的CNG:

1.png

这些字节足以用来唯一识别cng!CfgAdtpFormatPropertyBlock函数。

我使用的钩子存根代码如下所示:

UINT8 __stub_detour[] =
{
    0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   /// mov rax, address
    0xff, 0xd0,                        /// call rax
    0x0f, 0x1f, 0x00                       /// filler
};

上面的代码非常简单。简单来说,钩子的地址会在运行时被复制到八个空字节中,然后放到RAX寄存器中,最后将被调用。我选择使用CALL指令的原因很简单,因为我可以很容易地使用RET指令返回原来的函数。无论如何,我最终得到了12个字节,但由于它只覆盖了一条指令的大部分内容,最后不得不填充上三个字节。

接下来,我必须计划好如何以安全的方式修补这个函数。就这里来说,CNG.sys只提供了读权限,而没有提供写权限,那么,怎样才能在不引起蓝屏的情况下执行写入操作呢?一种方法是获取Control Register 0的值,并禁用写保护位(CR0[WP])。这样做的缺点是,这是基于每个CPU状态进行的,也就是说,如果我在处理器0上禁用了CR0[WP],而一个线程迁移到处理器1上并试图修补cng!CfgAdtpFormatPropertyBlock的话,就会蓝屏,因为CR0[WP]在处理器1上并没有被禁用。因此,我们必须防止线程迁移到另一个CPU上,这样才可以正常修补CfgAdtpFormatPropertyBlock补丁,然后重新启用CR0[WP];然而,这涉及到修改内核组件,这正是我试图避免的。

我也可以尝试禁用Page Table Entry中的Read Only位,但这也需要修改内核组件。除此之外,我们还使用内存描述符列表(MDL)对内存进行双映射。这似乎完全符合我为自己设定的目标,所以,我就选择了这个方法。另外,这样做会更加安全!

PMDL pmdl = IoAllocateMdl(targetAddress, length, FALSE, FALSE, NULL);
if (pmdl != NULL)
{
    __try
    {
        MmProbeAndLockPages(pmdl, KernelMode, IoModifyAccess);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        /// error handling
    }
    PVOID mappedPage = MmMapLockedPagesSpecifyCache(pmdl, KernelMode, MmNonCached, NULL, FALSE, NormalPagePriority);
    if (mappedPage != NULL)
    {
        /// write to memory and clean up
    }
    else
    {
        /// error handling
    }
}

这样做的目的,是用cng!CfgAdtpFormatPropertyBlock的地址分配一个MDL,其长度为x字节,也就是我们的挂钩存根代码的程度。同时,我使用MmProbeAndLockPages来分页并锁定这部分内存,最后,使用MmMapLockedPagesSpecifyCache来映射用IoAllocateMdl创建的MDL所描述的物理页面。简单来说,我所做的事情,就是将cng!CfgAdtpFormatPropertyBlock的虚拟地址重新映射到一个内存区间,该区间存放具有读写权限的同一组物理页面。

现在,MDL已经映射好了,那么,我们的目标是否已经达成了呢?看看吧。

2.png

在确认我们的确能够对内核内存执行写入操作后,接下来我们要做的事情,就是开发实际的代码来防范这个漏洞。下面,我们给出相应的代码:

.code
 
__detoured_function proc
        add rsp, 8         ; adjust rsp to load the correct values
        mov rax, rsp           ; restore the first overwritten instruction
        sub rsp, 8         ; readjust for the ret
        mov qword ptr [rax + 8h], rbx  ; restore the second overwritten instruction
        mov qword ptr [rax + 10h], rbp ; restore the third overwritten instruction
        mov qword ptr [rax + 18h], rsi ; restore the fourth overwritten instruction
        cmp dx, 2aabh
        jge status_unsuccessful
        ret
 
status_unsuccessful:
        xor rcx, rcx           ; force it to fail
        ret
__detoured_function endp
 
end

首先,我们需要恢复被覆盖的指令,以使这个钩子可以正常工作,同时,由于这里使用了CALL指令,因此,也需要对RSP稍作修改。重要的是,我不仅知道使用哪种类型的钩子,还知道寄存器在期待什么样的值。接下来,让我们看看函数正常调用时,堆栈的布局情况如下所示:

1: kd> dps rsp l5
ffffd586`90a26978  fffff802`44291e39 cng!CfgAdtReportFunctionPropertyOperation+0x22d
ffffd586`90a26980  00000000`00000000
ffffd586`90a26988  00000000`00000001
ffffd586`90a26990  000000ed`00000000
ffffd586`90a26998  ffffd586`90a26a80

就这里来说,只要把cng!CfgAdtReportFunctionPropertyOperation+0x22d放到RAX中,就可以了。在第4行到第6行,我们通过8对RSP进行相应的调整,得到的正是RAX的期望值,然后,还需要重新调整进行相应的调整。重要的部分是第10行和第11行。此时,寄存器的当前状态如下所示:

1: kd> r
rax=0000000000000000 rbx=0000000000000000 rcx=ffffc206e9a85000
rdx=0000000000002aab rsi=ffffc206e9a85000 rdi=0000000000000001
rip=fffff8024429245c rsp=ffffd58690a26978 rbp=ffffd58690a26a80
 r8=ffffd58690a269c8  r9=ffffd58690a26ed0 r10=0000000000000004
r11=0000000000000000 r12=ffffc206e9a85000 r13=ffffd58690a26f38
r14=0000000000000003 r15=ffffd58690a26f28

在RDX寄存器中,保存的是分配空间的大小。如果DX大于或等于0x2aab,我们将跳转到status_unsuccessful。原始的cng!CfgAdtpFormatPropertyBlock函数会进行相应的检查,以确保源缓冲区、长度和目标字符串参数不是NULL;否则的话,就会返回STATUS_INVALID_PARAMETER或STATUS_INVALID_RESOURCES。实际上,最简单的做法就是在第一次检查时失败,直接以STATUS_INTEGER_OVERFLOW这种方式失败,然后退出。我的做法是将RCX设置为NULL,迫使程序因STATUS_INVALID_PARAMETER检查失败而退出。

3.png

经过一番折腾,我们终于在遥远的补丁程序发布之前建好了自己的一道篱笆!

5.png

小结

在本文中,我们介绍了Project Zero披露的CNG漏洞,并研究了PoC的内部机制。同时,我们找到了一种不同的方式来触发这个漏洞:我们能够控制触发崩溃的数据。然后,我们介绍了针对该漏洞的防御措施。总的来说,虽然这里没有成功利用该漏洞,但我们成功实施了一个针对报告提供的PoC的防御措施。

本文翻译自:https://ch3rn0byl.com/2021/02/a-look-at-cve-2020-17087/如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/j5nW
如有侵权请联系:admin#unsafe.sh