首页
社区
课程
招聘
[原创]CVE-2020-1472 NetLogon 权限提升漏洞研究
发表于: 2020-12-10 18:21 10656

[原创]CVE-2020-1472 NetLogon 权限提升漏洞研究

2020-12-10 18:21
10656

CVE-2020-1472 NetLogon 权限提升漏洞是微软8月份发布安全公告披露的紧急漏洞,CVSS漏洞评分10分,漏洞利用后果严重,未经身份认证的攻击者可通过使用 Netlogon 远程协议(MS-NRPC)连接域控制器来利用此漏洞。成功利用此漏洞的攻击者可获得域管理员访问权限。

Netlogon 服务用于维护计算机与对域中的用户和其他服务进行身份验证的域控制器之间的安全通道,Netlogon 客户端和服务端之间通过RPC调用来进行通信。在进行正式通信之前,双方需进行身份认证并协商出一个 SessionKey。SessionKey 将用于保护双方后续 RPC 通信流量。以下为 Netlogon 身份验证握手流程:

首先由客户端发起挑战(传送Client challenge),服务端响应Server challenge,然后双方都使用共享的密钥以及来自双方的 challenges 进行计算得到 SessionKey,这样双方就拥有了相同的 SessionKey 以及 Client challenge 和 Server challenge。然后客户端使用 SessionKey 作为密钥加密 Client challenge 得到 Client credential 并发送给服务端,服务端也采用相同的方法计算出一个 Client credential,比较这两者是否相同,如果相同,则客户端身份认证成功,然后双方对调来验证服务端的 Server credential,如果成功,则说明双方身份认证成功且拥有相同的 SessionKey,后续可采用该密钥进行加密和完整性保护。

SessionKey 计算过程
如果双方协商了AES support,就会采用 HMAC-SHA256 算法来计算 SessionKey,具体流程如下:

使用MD4算法对密码的 Unicode 字符串进行散列得到 M4SS,然后以 M4SS 为密钥采用 HMAC-SHA256 算法对 ClientChallenge + ServerChallenge 进行哈希得到 SessionKey,取 SessionKey 的低16个字节作为最终的 SessionKey。

Credential 计算过程
如果双方协商了AES support,后续会采用 AES-128 加密算法在 8 位 CFB 模式下计算 Credential(来自MS-NRPC文档)。 其计算过程大致如下:

在 ComputeNetlogonCredential 函数中将 IV 初始化为 0,Input 接收 Challenge,使用 IV、SessionKey 对 Input 进行加密,AesEncrypt 使用的算法为 8 位 CFB 模式的 AES-128。

下面来插播一下 AES-CFB8 算法,如下所示:

首先初始化随机 IV(16字节),对 IV 进行 AES 运算,将结果的第一个字节与 PLAINTEXT(可对应上面算法的 Input) 的下一个字节进行异或,将异或结果放在 IV 末尾,IV 整体向前移1位。然后重复上述 "加密->异或->移位" 操作,直到取出了 PLAINTEXT 中的所有字节。最后得到 CIPHERTEXT(对应上面算法的 Output,其长度与 Input 相同)。

然而,Netlogon 在计算 Credential 的过程中直接将 IV 初始化为 0,这会使 AES-CFB8 算法更加脆弱,Secura 的研究人员在阅读 Microsoft 文档时发现了这个安全问题。由于在认证过程中 SessionKey 是随机的 (至少 ServerChallenge 是随机的,所以 SessionKey 一定是随机的),因而对 IV 进行 AES 块加密得到的结果也是随机的,但只有结果中的第一个字节有机会参与后续运算,这个字节为 X 的概率为 1/256(一个字节可能的结果为 0 ~ 255)。那么我们假设第一轮 IV(全0) 加密结果的第一个字节为 X,我们就知道全 0 的输入可以获得输出 X ,因而我们可以构造 Challenge 为 XXXXXXXY,使得每一次异或的结果都为 0(除了最后一次,最后一位不参与加密运算),因为每一次加密结果的第一个字节都是 X,与 X 进行异或还是 0 ,因而每一轮的 "IV" 还是全 0 的。这样加密结果是可以预测的,我们可以得到一个确定的 Credential:00 00 00 00 00 00 00 (X Xor Y)。因而在平均 256 次尝试之后,可以成功使用 00 00 00 00 00 00 00 (X Xor Y) 模式的 Credential 欺骗服务器认证通过而无需知道真正的密码以及 SessionKey,利用的关键是获得一个对全 0 IV 进行加密之后得到结果的第一字节为 X 的环境。POC 中选择将 X、Y 设置为 0,如下所示:

使用公开的POC进行漏洞复现,如下所示,目标系统存在该漏洞,密码成功被置为空。

同时使用Wireshark抓包,前面说过 Netlogon 采用 RPC(Remote Procedure Call Protocol,远程过程调用协议)来进行通信。RPC 是一种通过网络从远程计算机程序上请求服务而不需要了解底层网络技术的协议。RPC 允许用户在程序中调用一个函数,而这个函数将在另外一个或多个远程机器上执行,并将结果返回给最初进行 RPC 调用的机器,在这个过程中RPC体系会替用户完成网络上连接建立、会话握手、用户验证、参数传递、结果返回等细节问题,使得远程过程调用和本地函数调用一样方便。因而,我们直接关注用户验证阶段就好。

注意 NetrServerReqChallenge 和 NetrServerAuthenticate3 请求&响应包,直接翻到最后一组。NetrServerReqChallenge 请求包将 Server Handle、Computer Name 以及 Client Challenge 序列化数据发送至服务端,其中,Client Challenge 为 "00 00 00 00 00 00 00 00"。

服务端返回 Server Challenge 为 "7a 06 53 36 16 8d 5f 78"。

发送 NetrServerAuthenticate3 请求,传送 Server Handle、Client Credential、Negotiation options等参数,Client Credential 依旧还是 "00 00 00 00 00 00 00 00"。其实,在每次尝试中 Client Challenge、Client Credential 都是 8 字节全零数据,只有最后一次认证成功了(成功了就不用尝试了呢)。

这次,服务端返回认证成功的代码(STATUS_SUCCESS),Server Credential 和服务端身份认证相关,不必关注。只需要记得这次使得身份认证成功的 Server Challenge 为 "7a 06 53 36 16 8d 5f 78" 就好,后面会有个小验证。

再接下来是发送 NetrServerPasswordSet2 请求,如下图所示,参数为 Server Handle(PrimaryName),还有一些 Wireshark 没有识别出来的参数 AccountName、SecureChannelType、ComputerName、Authenticator、ClearNewPassword,我按照格式用不同颜色的笔标记出来了,其中,Authenticator 和 ClearNewPassword 都是全 0 的:

然后服务端返回其 Authenticator,至此密码已成功被置为空。

POC中先后调用了 NetrServerReqChallenge 函数和 NetrServerAuthenticate3 函数。因而,服务端会通过 NetrServerReqChallenge 函数接收 ClientChallenge 并生成 ServerChallenge,在接收到 NetrServerAuthenticate3 请求后会调用 NetrServerAuthenticate3 函数进行认证。以下为两个函数原型,可对比抓包数据来看,其中,in 和 out 分别对应了客户端和服务端请求响应的参数。

重点来看服务端对客户端身份认证环节,在 NetrServerAuthenticate3 函数中会调用 NlMakeSessionKey 函数计算 SessionKey,然后调用 NlComputeCredentials 函数计算 ClientCredential,比较客户端发来的 ClientCredential 和自己计算出来的是否相同。

在计算 ClientCredential 的过程中会调用 SymCryptCfbEncrypt 函数进行 AES-CFB8 运算,如下所示,会循环调用 SymCryptAesEncrypt 函数对数据块进行加密, IV 被初始化为 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00。

将上述流程使用 python 实现,设置 Client Challenge 为 41 41 41 41 41 41 41 41(即 X 为 0x41,Y 为 0),随机生成 Server Challenge,打印每次的 SessionKey 和 ClientCredential,看会不会出现全 0 的 ClientCredential:

在经过 2000 次测试之后,发现成功需要平均的次数为 252,已经很接近 256 了,理论上测试次数越多越接近 256。幸运的是,在每一次测试中都在 2000 次之内得到了全 0 的 ClientCredential。

稍微修改一下代码,对抓包得到的 Server Challenge 进行测试,可发现成功得到全 0 的 Client Credential,服务端计算的 Authenticator.Credential 为 01 55 c7 b5 50 3b 23 ab(和抓包数据相吻合)。

POC 中的下一步是调用 NetrServerPasswordSet2 将密码置空,以下为 NetrServerPasswordSet2 函数原型,在该函数调用请求中需要提交 Authenticator 认证数据(NETLOGON_AUTHENTICATOR 结构)以及 ClearNewPassword 数据(NL_TRUST_PASSWORD 结构)。

NETLOGON_AUTHENTICATOR 前面介绍过,包括计算得到的 Credential 和当前的 Timestamp;NL_TRUST_PASSWORD 结构体中包括 Buffer(Unicode类型,512 字节)和 4 字节的 Length(指明 Password 长度)。

如果表示计算机账户密码,Buffer 中的前 512 - Length 个字节必须为随机数,作为加密熵源,后面 Length 个字节为密码。

服务端在获取到客户端发送的 Authenticator 中的 timestamp 后,只是判断该值是否为0xFFFFFFFF,如果不是的话就直接使用用户发送的 timestamp 进行后续计算(计算过程前面已经分析过)。如下所示,在 netlogon!NlCheckAuthenticator 函数中验证Authenticator,采用 NlComputeCredentials 函数计算 Credential,由于还是使用同一条件下的算法(相同的 IV 和 SessionKey),因而使用和之前 Client Challenge 相同的输入依然可以得到全 0 输出,POC 中使用的 timestamp 还是0,这样 timestamp 加上之前计算得到的 Credential 后还是全 0 的,计算出的 TempCredential 也还是全 0,这样我们使用 00 00 00 00 00 00 00 00 的 Authenticator.Credential 就可以通过验证。由于 timestamp 的长度为 4 个字节,因而在前面的模式中只有 X 取 0 的情况下可以通过验证,即 Client Challenge、Client Credential、Authenticator.Credential 都为 00 00 00 00 00 00 00 Y,Authenticator.timestamp 为 0。

Authenticator 认证通过后会调用 NlDecrypt 函数对 TRUST_PASSWORD 进行 8 位 CFB 模式 AES-128 解密,在进行一些判断后会调用 NlSetIncomingPassword -> NlSamChangePasswordNamedUser -> SamISetMachinePassword 设置密码。以下为测试,随意填充 ClearNewPassword 结构,这里我将密码长度设置为 0x10,密码为 "testtest" ,其余数据用 00 填充(应该是随机数)。理论上这个结构应该进行 8 位 CFB 模式的 AES-128 加密,但我们不知道原来的密码,也算不出 SessionKey,所以干脆就这样啦。解密之后长度变成了 0xc6e8ca2,由于后面会有是否大于 0x200 的判断,将其手动修改为 0x10,然后继续运行程序。

解密后的密码为 "74 7d 5d be 3e 03 af cc 2e cb b8 52 1c 4b af f5",使用 MD4 散列算法进行运算得到 2d7091de951698701d2c34e3ccec0596,使用此哈希可从域控制器中复制用户凭据:

现在再来回顾一下漏洞模式,由于我们可以走到这里,说明客户端和服务端直接已经协商出了给定输入 00 就可以得到 AES 块加密结果第一个字节为 00 的 SessionKey,如果我们给出 516 个 00,那么 8 位 CFB 模式 AES-128 加密的结果也是 516 个 00。我们将 ClearNewPassword 结构填充为 516 个 00 ,这样系统在解密的时候得到的结果也是 516 个 00,这样密码的长度字段就被解析为 0,账户密码被置空。

然后可以使用 wmiexec 拿到域控制器中的本地管理员权限(后面恢复密码的操作就不介绍了,教程很多):

使用前面总结的 00 00 00 00 00 00 00 Y 模式进行漏洞利用,这里 Y 为 0x41,ClearNewPassword 结构完全置零,成功将密码置空。以下为抓包数据:

1、NetrServerReqChallenge 请求,Client Challenge 为 00 00 00 00 00 00 00 41

2、NetrServerAuthenticate3 请求,Client Credential 为 00 00 00 00 00 00 00 41,Negotiation options 设置为 0xffffffff,POC 中将其设置为 0x212fffff( 将 Secure NRPC 位(Netlogon signing and sealing)设置为 0),但测试时将其设置为 0x612fffff 也是可以成功的,甚至,我直接将其设置为 0xffffffff。但无论如何,AES supported 位必须被设置,可参考 MS-NRPC 文档 3.1.4.2 节查看 NegotiateFlags 位。

3、NetrServerPasswordSet2 请求,Authenticator 中 Credential 设置为 00 00 00 00 00 00 00 41,timestamp 设置为 0。ClearNewPassword 设置为全 0。

4、最终,服务端设置成功,返回其 Authenticator。

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1472
https://www.secura.com/pathtoimg.php?id=2055
https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NRPC/%5BMS-NRPC%5D.pdf
https://mp.weixin.qq.com/s/wHoT-h468TXR48zzc79XgQ
https://nakedsecurity.sophos.com/2020/09/17/zerologon-hacking-windows-servers-with-a-bunch-of-zeros/
https://bbs.pediy.com/thread-262236.htm
https://github.com/mstxq17/cve-2020-1472

 
 
ComputeSessionKey(SharedSecret, ClientChallenge,
ServerChallenge)
M4SS := MD4(UNICODE(SharedSecret))
CALL SHA256Reset(HashContext, M4SS, sizeof(M4SS));
CALL SHA256Input(HashContext, ClientChallenge, sizeof(ClientChallenge));
CALL SHA256FinalBits (HashContext, ServerChallenge, sizeof(ServerChallenge));
CALL SHA256Result(HashContext, SessionKey);
SET SessionKey to lower 16 bytes of the SessionKey;
ComputeSessionKey(SharedSecret, ClientChallenge,
ServerChallenge)
M4SS := MD4(UNICODE(SharedSecret))
CALL SHA256Reset(HashContext, M4SS, sizeof(M4SS));
CALL SHA256Input(HashContext, ClientChallenge, sizeof(ClientChallenge));
CALL SHA256FinalBits (HashContext, ServerChallenge, sizeof(ServerChallenge));
CALL SHA256Result(HashContext, SessionKey);
SET SessionKey to lower 16 bytes of the SessionKey;
ComputeNetlogonCredential(Input, SessionKey, Output)
SET IV = 0
CALL AesEncrypt(Input, SessionKey, IV, Output)
ComputeNetlogonCredential(Input, SessionKey, Output)
SET IV = 0
CALL AesEncrypt(Input, SessionKey, IV, Output)
 
 
 
 
typedef struct _NETLOGON_AUTHENTICATOR {
 NETLOGON_CREDENTIAL Credential;
 DWORD Timestamp;
} NETLOGON_AUTHENTICATOR,
*PNETLOGON_AUTHENTICATOR;
typedef struct _NETLOGON_AUTHENTICATOR {
 NETLOGON_CREDENTIAL Credential;
 DWORD Timestamp;
} NETLOGON_AUTHENTICATOR,
*PNETLOGON_AUTHENTICATOR;
SET TimeNow = current time;
SET ClientAuthenticator.Timestamp = TimeNow;
SET ClientStoredCredential = ClientStoredCredential + TimeNow;
CALL ComputeNetlogonCredential(ClientStoredCredential,
Session-Key, ClientAuthenticator.Credential);
SET TimeNow = current time;
SET ClientAuthenticator.Timestamp = TimeNow;
SET ClientStoredCredential = ClientStoredCredential + TimeNow;
CALL ComputeNetlogonCredential(ClientStoredCredential,
Session-Key, ClientAuthenticator.Credential);
SET ServerStoredCredential = ServerStoredCredential + ClientAuthenticator.Timestamp;
CALL ComputeNetlogonCredential(ServerStoredCredential,
Session-Key, TempCredential);
IF TempCredential != ClientAuthenticator.Credential
THEN return access denied error
SET ServerStoredCredential = ServerStoredCredential + 1;
CALL ComputeNetlogonCredential(ServerStoredCredential,
Session-Key, ServerAuthenticator.Credential);
SET ServerStoredCredential = ServerStoredCredential + ClientAuthenticator.Timestamp;
CALL ComputeNetlogonCredential(ServerStoredCredential,
Session-Key, TempCredential);
IF TempCredential != ClientAuthenticator.Credential
THEN return access denied error
SET ServerStoredCredential = ServerStoredCredential + 1;
CALL ComputeNetlogonCredential(ServerStoredCredential,
Session-Key, ServerAuthenticator.Credential);
SET ClientStoredCredential = ClientStoredCredential + 1;
CALL ComputeNetlogonCredential(ClientStoredCredential,
Session-Key, TempCredential);
IF TempCredential != ServerAuthenticator.Credential
THEN return abort
SET ClientStoredCredential = ClientStoredCredential + 1;
CALL ComputeNetlogonCredential(ClientStoredCredential,
Session-Key, TempCredential);
IF TempCredential != ServerAuthenticator.Credential
THEN return abort
 
 
 
 
 
 
 
 
 
 
 
 
 
 
NTSTATUS NetrServerReqChallenge(
 [in, unique, string] LOGONSRV_HANDLE PrimaryName,
 [in, string] wchar_t* ComputerName,
 [in] PNETLOGON_CREDENTIAL ClientChallenge,
 [out] PNETLOGON_CREDENTIAL ServerChallenge
);
 
NTSTATUS NetrServerAuthenticate3(
 [in, unique, string] LOGONSRV_HANDLE PrimaryName,
 [in, string] wchar_t* AccountName,
 [in] NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
 [in, string] wchar_t* ComputerName,
 [in] PNETLOGON_CREDENTIAL ClientCredential,
 [out] PNETLOGON_CREDENTIAL ServerCredential,
 [in, out] ULONG * NegotiateFlags,
 [out] ULONG * AccountRid
);
NTSTATUS NetrServerReqChallenge(
 [in, unique, string] LOGONSRV_HANDLE PrimaryName,
 [in, string] wchar_t* ComputerName,
 [in] PNETLOGON_CREDENTIAL ClientChallenge,
 [out] PNETLOGON_CREDENTIAL ServerChallenge
);
 
NTSTATUS NetrServerAuthenticate3(
 [in, unique, string] LOGONSRV_HANDLE PrimaryName,
 [in, string] wchar_t* AccountName,
 [in] NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
 [in, string] wchar_t* ComputerName,
 [in] PNETLOGON_CREDENTIAL ClientCredential,
 [out] PNETLOGON_CREDENTIAL ServerCredential,
 [in, out] ULONG * NegotiateFlags,
 [out] ULONG * AccountRid
);
//NetrServerAuthenticate3
  _mm_store_si128((__m128i *)&v58, v23);    // v58 => md4(unicode(secret))
  LODWORD(v45) = NlMakeSessionKey(flags, (__int64)&v58, (__int64)CC, (__int64)SC);    // 这里计算SessionKey
  if ( (signed int)v45 < 0 )
  {
    NlPrintDomRoutine(
      0x100u,
      (__int64)v9,
      (__int64)L"NetrServerAuthenticate: Can't NlMakeSessionKey for %ws 0x%lx.\n",
      (__int64)v7);
      RtlLeaveCriticalSection(&NlGlobalChallengeCritSect);
      goto LABEL_59;
   }
  NlPrintRoutine(0x4000000u, (__int64)L"NetrServerAuthenticate: SessionKey %lu = ", v22, v25);
  NlpDumpBuffer(0x4000000, (__int64)&SessionKey, 0x10u);
  NlComputeCredentials(CC, pbOutput, &SessionKey, *&flag);    // 这里计算ClientCredential
  NlPrintRoutine(0x4000000u, (__int64)L"NetrServerAuthenticate: ClientCredential %lu GOT  = ", v22, v26);
  NlpDumpBuffer(0x4000000, 0i64, 8u);
  NlPrintRoutine(0x4000000u, (__int64)L"NetrServerAuthenticate: ClientCredential %lu MADE = ", v22, v27);
  NlpDumpBuffer(0x4000000, (__int64)pbOutput, 8u);
  if ( v0 == *(_QWORD *)pbOutput )    //验证接收的ClientCredential和刚计算出的是否相等
 
1: kd> p
netlogon!NetrServerAuthenticate3+0x399:
00007ffb`0df74799 488b4580        mov     rax,qword ptr [rbp-80h]
1: kd>
netlogon!NetrServerAuthenticate3+0x39d:
00007ffb`0df7479d 488b00          mov     rax,qword ptr [rax]
1: kd>
netlogon!NetrServerAuthenticate3+0x3a0:
00007ffb`0df747a0 483b45b0        cmp     rax,qword ptr [rbp-50h]
1: kd> db poi(rbp-80) l8
00000083`db04efc8  00 00 00 00 00 00 00 00                          ........
1: kd> db rbp-50 l8
00000083`db7de780  99 8f 65 7c 7e 91 e0 99                          ..e|~...
//NetrServerAuthenticate3
  _mm_store_si128((__m128i *)&v58, v23);    // v58 => md4(unicode(secret))
  LODWORD(v45) = NlMakeSessionKey(flags, (__int64)&v58, (__int64)CC, (__int64)SC);    // 这里计算SessionKey
  if ( (signed int)v45 < 0 )
  {
    NlPrintDomRoutine(
      0x100u,
      (__int64)v9,
      (__int64)L"NetrServerAuthenticate: Can't NlMakeSessionKey for %ws 0x%lx.\n",
      (__int64)v7);
      RtlLeaveCriticalSection(&NlGlobalChallengeCritSect);
      goto LABEL_59;
   }
  NlPrintRoutine(0x4000000u, (__int64)L"NetrServerAuthenticate: SessionKey %lu = ", v22, v25);
  NlpDumpBuffer(0x4000000, (__int64)&SessionKey, 0x10u);
  NlComputeCredentials(CC, pbOutput, &SessionKey, *&flag);    // 这里计算ClientCredential
  NlPrintRoutine(0x4000000u, (__int64)L"NetrServerAuthenticate: ClientCredential %lu GOT  = ", v22, v26);
  NlpDumpBuffer(0x4000000, 0i64, 8u);
  NlPrintRoutine(0x4000000u, (__int64)L"NetrServerAuthenticate: ClientCredential %lu MADE = ", v22, v27);
  NlpDumpBuffer(0x4000000, (__int64)pbOutput, 8u);
  if ( v0 == *(_QWORD *)pbOutput )    //验证接收的ClientCredential和刚计算出的是否相等
 
1: kd> p
netlogon!NetrServerAuthenticate3+0x399:
00007ffb`0df74799 488b4580        mov     rax,qword ptr [rbp-80h]
1: kd>
netlogon!NetrServerAuthenticate3+0x39d:
00007ffb`0df7479d 488b00          mov     rax,qword ptr [rax]
1: kd>
netlogon!NetrServerAuthenticate3+0x3a0:
00007ffb`0df747a0 483b45b0        cmp     rax,qword ptr [rbp-50h]
1: kd> db poi(rbp-80) l8
00000083`db04efc8  00 00 00 00 00 00 00 00                          ........
1: kd> db rbp-50 l8
00000083`db7de780  99 8f 65 7c 7e 91 e0 99                          ..e|~...
//SymCryptCfbEncrypt
 
  if ( num_8 >= num_1 )                         // a2=1
  {
    v9 = vars30;
    num_f = v4 - num_1;                         // f
    v11 = v5 - vars30;                          // vars38 - vars30 当然是8
    do
    {
      (*(void (__fastcall **)(__int64, char *, char *))(v22 + 8))(SessionKey, IV, &Src);// SymCryptAesEncrypt,结果保存在src中
 
      v12 = num_1;
      pSrc1 = &Src;
      pSrc2 = &Src;
      plaintext = (char *)v9;                   // 指向CC,vars38存放运算结果
      ......
      if ( v12 )
      {
        v16 = plaintext - pSrc2;                // 计算plaintext相对于src的偏移
        v17 = pSrc1 - pSrc2;                    // 在CFB8中这个值就是0
        do
        {
          pSrc2[v17] = *pSrc2 ^ pSrc2[v16];     // 将加密结果的第一个字节和下一个字节异或( plaintext 的下一个字节),并放回src
          ++pSrc2;
          --v12;
        }
        while ( v12 );
      }
      memcpy_0((void *)(v11 + v9), &Src, num_1);// 从vars38开始放入运算结果 ++
      memmove(IV, &IV[num_1], num_f);           // 整体向前移一位
      memcpy_0(&IV[num_f], &Src, num_1);        // 把加密结果的第一个字符放在末尾
      v8 -= num_1;
      v9 += num_1;
    }
    while ( v8 >= num_1 );
    v7 = v23;
  }
  return memcpy_0(v7, IV, v4);    //将最终结果复制到 v7 指向的内存
}
 
1: kd>
bcryptPrimitives!SymCryptCfbEncrypt+0x85:
00007ffb`0e6a9d19 41ff5708        call    qword ptr [r15+8]
1: kd> db rdx l10
00000083`db7de2a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
//SymCryptCfbEncrypt
 
  if ( num_8 >= num_1 )                         // a2=1
  {
    v9 = vars30;
    num_f = v4 - num_1;                         // f
    v11 = v5 - vars30;                          // vars38 - vars30 当然是8
    do
    {
      (*(void (__fastcall **)(__int64, char *, char *))(v22 + 8))(SessionKey, IV, &Src);// SymCryptAesEncrypt,结果保存在src中
 
      v12 = num_1;
      pSrc1 = &Src;
      pSrc2 = &Src;
      plaintext = (char *)v9;                   // 指向CC,vars38存放运算结果
      ......
      if ( v12 )
      {
        v16 = plaintext - pSrc2;                // 计算plaintext相对于src的偏移
        v17 = pSrc1 - pSrc2;                    // 在CFB8中这个值就是0
        do
        {
          pSrc2[v17] = *pSrc2 ^ pSrc2[v16];     // 将加密结果的第一个字节和下一个字节异或( plaintext 的下一个字节),并放回src
          ++pSrc2;
          --v12;
        }
        while ( v12 );
      }
      memcpy_0((void *)(v11 + v9), &Src, num_1);// 从vars38开始放入运算结果 ++
      memmove(IV, &IV[num_1], num_f);           // 整体向前移一位
      memcpy_0(&IV[num_f], &Src, num_1);        // 把加密结果的第一个字符放在末尾
      v8 -= num_1;
      v9 += num_1;
    }
    while ( v8 >= num_1 );
    v7 = v23;
  }
  return memcpy_0(v7, IV, v4);    //将最终结果复制到 v7 指向的内存
}
 
1: kd>
bcryptPrimitives!SymCryptCfbEncrypt+0x85:
00007ffb`0e6a9d19 41ff5708        call    qword ptr [r15+8]
1: kd> db rdx l10
00000083`db7de2a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
from Crypto.Hash import MD4
from Crypto.Cipher import AES
from termcolor import colored
import os, hmac, hashlib, struct
 
def getSessionKey(CC):
 
    SC = os.urandom(8)
    hstring = CC + SC
 
    secret = "testtest"    #密码我换了的
    u_secret = unicode_str(secret)
    hkey = MD4.new(data = u_secret).digest()
    #print "test hkey:",print_func(hkey)
 
    SessionKey =  hmac.new(hkey, hstring, hashlib.sha256).digest()[:16]
 
    #print "sessionkey",print_func(SessionKey)
    return SessionKey
 
def AES_128_CFB(Key,iv,String):
    cryptor = AES.new(key=Key, mode=AES.MODE_CFB, IV=iv,segment_size=128)
    ciphertext = cryptor.encrypt(String)
    return str(ciphertext)[0]
 
def unicode_str(sstr):
 
    res = ""
    for i in sstr:
        res += i
        res += "\x00"
    return res
 
def print_func(string):
 
    res = ""
 
    for i in range(len(string)):
        hexnum = hex(struct.unpack("B", string[i])[0])[2:]
        if len(hexnum) == 1:
            hexnum = "0" + hexnum
        res += hexnum
 
    return res
 
def vuln_func(SessionKey,challenge):
    ClientCredential = ""
    iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    string = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
 
    for i in range(8):
        res = AES_128_CFB(SessionKey,iv,string)
        insert_byte = struct.pack("B",struct.unpack("B",res)[0] ^ struct.unpack("B",challenge[i])[0])
        iv = iv[1:] + insert_byte
        ClientCredential += insert_byte
 
    text = "[*]SessionKey:" + print_func(SessionKey) + "    ClientCredential:" + print_func(ClientCredential)
 
    if ClientCredential.startswith('\x00'):
        print colored(text,'red')
        return True
    else:
        print colored(text,'blue')
 
    return False
 
if __name__ == "__main__":
 
    total = 0
 
    for t in range(2000):    #可以改小一些
        for i in range(2000):
            challenge = "\x41\x41\x41\x41\x41\x41\x41\x41"
            SessionKey = getSessionKey(challenge)
            if vuln_func(SessionKey,challenge):
                total += i+1
                print "[*] The number of attempts({}): {}".format(str(t+1),str(i+1))
                break
 
    print "[+] Average times: "+ str(total/2000)    #可以改小一些
from Crypto.Hash import MD4
from Crypto.Cipher import AES
from termcolor import colored
import os, hmac, hashlib, struct
 
def getSessionKey(CC):
 
    SC = os.urandom(8)
    hstring = CC + SC
 
    secret = "testtest"    #密码我换了的
    u_secret = unicode_str(secret)
    hkey = MD4.new(data = u_secret).digest()
    #print "test hkey:",print_func(hkey)
 
    SessionKey =  hmac.new(hkey, hstring, hashlib.sha256).digest()[:16]
 
    #print "sessionkey",print_func(SessionKey)
    return SessionKey
 
def AES_128_CFB(Key,iv,String):
    cryptor = AES.new(key=Key, mode=AES.MODE_CFB, IV=iv,segment_size=128)
    ciphertext = cryptor.encrypt(String)
    return str(ciphertext)[0]
 
def unicode_str(sstr):
 
    res = ""
    for i in sstr:
        res += i
        res += "\x00"
    return res
 
def print_func(string):
 
    res = ""
 
    for i in range(len(string)):
        hexnum = hex(struct.unpack("B", string[i])[0])[2:]
        if len(hexnum) == 1:
            hexnum = "0" + hexnum
        res += hexnum
 
    return res
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-12-10 18:23 被嫣语菲菲~编辑 ,原因:
收藏
免费 5
支持
分享
打赏 + 20.00雪花
打赏次数 1 雪花 + 20.00
 
赞赏  mb_msepdwyb   +20.00 2021/02/26 多谢大佬告诉我版本!我的汇报有救了555
最新回复 (5)
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
能告诉以下windows版本吗?好几个版本逆向都看不见关键地方的代码
2021-2-25 00:11
0
雪    币: 2671
活跃值: (4542)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
3
Windows Server 2012 R2
2021-2-26 18:23
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
SymCryptCfbEncrypt找不到,但是引用了bcrypt.dll的BCryptEncrypt函数,不清楚这个函数是不是
2021-2-27 19:02
0
雪    币: 2671
活跃值: (4542)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
5
mb_msepdwyb SymCryptCfbEncrypt找不到,但是引用了bcrypt.dll的BCryptEncrypt函数,不清楚这个函数是不是
bcryptPrimitives!SymCryptCfbEncrypt,这里断不下来嘛
2021-3-1 14:15
0
雪    币: 181
活跃值: (621)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
mb_msepdwyb SymCryptCfbEncrypt找不到,但是引用了bcrypt.dll的BCryptEncrypt函数,不清楚这个函数是不是
Sym系列的最后都和Bcrypt挂上勾了,毕竟是一家人出来的东西
2021-5-23 12:54
0
游客
登录 | 注册 方可回帖
返回
//