-
-
CVE-2020-17087整数溢出漏洞分析
-
2022-3-15 14:04 20974
-
看到GPZ很久之前披露的一个在野cng.sys驱动漏洞,本菜鸟尝试对其进行简单分析,希望能从中学习到一些知识
漏洞描述
漏洞概述:
内核驱动模块cng.sys
中存在整数溢出漏洞,利用此漏洞进行越界读写,最终可实现本地提取,此漏洞被黑客用于Chrome Sandbox Escape(CVE-2020-15999)
影响的windows版本:
Windows 10 2004以及之前的版本
漏洞分析
Windows版本:win10 1903 10.0.18362.836
根据GPZ给出的Poc,可以定位到漏洞函数cng!CfgAdtpFormatPropertyBlock
,size
可以被用户控制,6 * size
的结果强行转换为无符号的16位整数,0x10000 // 0x6
的结果为0x2aa,0x10000 / 6
的结果为10922.666666666666,假设控制size的大小为0x2ab,将其乘上6就溢出为2,BCryptAlloc
函数内部会调用ExAllocatePoolWithTag或者SkAllocatePool在类型为NonPagedPoolNx池动态申请池空间,此时NumberOfBytes
为2,申请的空间就十分小,然而do-while循环的次数为size(即0x2ab),每次向池空间写入6字节,最终导致越界写入,触发BSOD
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | __int64 __fastcall CfgAdtpFormatPropertyBlock(char * a1, unsigned __int16 size, __int64 a3) { unsigned int v3; / / ebx char * v6; / / r14 __int16 v7; / / di _WORD * pool_ptr; / / rax _WORD * v9; / / rdx _WORD * pool_ptr_w; / / rcx __int64 count; / / r8 _WORD * v12; / / rcx char v13; / / al v3 = 0 ; v6 = a1; if ( a1 && size && a3 ) { v7 = 6 * size; pool_ptr = BCryptAlloc((unsigned __int16)( 6 * size)); / / 6 * 0x2aab = 2 v9 = pool_ptr; if ( pool_ptr ) { pool_ptr_w = pool_ptr; if ( size ) { count = size; do { / / store 6 bytes every time * pool_ptr_w = (unsigned __int8)a0123456789abcd[(unsigned __int64)(unsigned __int8) * v6 >> 4 ]; v12 = pool_ptr_w + 1 ; v13 = * v6 + + ; * v12 + + = (unsigned __int8)a0123456789abcd[v13 & 0xF ]; * v12 = 0x20 ; pool_ptr_w = v12 + 1 ; - - count; } while ( count ); } * (_QWORD * )(a3 + 8 ) = v9; * (_WORD * )(a3 + 2 ) = v7; * (_WORD * )a3 = v7 - 2 ; } else { return 0xC000009A ; } } else { return 0xC000000D ; } return v3; } PVOID __fastcall BCryptAlloc(SIZE_T NumberOfBytes) { char DeviceContext; / / al DeviceContext = (char)WPP_MAIN_CB.Queue.Wcb.DeviceContext; if ( !LODWORD(WPP_MAIN_CB.Queue.Wcb.DeviceContext) ) DeviceContext = GetTrustedEnvironment(); if ( (DeviceContext & 2 ) ! = 0 ) return (PVOID)SkAllocatePool( 0x200i64 , NumberOfBytes, 'bgnC' ); / / NonPagedPoolNx else return ExAllocatePoolWithTag((POOL_TYPE) 0x200 , NumberOfBytes, 'bgnC' ); / / NonPagedPoolNx } |
漏洞调试
cng!CngDispatch
从驱动的cng!DriverEntry入口函数入手,发现好几个MajorFunction都设置为cng!CngDispatch
1 2 3 4 5 6 7 | DriverObject - >MajorFunction[ 0 ] = (PDRIVER_DISPATCH)CngDispatch; DriverObject - >MajorFunction[ 2 ] = (PDRIVER_DISPATCH)CngDispatch; DriverObject - >MajorFunction[ 3 ] = (PDRIVER_DISPATCH)CngDispatch; DriverObject - >MajorFunction[ 4 ] = (PDRIVER_DISPATCH)CngDispatch; DriverObject - >MajorFunction[ 5 ] = (PDRIVER_DISPATCH)CngDispatch; DriverObject - >MajorFunction[ 0xA ] = (PDRIVER_DISPATCH)CngDispatch; DriverObject - >MajorFunction[ 0xE ] = (PDRIVER_DISPATCH)CngDispatch; |
跟进cng!CngDispatch函数,可以看到switch中以Major Function Code作为条件,当前案例中关注的是case 0xe,宏IRP_MJ_DEVICE_CONTROL的值为0xe,用户态调用DeviceIoControl这个API最终执行到内核驱动模块中的DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]
IoCode即为IO控制码,从崩溃的栈帧回溯的执行路径获悉IoCode为0x390400,但是很遗憾,我的分析属于一种根据结果来推断过程(根据崩溃的栈帧),这也是四哥痛骂的那波人之一(我太菜了),代码审计的逻辑是从cng!CngDispatch函数开始,进入到cng!CngDeviceControl函数中根据不同IoCode进行递归,然后进行代码审计。本身来说这个漏洞成因相对简单,需要学习的是寻找触发漏洞路径
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 | switch ( CurrentStackLocation - >MajorFunction ) { ... case 0xEu : IoCode = CurrentStackLocation - >Parameters.Read.ByteOffset.LowPart; if ( (IoCode & 3 ) = = 2 && CurrentStackLocation - >Parameters.Read.Length ) { ... } else { OutputBuffer = irp - >AssociatedIrp.MasterIrp; InputBuffer = OutputBuffer; OutputLen = CurrentStackLocation - >Parameters.Read.Length; } irp - >IoStatus.Status = CngDeviceControl( InputBuffer, CurrentStackLocation - >Parameters.Create.Options, / / InputLen OutputBuffer, &OutputLen, IoCode, irp - >RequestorMode); irp - >IoStatus.Information = OutputLen; break ; } |
cng!CngDeviceControl
在cng!CngDispatch函数中下断点,然后查看一下cng!CngDeviceControl函数的6个参数,分别为输入缓冲区、输入缓冲区长度、输出缓冲区、输出缓冲区长度的指针、IO控制码、请求模式
1 | 1 : kd> ba e1 / w "@$curprocess.Name == \"poc.exe\"" cng!CngDispatch + 85 |
从上图的第5个参数可以看到传入的IoCode为0x390400,因为抵达漏洞函数需要执行过函数cng!ConfigIoHandler_Safeguarded,cng!CngDeviceControl函数中存在这样的判断
1 2 | if ( IoCode = = 0x390400 ) return ConfigIoHandler_Safeguarded(InputBuffer, NumberOfBytes, (IRP * )OutputBuffer, OutputLen); |
cng!ConfigIoHandler_Safeguarded
cng!ConfigIoHandler_Safeguarded使用的参数为cng!CngDeviceControl前四个参数:rcx,rdx,r8,r9
跟进cng!ConfigIoHandler_Safeguarded函数,可以看到函数根据输入缓冲区的长度申请了两个大小相同的池块,姑且称为pool1、pool2,将输入缓冲区的内容拷贝至pool1,pool2初始化为0,在后续的分析过程,将pool1的首地址称为pool_ptr1,pool2的首地址称为pool_ptr2
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 50 51 52 53 | __int64 __fastcall ConfigIoHandler_Safeguarded( PVOID InputBuffer, SIZE_T InputLen, PVOID OutputBuffer, ULONG * OutputLen) { ... size = InputLen; if ( OutputBuffer ) v7 = * OutputLen; / / v7 = 8 else v7 = 0 ; * OutputLen = 0 ; v8 = 8 ; len = v7; / / len = 8 if ( v7 < 8 ) { if ( v7 ) memset(OutputBuffer, 0 , v7); return 0xC0000023 ; } else { v9 = (unsigned int )InputLen; pool_ptr1 = BCryptAlloc((unsigned int )InputLen); / / allocate pool1 v11 = BCryptAlloc(v9); / / allocate pool2 pool_ptr2 = v11; if ( pool_ptr1 && v11 ) { memmove(pool_ptr1, InputBuffer, v9); / / copy InputBuffer to pool1 memset(pool_ptr2, 0 , v9); / / clear pool2 v13 = IoUnpack_SG_ParamBlock_Header(pool_ptr1, size, &v19, pool_ptr2); if ( v13 ) { v15 = WinErrorToNtStatus(v13); } else { v14 = ConfigFunctionIoHandler( v19, ( int )pool_ptr1, size, (struct _CRYPT_CONTEXT_FUNCTION_PROVIDERS * )OutputBuffer, & len , (__int64)pool_ptr2); ... } ... } ... } |
在调试器中查看执行memmove前后的变化,验证以上说法
cng!IoUnpack_SG_ParamBlock_Header
从伪代码中可以得知调用cng!IoUnpack_SG_ParamBlock_Header的返回值必须为0,否则函数直接返回,于是继续跟进cng!IoUnpack_SG_ParamBlock_Header,函数首先进行pool_ptr1 + 2 < pool_ptr1的判断,然后又进行pool_ptr1 + 2 > (_DWORD )((char )pool_ptr1 + size这样的判断,不太清楚这样做的意义,获取pool1的前4字节,与0x1A2B3C4D
进行比较,因此输入缓冲区的前4字节设置为0x1A2B3C4D,第3个参数所指内存会保存pool1的第二个4字节,数值为0x10400,for循环内会将pool2的前8字节置位
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 | __int64 __fastcall IoUnpack_SG_ParamBlock_Header(_DWORD * pool_ptr1, unsigned int size, _DWORD * a3, _QWORD * pool_ptr2) { ... v4 = 0 ; if ( !pool_ptr1 ) return 1i64 ; if ( pool_ptr1 + 2 < pool_ptr1 ) return 1i64 ; v5 = size; if ( pool_ptr1 + 2 > (_DWORD * )((char * )pool_ptr1 + size) || * pool_ptr1 ! = 0x1A2B3C4D ) return 1i64 ; if ( a3 ) * a3 = pool_ptr1[ 1 ]; if ( !pool_ptr2 ) return 0i64 ; v6 = pool_ptr2 + 1 ; if ( pool_ptr2 + 1 > = pool_ptr2 ) { if ( v6 < = (_QWORD * )((char * )pool_ptr2 + size) ) { v7 = 0 ; for ( i = pool_ptr2; ! * i; + + i ) { if ( (unsigned int ) + + v7 > = 8 ) { * pool_ptr2 = 0xFFFFFFFFFFFFFFFFui64 ; return 0i64 ; } ... } ... } ... } ... } |
查看pool2在执行完循环前后的变化
1 2 3 4 5 6 | 1 : kd> dq @rax l4 ffffbb0f` 7e160000 00000000 ` 00000000 00000000 ` 00000000 ffffbb0f` 7e160010 00000000 ` 00000000 00000000 ` 00000000 1 : kd> dq @r9 l4 ffffbb0f` 7e160000 ffffffff`ffffffff 00000000 ` 00000000 ffffbb0f` 7e160010 00000000 ` 00000000 00000000 ` 00000000 |
cng!ConfigFunctionIoHandler
回到cng!ConfigIoHandler_Safeguarded函数,函数继续调用cng!ConfigFunctionIoHandler函数,第一个参数传入的值为0x10400,HIWORD(a1)返回的结果为1,就会进入到cng!ConfigurationFunctionIoHandler函数,a2、a3、a4、a5、a6对应的是pool_ptr1、InputLen、OutputBuffer、&OutputLen、pool_ptr2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | NTSTATUS __fastcall ConfigFunctionIoHandler( unsigned int a1, int a2, unsigned int a3, struct _CRYPT_CONTEXT_FUNCTION_PROVIDERS * a4, _DWORD * a5, __int64 a6) { switch ( HIWORD(a1) ) { case 0u : return RegistrationFunctionIoHandler(a1, a2, a3, ( int )a4, (ULONG)a5, a6); case 1u : return ConfigurationFunctionIoHandler(a1, a2, a3, a4, a5, a6); case 2u : return ResolutionFunctionIoHandler(a1, a2, a3, ( int )a4, (ULONG)a5, a6); } return 0xC00000AF ; } |
在调试器中查看cng!ConfigurationFunctionIoHandler函数的参数
cng!ConfigurationFunctionIoHandler
cng!ConfigurationFunctionIoHandler函数开头调用cng!IoUnpack_SG_Configuration_ParamBlock,从函数名跟上文的分析来看,该函数会对池块的某些部分进行一些必要的检查并写入一些值,函数的返回值依然需要为0
cng!IoUnpack_SG_Configuration_ParamBlock
跟进cng!IoUnpack_SG_Configuration_ParamBlock函数,先对pool1的大小与8进行对比,由于我们使用的大小为0x3aab,还不能直接返回0,将pool1的首地址加上0x68得到v18,当前实例中保证pool1 < v18 < (pool1 + poolsize),紧接着就是一连串的判断后决定是否调用cng!IoUnpack_SG_SzString、cng!IoUnpack_SG_ContextFunctionConfig、cng!IoUnpack_SG_Buffer函数,由于a4、a6、a7等都为变量的地址(不为0),故都会进入分支执行这些函数
在调试器中查看一下第一次执行完cng!IoUnpack_SG_SzString函数后的变化
在调试器中查看一下最后一次执行完cng!IoUnpack_SG_ContextFunctionConfig函数后的变化
在调试器中查看一下执行完cng!IoUnpack_SG_Buffer函数后的变化
从上面几张图片可以看到,执行完cng!IoUnpack_SG_SzString、cng!IoUnpack_SG_ContextFunctionConfig、cng!IoUnpack_SG_Buffer函数后,缓冲区偏移0x10、0x18、0x20、0x28、0x30、0x40、0x48、0x58的内容在原有的基础上加上了pool1的首地址
cng!IoUnpack_SG_Buffer
对cng!IoUnpack_SG_Buffer函数进行简单的分析,可以发现函数内向pool_ptr2偏移0x58字节写入8字节的0xff,在pool_ptr2偏移poi(pool_ptr1 + 0x58)(当前数值为0x1000)处开始写入poi(pool_ptr1 + 0x50)(当前数值为0x2ab)字节的0xff
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | ... if ( pool_ptr1_off58 ) { v9 = (__int64)pool_ptr1_off58 - pool_ptr1; if ( pool_ptr2 ) { v10 = (char * )(v9 + pool_ptr2); / / v10 = pool_ptr2 + 0x58 if ( v9 + pool_ptr2 > = pool_ptr2 ) { if ( v10 + 8 > = v10 && (unsigned __int64)(v10 + 8 ) < = pool_ptr2 + size ) { v11 = 0 ; v12 = (char * )(v9 + pool_ptr2); / / v12 = pool_ptr2 + 0x58 while ( ! * v12 ) { + + v11; + + v12; if ( v11 > = 8 ) { * (_QWORD * )v10 = 0xFFFFFFFFFFFFFFFFui64 ; / / * (pool_ptr2 + 0x58 ) = 0xFFFFFFFFFFFFFFFF goto LABEL_10; } ... } ... } ... } ... LABEL_10: v13 = 0i64 ; if ( * pool_ptr1_off58 ! = 0xFFFFFFFFFFFFFFFFui64 ) v13 = * pool_ptr1_off58 + pool_ptr1; * pool_ptr1_off58 = v13; / / * (pool_ptr1 + 0x58 ) + = pool_ptr1 } if ( !v13 ) return 0i64 ; if ( v13 > = pool_ptr1 && pool_ptr1_off50_val + v13 > = v13 && pool_ptr1_off50_val + v13 < = size + pool_ptr1 ) { v17 = v13 - pool_ptr1; / / v17 = * (pool_ptr1 + 0x58 ) - pool_ptr1 = 0x1000 if ( pool_ptr2 ) { v18 = (void * )(v17 + pool_ptr2); if ( v17 + pool_ptr2 > = pool_ptr2 ) { v19 = pool_ptr2 + pool_ptr1_off50_val + v17; / / v19 = pool_ptr2 + 0x1000 + 0x2aab if ( v19 > = (unsigned __int64)v18 && v19 < = v6 + pool_ptr2 ) { v20 = 0 ; if ( !(_DWORD)pool_ptr1_off50_val ) { LABEL_37: memset((void * )(v17 + pool_ptr2), 0xFF , pool_ptr1_off50_val); return 0i64 ; } ... } ... } ... } ... } ... |
在调试器中查看pool2的内容进行验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 1 : kd> dq 0xffffbb0f7e160058 l1 ffffbb0f` 7e160058 ffffffff`ffffffff 1 : kd> dq 0xffffbb0f7e161000 ffffbb0f` 7e161000 ffffffff`ffffffff ffffffff`ffffffff ffffbb0f` 7e161010 ffffffff`ffffffff ffffffff`ffffffff ffffbb0f` 7e161020 ffffffff`ffffffff ffffffff`ffffffff ffffbb0f` 7e161030 ffffffff`ffffffff ffffffff`ffffffff ffffbb0f` 7e161040 ffffffff`ffffffff ffffffff`ffffffff ffffbb0f` 7e161050 ffffffff`ffffffff ffffffff`ffffffff ffffbb0f` 7e161060 ffffffff`ffffffff ffffffff`ffffffff ffffbb0f` 7e161070 ffffffff`ffffffff ffffffff`ffffffff 1 : kd> dq 0xffffbb0f7e163aab - 10 ffffbb0f` 7e163a9b ffffffff`ffffffff ffffffff`ffffffff ffffbb0f` 7e163aab dcde6800` 00000000 000000ff ` 8d6f0e3e ffffbb0f` 7e163abb 00000000 ` 00000000 15fab900 ` 00000000 ffffbb0f` 7e163acb 000000ff `ffbb0f7e 00000000 ` 00000000 ffffbb0f` 7e163adb 00000000 ` 00000000 00000000 ` 00000000 ffffbb0f` 7e163aeb 00000000 ` 00000000 00000000 ` 00000000 ffffbb0f` 7e163afb 00000000 ` 00000000 00000000 ` 00000000 ffffbb0f` 7e163b0b 00000000 ` 00000000 00000000 ` 00000000 |
执行完以上部分,紧接着就是一组赋值操作,然后返回0
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 | if ( a3 ) * a3 = * (_DWORD * )(pool_ptr1 + 8 ); if ( a4 ) * a4 = * (_QWORD * )(pool_ptr1 + 0x10 ); if ( a5 ) * a5 = * (_DWORD * )(pool_ptr1 + 0x18 ); if ( a6 ) * a6 = * (_QWORD * )(pool_ptr1 + 0x20 ); if ( a7 ) * a7 = * (_QWORD * )(pool_ptr1 + 0x28 ); if ( a8 ) * a8 = * (_QWORD * )(pool_ptr1 + 0x30 ); if ( a9 ) * a9 = * (_DWORD * )(pool_ptr1 + 0x38 ); if ( a10 ) * a10 = * (_QWORD * )(pool_ptr1 + 0x40 ); if ( a11 ) * a11 = * (_QWORD * )(pool_ptr1 + 0x48 ); if ( a12 ) * a12 = * (_DWORD * )(pool_ptr1 + 0x50 ); if ( a13 ) * a13 = * (_QWORD * )(pool_ptr1 + 0x58 ); if ( a14 ) * a14 = * (_QWORD * )(pool_ptr1 + 0x60 ); return 0i64 ; |
返回到cng!ConfigurationFunctionIoHandler函数后,由于输入缓冲区的第二个四字节0x10400被截断为0x400,就会执行到这样的分支
1 2 3 4 5 6 7 8 9 | if ( a1 = = 0x400 ) return BCryptSetContextFunctionProperty( dwTable, / / * a3 = * (_DWORD * )(pool_ptr1 + 8 ) pszContext, / / * a4 = * (_QWORD * )(pool_ptr1 + 0x10 ) dwInterface, / / * a5 = * (_DWORD * )(pool_ptr1 + 0x18 ) pszFunction, / / * a6 = * (_QWORD * )(pool_ptr1 + 0x20 ) pszProperty, / / * a8 = * (_QWORD * )(pool_ptr1 + 0x30 ) cbValue, / / * a12 = * (_DWORD * )(pool_ptr1 + 0x50 ) pbValue); / / * a13 = * (_QWORD * )(pool_ptr1 + 0x58 ) |
cng!BCryptSetContextFunctionProperty
跟进cng!BCryptSetContextFunctionProperty函数,继续分析此函数,cng!ValidateTableId所在的分支无法满足条件,利用cbValue和pbValue的值初始化DestinationString,利用此DestinationString调用cng!CfgReg_Acquire
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 | NTSTATUS __stdcall BCryptSetContextFunctionProperty( ULONG dwTable, / / pool_ptr1_off_8 LPCWSTR pszContext, / / pool_ptr1_off_10 ULONG dwInterface, / / pool_ptr1_off_18 LPCWSTR pszFunction, / / pool_ptr1_off_20 LPCWSTR pszProperty, / / pool_ptr1_off_30 ULONG cbValue, / / pool_ptr1_off_50 PUCHAR pbValue) / / pool_ptr1_off_58 { ... * (_QWORD * )&DestinationString.Length = 0i64 ; v36 = 0 ; v34 = 0 ; v35 = 0 ; v37 = 0i64 ; v38 = 0i64 ; v39 = 0i64 ; DestinationString. Buffer = 0i64 ; v42 = 0i64 ; if ( !ValidateTableId(dwTable) / / dwTable = 1 , false || !(unsigned int )ValidateInterfaceId(dwInterface, 0 ) / / dwInterface = 3 , false || !v13 / / pszFunction, false || * v13 = = (_WORD)v12 / / * pszFunction ! = 0 , false || !pszProperty / / pszProperty, false || * pszProperty = = (_WORD)v12 ) / / * pszProperty ! = 0 , false { v16 = 0x57 ; LABEL_36: if ( !v10 ) goto LABEL_38; goto LABEL_37; } if ( cbValue && pbValue ) / / true { * (_QWORD * )&DestinationString.Length = pbValue; LODWORD(DestinationString. Buffer ) = cbValue; HIDWORD(DestinationString. Buffer ) = cbValue; LODWORD(v42) = v12; } v14 = CfgReg_Acquire(v11, dwTable, 0x3001Fi64 ); ... } |
cng!CfgReg_Acquire
进一步跟进cng!CfgReg_Acquire,发现cng!VerifyRegistryAccess函数尝试对System\CurrentControlSet\Control\Cryptography\Configuration\Local注册表项执行操作,cng!VerifyRegistryAccess内部调用cng!KeRegOpenKey尝试打开注册表表项
根据cng!VerifyRegistryAccess的返回值为5得知打开失败,函数将5返回给cng!BCryptSetContextFunctionProperty
紧接着在cng!BCryptSetContextFunctionProperty的执行流程大体如下,分别使用pool1_ptr+0x100、pool1_ptr+0x200、pool1_ptr+0x400处的字符初始化三个新的UnicodeString
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 | ... v14 = CfgReg_Acquire(v11, dwTable, 0x3001Fi64 ); / / v14 = 5 v16 = v14; if ( v14 ) { v17 = v14 = = 5 ; / / v17 = true goto LABEL_43; } ... LABEL_43: if ( !v17 ) goto LABEL_49; } * (_QWORD * )&DestinationString.Length = 0i64 ; DestinationString. Buffer = 0i64 ; * (_QWORD * )&v40.Length = 0i64 ; v40. Buffer = 0i64 ; * (_QWORD * )&v43.Length = 0i64 ; v43. Buffer = 0i64 ; RtlInitUnicodeString(&DestinationString, pszContext); / / pool_ptr1_off_10 RtlInitUnicodeString(&v40, pszFunction); / / pool_ptr1_off_20 RtlInitUnicodeString(&v43, pszProperty); / / pool_ptr1_off_30 |
cng!CfgAdtReportFunctionPropertyModification
在初始化三个UnicodeString后,进入下面的分支调用cng!CfgAdtReportFunctionPropertyModification函数
在调试器中查看一下函数的参数,最少有10个,IDA反编译出来的结果中有11个
从上图中可以看到,在rsp+40的位置存储了0x2aab,这个数值来自于pool_ptr1 + 0x50的位置,查看一下寄存器r14w的数据来源,可以看到函数序言部分将pool_ptr1 + 0x50处的值赋给r14d
cng!CfgAdtReportFunctionPropertyModification函数内部调用最终的漏洞函数cng!CfgAdtpFormatPropertyBlock,调试器中查看一下cng!CfgAdtpFormatPropertyBlock的参数,可以看到第二个参数来自于cng!CfgAdtReportFunctionPropertyModification的第九个参数,数值为0x2aab
cng!CfgAdtpFormatPropertyBlock
由于漏洞原理在开篇提及,此处就对一些关键位置查看下。查看一下cng!BCryptAlloc的参数,果然溢出为2
调用完该函数申请到的池块大小为0x20字节
然而循环的次数为0x2aab次,每次向池块中写入6字节,最终就会覆写到相邻的内存,最终导致BSOD
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 | NOTE: The trap frame does not contain all registers. Some register values may be zeroed or incorrect. rax = 0000000000000030 rbx = 0000000000000000 rcx = ffffda02a29ff000 rdx = ffffda02a29fe610 rsi = 0000000000000000 rdi = 0000000000000000 rip = fffff80063ae2904 rsp = ffffd7804ace5f20 rbp = 0000000000002aab r8 = 0000000000002903 r9 = 0000000000000002 r10 = fffff80063b1ce70 r11 = 0000000000000002 r12 = 0000000000000000 r13 = 0000000000000000 r14 = 0000000000000000 r15 = 0000000000000000 iopl = 0 nv up ei pl zr ac po nc cng!CfgAdtpFormatPropertyBlock + 0x88 : fffff800` 63ae2904 668901 mov word ptr [rcx],ax ds:ffffda02`a29ff000 = ???? Resetting default scope STACK_TEXT: ffffd780` 4ace5348 fffff800` 62cadef2 : ffffda02`a29ff000 00000000 ` 00000003 ffffd780` 4ace54b0 fffff800` 62b292f0 : nt!DbgBreakPointWithStatus ffffd780` 4ace5350 fffff800` 62cad5e7 : fffff800` 00000003 ffffd780` 4ace54b0 fffff800` 62bdc3f0 ffffd780` 4ace59f0 : nt!KiBugCheckDebugBreak + 0x12 ffffd780` 4ace53b0 fffff800` 62bc7de7 : fffff800` 62e694f8 fffff800` 62cd7a45 ffffda02`a29ff000 ffffda02`a29ff000 : nt!KeBugCheck2 + 0x947 ffffd780` 4ace5ab0 fffff800` 62c0d19e : 00000000 ` 00000050 ffffda02`a29ff000 00000000 ` 00000002 ffffd780` 4ace5d90 : nt!KeBugCheckEx + 0x107 ffffd780` 4ace5af0 fffff800` 62a9a59f : fffff800` 62a05000 00000000 ` 00000002 00000000 ` 00000000 ffffda02`a29ff000 : nt!MiSystemFault + 0x19dcee ffffd780` 4ace5bf0 fffff800` 62bd5d5e : 00000000 ` 00000000 00000000 ` 00000000 ffffda02`a2f84000 00000000 ` 00000fff : nt!MmAccessFault + 0x34f ffffd780` 4ace5d90 fffff800` 63ae2904 : ffffd780` 4ace5fe0 ffffd780` 4ace6508 ffffda02`a2f84000 ffff062f` 1ac23f52 : nt!KiPageFault + 0x35e ffffd780` 4ace5f20 fffff800` 63ae224e : 00000000 ` 00000000 ffffd780` 4ace6050 ffffda02`a2f84000 00000000 ` 00000001 : cng!CfgAdtpFormatPropertyBlock + 0x88 ffffd780` 4ace5f50 fffff800` 63ae0282 : 00000000 ` 00000005 ffffd780` 4ace6720 ffffda02`a2f84000 ffffda02`a2f83200 : cng!CfgAdtReportFunctionPropertyOperation + 0x23e ffffd780` 4ace6470 fffff800` 63ac9580 : ffffd780` 4ace6720 ffffda02`a2f83100 ffffd780` 4ace65f0 ffffda02`a2f83200 : cng!BCryptSetContextFunctionProperty + 0x3a2 ffffd780` 4ace6570 fffff800` 63a92e86 : 00000000 ` 00003aab 00000000 ` 00000008 00000000 ` 00003aab ffffda02`a2f7f000 : cng!_ConfigurationFunctionIoHandler + 0x3bd5c ffffd780` 4ace6660 fffff800` 63a92d22 : 00000000 ` 00003aab fffff800` 62a5c339 ffffda02`a2f82fe0 00000000 ` 00000004 : cng!ConfigFunctionIoHandler + 0x4e ffffd780` 4ace66a0 fffff800` 63a91567 : 00000000 ` 00000000 fffff800` 00003aab 00000000 ` 00000000 00000000 ` 00010400 : cng!ConfigIoHandler_Safeguarded + 0xd2 ffffd780` 4ace6710 fffff800` 63a8e0ea : 00000000 ` 00000000 ffffda02`a2295ed0 ffffda02`a2295e00 00000000 ` 00000000 : cng!CngDeviceControl + 0x97 ffffd780` 4ace67e0 fffff800` 62a314e9 : ffffda02`a2295e00 00000000 ` 00000000 00000000 ` 00000002 00000000 ` 00000001 : cng!CngDispatch + 0x8a ffffd780` 4ace6820 fffff800` 62fd6a55 : ffffd780` 4ace6b80 ffffda02`a2295e00 00000000 ` 00000001 ffffda02`a2a34820 : nt!IofCallDriver + 0x59 ffffd780` 4ace6860 fffff800` 62fd6860 : 00000000 ` 00000000 ffffd780` 4ace6b80 ffffda02`a2295e00 ffffd780` 4ace6b80 : nt!IopSynchronousServiceTail + 0x1a5 ffffd780` 4ace6900 fffff800` 62fd5c36 : 000002e2 ` 391a3000 00000000 ` 00000000 00000000 ` 00000000 00000000 ` 00000000 : nt!IopXxxControlFile + 0xc10 ffffd780` 4ace6a20 fffff800` 62bd9558 : 00000000 ` 00000000 00000000 ` 00000000 00000000 ` 00000000 000000fa ` 99aff5e8 : nt!NtDeviceIoControlFile + 0x56 ffffd780` 4ace6a90 00007ffc ` 3233c1a4 : 00007ffc ` 2fe7eaa7 00000000 ` 00000000 0000ed39 ` 9d02f136 00000000 ` 00000000 : nt!KiSystemServiceCopyEnd + 0x28 000000fa ` 99aff918 00007ffc ` 2fe7eaa7 : 00000000 ` 00000000 0000ed39 ` 9d02f136 00000000 ` 00000000 000000fa ` 99aff940 : ntdll!NtDeviceIoControlFile + 0x14 000000fa ` 99aff920 00007ffc ` 32016430 : 00000000 ` 00390400 00000000 ` 00000024 00007ff7 ` 98f322b8 000000fa ` 99aff9e8 : KERNELBASE!DeviceIoControl + 0x67 000000fa ` 99aff990 00007ff7 ` 98f311fb : 000002e2 ` 391a13c0 000002e2 ` 391970a0 00000000 ` 00000000 00000000 ` 00000000 : KERNEL32!DeviceIoControlImplementation + 0x80 000000fa ` 99aff9e0 000002e2 ` 391a13c0 : 000002e2 ` 391970a0 00000000 ` 00000000 00000000 ` 00000000 000000fa ` 99affa20 : poc + 0x11fb 000000fa ` 99aff9e8 000002e2 ` 391970a0 : 00000000 ` 00000000 00000000 ` 00000000 000000fa ` 99affa20 00000010 ` 00000008 : 0x000002e2 ` 391a13c0 000000fa ` 99aff9f0 00000000 ` 00000000 : 00000000 ` 00000000 000000fa ` 99affa20 00000010 ` 00000008 000000fa ` 99affa28 : 0x000002e2 ` 391970a0 |
补丁分析
对比一下补丁前后的文件变化,可以看到只有cng!CfgAdtpFormatPropertyBlock函数发生了变化
查看一下函数的前后变化,发现补丁后函数调用cng!RtlUShortMult函数对传进来的size进行了处理,然后再传给cng!BCryptAlloc函数申请池空间
cng!RtlUShortMult函数十分简短,就是对size * 6的结果进行溢出判断(与0xffff比较)
1 2 3 4 5 6 7 8 9 10 11 12 13 | __int64 __fastcall RtlUShortMult(unsigned __int16 a1, __int64 a2, unsigned __int16 * a3) { unsigned int v3; / / ecx unsigned __int16 v4; / / dx v3 = 6 * a1; if ( v3 > 0xFFFF ) v4 = 0xFFFF ; else v4 = v3; * a3 = v4; return v3 > 0xFFFF ? 0xC0000095 : 0 ; } |
参考链接
- https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2020/CVE-2020-17087.html
- https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-17087
- https://www.anquanke.com/post/id/221964
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界