看到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
__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
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进行递归,然后进行代码审计。本身来说这个漏洞成因相对简单,需要学习的是寻找触发漏洞路径
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: kd> ba e1 /w "@$curprocess.Name == \"poc.exe\"" cng!CngDispatch+85
从上图的第5个参数可以看到传入的IoCode为0x390400,因为抵达漏洞函数需要执行过函数cng!ConfigIoHandler_Safeguarded,cng!CngDeviceControl函数中存在这样的判断
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
__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前后的变化,验证以上说法(ps:pool size没有加上大小为0x10字节的pool header)
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字节置位
__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: 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
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
...
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: 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
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,就会执行到这样的分支
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
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
...
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
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比较)
__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;
}