CVE-2020-17087在野0day分析

阅读量    147782 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

前言

2020年10月30日,谷歌P0团队批露了微软尚未修复的内核在野0day,CVE-2020-17087, 谷歌称目前已发现在野利用,目前微软尚未发布针对此漏洞的补丁程序,预计本周二(11/10)补丁日进行修复。

目前此漏洞POC已被P0公开,预计影响为Win7以后全平台。

当前测试环境:Win10 1903 x64(已安装10月补丁)。

 

漏洞原理

此漏洞为Windows内核加密模块(cng.sys)出现的整数溢出和缓冲区漏洞,在函数cng!CfgAdtpFormatPropertyBlock,由于对传入的第二参数进行乘以6后,出现上溢,造成后续申请缓冲区过小,访问内核无效内存,造成BSOD。

调用栈回溯如下:

2: kd> !analyze -v
Connected to Windows 10 18362 x64 target at (Fri Nov  6 20:49:23.242 2020 (UTC + 8:00)), ptr64 TRUE
Loading Kernel Symbols
..................................

Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take too long.
Run !sym noisy before .reload to track down problems loading symbols.

.............................
................................................................
...........................................................
Loading User Symbols
.....
Loading unloaded module list
.....

************* Symbol Loading Error Summary **************
Module name            Error
SharedUserData         No error - symbol load deferred

You can troubleshoot most symbol related issues by turning on symbol loading diagnostics (!sym noisy) and repeating the command that caused symbols to be loaded.
You should also verify that your symbol search path (.sympath) is correct.
ERROR: FindPlugIns 8007007b
ERROR: Some plugins may not be available [8007007b]
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced.  This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: ffffcf0640665000, memory referenced.
Arg2: 0000000000000002, value 0 = read operation, 1 = write operation.
Arg3: fffff8023daa2924, If non-zero, the instruction address which referenced the bad memory
    address.
Arg4: 0000000000000002, (reserved)

Debugging Details:
------------------

*** WARNING: Unable to verify checksum for CVE-2020-17087.exe

KEY_VALUES_STRING: 1


PROCESSES_ANALYSIS: 1

SERVICE_ANALYSIS: 1

STACKHASH_ANALYSIS: 1

TIMELINE_ANALYSIS: 1


DUMP_CLASS: 1

DUMP_QUALIFIER: 0

BUILD_VERSION_STRING:  18362.1.amd64fre.19h1_release.190318-1202

DUMP_TYPE:  0

BUGCHECK_P1: ffffcf0640665000

BUGCHECK_P2: 2

BUGCHECK_P3: fffff8023daa2924

BUGCHECK_P4: 2

READ_ADDRESS:  ffffcf0640665000 Special pool

FAULTING_IP: 
cng!CfgAdtpFormatPropertyBlock+a8
fffff802`3daa2924 668901          mov     word ptr [rcx],ax

MM_INTERNAL_CODE:  2

IMAGE_NAME:  cng.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  0

MODULE_NAME: cng

FAULTING_MODULE: fffff8023da40000 cng

CPU_COUNT: 4

CPU_MHZ: e10

CPU_VENDOR:  GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 9e

CPU_STEPPING: 9

CPU_MICROCODE: 6,9e,9,0 (F,M,S,R)  SIG: 8E'00000000 (cache) 8E'00000000 (init)

DEFAULT_BUCKET_ID:  WIN8_DRIVER_FAULT

BUGCHECK_STR:  AV

PROCESS_NAME:  CVE-2020-17087.exe

CURRENT_IRQL:  0

ANALYSIS_SESSION_HOST:  

ANALYSIS_SESSION_TIME:  11-06-2020 20:49:59.0083

ANALYSIS_VERSION: 10.0.18362.1 amd64fre

TRAP_FRAME:  ffffe10a83626d10 -- (.trap 0xffffe10a83626d10)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=0000000000000020 rbx=0000000000000000 rcx=ffffcf0640665000
rdx=ffffcf0640664ff0 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8023daa2924 rsp=ffffe10a83626ea0 rbp=0000000000002aab
 r8=0000000000002aa9  r9=0000000000000002 r10=fffff8023dadce70
r11=ffffe10a83626df0 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei ng nz ac po nc
cng!CfgAdtpFormatPropertyBlock+0xa8:
fffff802`3daa2924 668901          mov     word ptr [rcx],ax ds:ffffcf06`40665000=????
Resetting default scope

LAST_CONTROL_TRANSFER:  from fffff8023cb67ef2 to fffff8023ca89b30

STACK_TEXT:  
ffffe10a`836262c8 fffff802`3cb67ef2 : ffffcf06`40665000 00000000`00000003 ffffe10a`83626430 fffff802`3c9e32f0 : nt!DbgBreakPointWithStatus
ffffe10a`836262d0 fffff802`3cb675e7 : fffff802`00000003 ffffe10a`83626430 fffff802`3ca963f0 ffffe10a`83626970 : nt!KiBugCheckDebugBreak+0x12
ffffe10a`83626330 fffff802`3ca81de7 : fffff802`3cd234f8 fffff802`3cb91a45 ffffcf06`40665000 ffffcf06`40665000 : nt!KeBugCheck2+0x947
ffffe10a`83626a30 fffff802`3cac719e : 00000000`00000050 ffffcf06`40665000 00000000`00000002 ffffe10a`83626d10 : nt!KeBugCheckEx+0x107
ffffe10a`83626a70 fffff802`3c95459f : 00000000`00000fff 00000000`00000002 00000000`00000000 ffffcf06`40665000 : nt!MiSystemFault+0x19dcee
ffffe10a`83626b70 fffff802`3ca8fd5e : ffffcf06`1a5f0080 ffffcf06`40664ff0 ffffcf06`397f7000 fffff802`3c97f1ad : nt!MmAccessFault+0x34f
ffffe10a`83626d10 fffff802`3daa2924 : 00000000`00000010 00000000`00040200 ffffe10a`83626ec8 00000000`00000018 : nt!KiPageFault+0x35e
ffffe10a`83626ea0 fffff802`3daa224e : 00000000`00000000 ffffe10a`83626fd0 ffffcf06`397f7000 00000000`00000001 : cng!CfgAdtpFormatPropertyBlock+0xa8
ffffe10a`83626ed0 fffff802`3daa0282 : 00000000`00000005 ffffe10a`836276a0 ffffcf06`397f7000 ffffcf06`397f6200 : cng!CfgAdtReportFunctionPropertyOperation+0x23e
ffffe10a`836273f0 fffff802`3da89580 : ffffe10a`836276a0 ffffcf06`397f6100 ffffe10a`83627570 ffffcf06`397f6200 : cng!BCryptSetContextFunctionProperty+0x3a2
ffffe10a`836274f0 fffff802`3da52e86 : 00000000`00003aab 00000000`00000008 00000000`00003aab ffffcf06`397ef000 : cng!_ConfigurationFunctionIoHandler+0x3bd5c
ffffe10a`836275e0 fffff802`3da52d22 : 00000000`00003aab fffff802`3da52c64 00000000`00000010 00000000`00040344 : cng!ConfigFunctionIoHandler+0x4e
ffffe10a`83627620 fffff802`3da51567 : 00000000`00000000 fffff802`00003aab 00000000`00000000 00000000`00010400 : cng!ConfigIoHandler_Safeguarded+0xd2
ffffe10a`83627690 fffff802`3da4e0ea : 00000000`00000000 ffffcf06`377fbda0 ffffcf06`377fbcd0 00000000`00000008 : cng!CngDeviceControl+0x97
ffffe10a`83627760 fffff802`3c8eb4e9 : ffffcf06`377fbcd0 00000000`00000000 00000000`00000002 00000000`00000001 : cng!CngDispatch+0x8a
ffffe10a`836277a0 fffff802`3ce90a55 : ffffe10a`83627b00 ffffcf06`377fbcd0 00000000`00000001 ffffcf06`2db2d930 : nt!IofCallDriver+0x59
ffffe10a`836277e0 fffff802`3ce90860 : 00000000`00000000 ffffe10a`83627b00 ffffcf06`377fbcd0 ffffe10a`83627b00 : nt!IopSynchronousServiceTail+0x1a5
ffffe10a`83627880 fffff802`3ce8fc36 : 00000258`88818000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0xc10
ffffe10a`836279a0 fffff802`3ca93555 : 00000000`00000000 00000000`00000000 00000000`00000000 00000072`294ff7e8 : nt!NtDeviceIoControlFile+0x56
ffffe10a`83627a10 00007ffe`6b65c1a4 : 00007ffe`6930eaa7 00000000`00000000 cccccccc`cccccccc cccccccc`cccccccc : nt!KiSystemServiceCopyEnd+0x25
00000072`294ffb18 00007ffe`6930eaa7 : 00000000`00000000 cccccccc`cccccccc cccccccc`cccccccc cccccccc`cccccccc : ntdll!NtDeviceIoControlFile+0x14
00000072`294ffb20 00007ffe`69db6430 : 00000000`00390400 cccccccc`cccccccc cccccccc`cccccccc cccccccc`cccccccc : KERNELBASE!DeviceIoControl+0x67
00000072`294ffb90 00007ff7`c952e475 : 00000000`00000000 00000000`00000000 00000072`294ffc20 00000000`00000000 : KERNEL32!DeviceIoControlImplementation+0x80
00000072`294ffbe0 00007ff7`c952ed44 : 00007ff7`c952ea80 00007ff7`c953004d 00000000`00000000 00007ff7`c95dd790 : CVE_2020_17087!main+0x285 [d:\project\c++\cve-2020-17087\cve-2020-17087\cve-2020-17087.cpp @ 55] 
00000072`294ffde0 00007ff7`c952ebe7 : 00007ff7`c95dd000 00007ff7`c95dd220 00000000`00000000 00000000`00000000 : CVE_2020_17087!invoke_main+0x34 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 65] 
00000072`294ffe20 00007ff7`c952eaae : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : CVE_2020_17087!__scrt_common_main_seh+0x127 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 253] 
00000072`294ffe80 00007ff7`c952ed69 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : CVE_2020_17087!__scrt_common_main+0xe [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 296] 
00000072`294ffeb0 00007ffe`69db7c24 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : CVE_2020_17087!mainCRTStartup+0x9 [f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17] 
00000072`294ffee0 00007ffe`6b62cea1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
00000072`294fff10 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21


THREAD_SHA1_HASH_MOD_FUNC:  93bb0a37142c095b1b37f11f6eef148b047836ba

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  bdcdc47df46b8a0e8d0af92560d7f5cbc351f2ce

THREAD_SHA1_HASH_MOD:  539b7118e8662a78213df7350439d52c7ac5ea35

FOLLOWUP_IP: 
cng!CfgAdtpFormatPropertyBlock+a8
fffff802`3daa2924 668901          mov     word ptr [rcx],ax

FAULT_INSTR_CODE:  49018966

SYMBOL_STACK_INDEX:  7

SYMBOL_NAME:  cng!CfgAdtpFormatPropertyBlock+a8

FOLLOWUP_NAME:  MachineOwner

STACK_COMMAND:  .thread ; .cxr ; kb

BUCKET_ID_FUNC_OFFSET:  a8

FAILURE_BUCKET_ID:  AV_VRF_INVALID_cng!CfgAdtpFormatPropertyBlock

BUCKET_ID:  AV_VRF_INVALID_cng!CfgAdtpFormatPropertyBlock

PRIMARY_PROBLEM_CLASS:  AV_VRF_INVALID_cng!CfgAdtpFormatPropertyBlock

TARGET_TIME:  2020-11-06T12:49:17.000Z

OSBUILD:  18362

OSSERVICEPACK:  0

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

SUITE_MASK:  272

PRODUCT_TYPE:  1

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 10

OSEDITION:  Windows 10 WinNt TerminalServer SingleUserTS

OS_LOCALE:  

USER_LCID:  0

OSBUILD_TIMESTAMP:  unknown_date

BUILDDATESTAMP_STR:  190318-1202

BUILDLAB_STR:  19h1_release

BUILDOSVER_STR:  10.0.18362.1.amd64fre.19h1_release.190318-1202

ANALYSIS_SESSION_ELAPSED_TIME:  3908

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:av_vrf_invalid_cng!cfgadtpformatpropertyblock

FAILURE_ID_HASH:  {c7c09a48-39c6-9551-71d6-bb35c7878b4b}

Followup:     MachineOwner
---------

CfgAdtpFormatPropertyBlock函数如下,可以看到出现整数溢出的逻辑。

没有反编译工具差点没看出来6倍(2+1)+(2+1)。。。

 

分析与调试

cng!CngDispatch

两参数 PDEVICE_OBJECT 以及 PIRP

在调用CngDeviceControl 函数之前,主要a2的IRP数据进行处理,获取In/Out buffer地址以及Length,还有IOCTL code和RequestMode ,分别作为六个参数,传入CngDeviceControl 。通过调试来看,此派遣历程使用的操作模式Method为METHOD_BUFFERED。

通过IOCTL Code最后一位同样也可以看出,Method 0即为 METHOD_BUFFERED。

#define CTL_CODE( DeviceType, Function, Method, Access ) (                 \
    ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)

此时的寄存器以及内存的值

进入cng!CngDeviceControl 函数,大量判断IOCTL code之后,调用ConfigIoHandler_Safeguarded函数,传入前四参数即缓冲区相关四参数。

进入ConfigIoHandler_Safeguarded函数,

使用InputLength申请两块内存,第一块block1拷贝传入的数据。

1: kd> dq ffffc18e`eae97000
ffffc18e`eae97000  00010400`1a2b3c4d 00000000`00000001
ffffc18e`eae97010  00000000`00000100 00000000`00000003
ffffc18e`eae97020  00000000`00000200 00000000`00000300
ffffc18e`eae97030  00000000`00000400 00000000`00000000
ffffc18e`eae97040  00000000`00000500 00000000`00000600
ffffc18e`eae97050  00000000`00002aab 00000000`00001000
ffffc18e`eae97060  00000000`00000000 00000000`00000000
ffffc18e`eae97070  00000000`00000000 00000000`00000000

第二块block2初始化为0。

将第一块地址 v10, 大小v17,第二块局部变量v19 int型地址,第二块地址v12作为四参数,调用IoUnpack_SG_ParamBlock_Header函数,根据逻辑来看,此函数最终返回0。否则当前函数将返回。

进入IoUnpack_SG_ParamBlock_Header函数:

对传入缓冲区数据一顿判断(表示很疑惑,为什么对常识性的地址大小进行判断而不是其中的值,防止溢出??),另外将传入缓冲区得数据起始偏移为4得值拷贝到第三参数a3所在地址,很明显进入while循环后,由于block2之前已经初始化为0,那么循环8次后将a4置为-1,进入goto语句,返回0。

再回到ConfigIoHandler_Safeguarded函数中,接着调用ConfigFunctionIoHandler函数,将刚刚拿到的传入缓冲区地址偏移4处的值(0x10400)作为第一参数,block1地址作为第二参数,传入缓冲区大小InBufferLen作为第三参数,输出缓冲区地址作为第四参数,输出缓冲区大小为第五参数,block2地址作为第六参数。

进入ConfigFunctionIoHandler函数,

第一参数逻辑右移16位,结果为1(0x10000=<a1<0x1FFFF)时,调用ConfigurationFunctionIoHandler函数并传入6参数(注意a1变为两字节);

结果为2(0x20000=<a1<0x2FFFF)时,调用ResolutionFunctionIoHandler函数并传入前两参数;大于2或者小于0失败C00000AF(STATUS_ILLEGAL_FUNCTION);等于0则调用RegistrationFunctionIoHandler调用函数并传入前两参数;

我们进入ConfigurationFunctionIoHandler函数,首先映入眼帘的是一个熟悉的函数 IoUnpack_SG_Configuration_ParamBlock,估摸着和IoUnpack_SG_ParamBlock_Header函数有异曲同工之处。

//v7 block2地址

在此函数中,循环后goto语句进入几个子函数

//v17 block1地址
//v15 传入缓冲区大小InBufferLen
//a15为block2地址

这些函数内部的操作,将传入第一参数block1+offset处覆盖为原先值加上起始地址后的值。

eg:block1+10处的值原先为100h,加上起始地址后变为ffffc18e`eae97100,写回原先位置处。

最后进入IoUnpack_SG_Buffer函数,传入Inputbuf block1+0x10地址,block1地址,缓冲区长度,block2起始地址4参数。经过内存断点调试,我们发现在此函中调用IoUnpack_SG_Buffer函数时,访问了block1+0x50处内存

调试后发现,此函数同样修改了传入的block1+58处内存为值加上起始地址,另外,通过block1中成员大小的各种边界判断之后,将block2+58处的值置为-1,此外,循环(block1+50h)即2AAB次,将block2+(block1+58h)即block2+1000处开始,均置为-1,即修改了2AAB字节的内存。返回0。最终block1的内存布局如下:

1: kd> dq rdi
ffffc18e`eae97000  00010400`1a2b3c4d 00000000`00000001
ffffc18e`eae97010  ffffc18e`eae97100 00000000`00000003
ffffc18e`eae97020  ffffc18e`eae97200 ffffc18e`eae97300
ffffc18e`eae97030  ffffc18e`eae97400 00000000`00000000
ffffc18e`eae97040  ffffc18e`eae97500 ffffc18e`eae97600
ffffc18e`eae97050  00000000`00002aab ffffc18e`eae98000
ffffc18e`eae97060  00000000`00000000 00000000`00000000
ffffc18e`eae97070  00000000`00000000 00000000`00000000

此函数中对block2内存做修改 ,同时对大量参数使用block1内存进行赋值。注意,a12,即当前的第12参数,拿到了block1+50处的内存值。

并返回0。IoUnpack_SG_Configuration_ParamBlock同样返回0。

后续由于a1由原先的0x10400截断为0x400,调用BCryptSetContextFunctionProperty函数,传入七个参数分别为在IoUnpack_SG_Configuration_ParamBlock做赋值,v14为传入第3参数,被赋值为*(block1+8)

v16为传入第4参数,被赋值为*(block1+10h)

v15为传入第5参数,被赋值为*(block1+18h)

v17为传入第6参数,被赋值为*(block1+20h)

v22为传入第8参数,被赋值为*(block1+30h)

v19为传入第12参数,被赋值为*(block1+50h)

v24为传入第13参数,被赋值为*(block1+58h)

内部v17所处内存即为block1地址,此时block1处内存为

可以看到,与我们刚开始传入缓冲区的略有不同,似乎在某些位置上,原先的值变为加上当前起始地址的结果。

进入 BCryptSetContextFunctionProperty函数,最后两参数分别为(block1+50h),以及(block1+58h)。

其中调用CfgReg_Acquire尝试打开注册表项System\CurrentControlSet\Control\Cryptography\Configuration\Local

失败,返回error =5。

后续构造三个UnicodeString,巧的是,这里三个UnicodeString的来源,正是block1+100,block1+200,block1+400处的所存储的字符串。

在多次调试发现,后续使用的2AAB正是在调用CfgAdtReportFunctionPropertyOperation前,此时的rsp+3f+38h,正是传入的第六参数(*block1+50h),后续函数会将此处的值取word,传入漏洞函数。

调用CfgAdtReportFunctionPropertyOperation函数,传入四参数(伪代码显示此时传入四参数,这是不准确的,查看汇编,我们可以看到至少传入了10个参数,标记处上方的rsp+48),而第九参数,为传入的第六参数*(block1+50h),取低两字节。

进入CfgAdtReportFunctionPropertyOperation 最终将rbp+0x460处的值(传入的第九参数),作为第二参数传入崩溃函数,block1+1000作为第一参数,局部变量UnicodeString传入第三参数,进入CfgAdtpFormatPropertyBlock前:

进入CfgAdtpFormatPropertyBlock函数,六倍edx以后,溢出变为2,申请大小为2,申请到special pool内存block3。

调试后,解释一下整个do… while循环中的操作,循环次数为2AAB,也就是传进来的a2,通过一个类似与密码表的字符数组,访问a1也就是block1+1000处值,右移4位,作为数组下标,取出单个byte(0x30),填充申请到的内存block3处(WORD),接着a1++,再与上0xF,作为下标取出单个byte(0x30),block3++,填充,再block3++,填充一个0x20,依旧当作WORD型。这是一次循环中所做的事情,循环计数器a2—。

一次循环完成后,内存布局如图

第二次循环结束,内存布局,

显然,第三次循环时,当放置0x20时,访问无效内存,系统崩溃。

有效内存覆盖完毕。

后续向下一页写0x20时,系统崩溃。

 

漏洞利用

在野利用配合chrome CVE-2020-15999(FreeType 2库的“ Load_SBit_Png”函数中的堆缓冲区溢出漏洞),进行针对chrome沙箱的逃逸,进而可进行提权或者代码执行。

分析来看,如果后续内存布局得当,计数器的值导致可访问的内存相当可观,但是缺陷是填充的值为密码本中数据,需要精巧的构造才行。时间有限,仅此抛砖引玉,各位内核师傅冲鸭!

 

时间线

2020.10.30 谷歌P0小组披露漏洞。

2020.11.09 本篇漏洞分析完成。

待续…

 

参考链接

[1]Issue 2104: Windows Kernel cng.sys pool-based buffer overflow in IOCTL 0x390400

https://bugs.chromium.org/p/project-zero/issues/detail?id=2104

[2]Issue 2104 attachment: cng_ioctl_390400.cpp (2.0 KB)

https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=472684

[3]内核池基础知识(池损坏)

https://saturn35.com/2019/07/23/20190723-3/

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多