在计算机安全领域,什么是信任呢?现代安全解决方案——遇到恶意代码或者恶意操作弹个窗提示下——提供的隐含的安全感?还是企业里对某个工作必需的软件经过认真评估后的信任?实际上,并没有唯一的正确答案。信任本质上是主观的。重要的是,每个组织都要认真考虑在技术层面信任的意义。即便具有成熟信任定义的组织,也应该质疑下由安全解决方案和操作系统验证过的信任是否可信。
既然你的脑子里有了关于信任对你意味着什么,不包括涉及人工干预的代码审查,什么是信任验证的技术手段?这显然是一个难以回答的问题,当然你也可能没有问过自己。本白皮书的目的是展示微软的Windows是怎样决策信任的。通过展示如何在Windows中颠覆信任,您将有机会有更多的机会多问问自己,信任对您而言到底意味着什么——一些在安全方面非常重要和不清楚的概念。
除了验证签名代码的来源和完整性之外,代码签名和信任验证也是许多安全产品(例如防病毒和EDR解决方案)的重要恶意软件分类组件。适当的信任验证也是大多数应用程序的执行组件白名单解决方案(AppLocker,Device Guard等)。 在许多情况下颠覆Windows的信任架构也有可能颠覆安全产品的功效。
使用Authenticode .aspx)数字签名,可以验证来自于特定供应商的可执行代码的合法性。在用户模式下,验证签名代码的信任的API是WinVerifyTrust .aspx)和WinVerifyTrustEx .aspx)(它只是WinVerifyTrust的包装器,具有更明确定义的函数原型)。
随着Windows的更新迭代,同时也有必要扩展签名和信任架构以便支持额外的文件格式和二进制Blob格式,签名可能就需要以不同的格式存储,信任也跟着这种技术以特有的方式进行验证。例如,数字签名以特定的PE文件格式 存储二进制格式。PowerShell脚本,从另一方面来看的话也是可以签名的文本文件,因此可以理解其签名需要不同的存储格式。另外,当签名代码时,需要计算要签名的代码的哈希(通常称为签名证书哈希),并且根据文件/ blob格式执行此操作的方式是不同的。可以理解,关于数字签名的认证,设备驱动程序的信任的验证方法与HTTPS证书的信任方式是不同的。
考虑到需要支持独特格式的数字签名,并以独特的方式执行信任验证,微软设计了可扩展架构来支持这一点。主题接口包(subject interface package .aspx),SIP)架构旨在支持数字签名的创建,检索和哈希计算、验证。使用信任提供者 .aspx)来执行签名代码的信任验证。通过使用WinVerifyTrust和wintrust.dll、crypt32.dll中的各种导出函数, 信任提供者和SIP架构帮助软件开发人员从执行代码签名和信任验证的具体步骤中完全抽象出来。在撰写本文时,没有证据表明该架构的文档已被扩展到可支持第三方软件开发人员希望支持的其特定文件格式的签名。这可能是因为无论格式如何变化任何文件都可以在技术上通过使用目录签名(catalog signing ,一种包含可以被认证码签名的文件哈希列表的文件格式)进行“签名”。需要注意的是编录文件的验证只有在“CryptSvc”服务运行的情况下才能生效。
除了各种Windows SDK 头文件以及零星的关于wintrust.dll和crypt32.dll导出函数的MSDN文档 .aspx)外,信任提供者和SIP并没有文档化。由于第三方实施的复杂性,微软可能故意选择对这些架构不进行文档化。该白皮书用于记录信任提供者和SIP架构,同时还解释攻击者如何将其滥用的方式作为破坏信任的手段,并且在执行信任验证的进程的上下文中获取代码执行的权限。
本白皮书中主要介绍的是CryptoAPI .aspx#_security_cryptoapi_gly)的可扩展性,主要包括加密编码、解码、证书管理等。微软不可能预见未来的加密要求,所以他们设计了一个完全可扩展的架构(大概可追溯到90年代初),以适应当前和未来的需求。不幸的是,这一非常广泛的可扩展性,有可能允许攻击者(具有提升的权限)来劫持现有的功能。
怎么知道一个可执行文件是否被签名呢?一个最简单直接的方法就是右键查看文件属性,然后切换到数字签名选项卡。
“数字签名”选项卡显示是否存在嵌入的认证签名
虽然这种方法可以用来确认某些文件类型是否被签名,如上图所示的ise.psm1(PowerShell脚本模块 .aspx)文件)的情况,但这远远不是枚举可签名文件类型的系统方法。对文件类型的签名支持只是SIP(负责数字签名的创建、检索和哈希计算、验证的体系结构)的部分实现。
以下是ise.psm1中嵌入签名的一部分:
这就是PowerShell代码中的签名如何存储的(MOF文件是一个例外)。 为了使问题复杂化,可以签名的每种文件类型都以独特的方式存储其签名。 例如,PE签名认证规范 解释了如何在PE文件(如EXE、DLL、SYS等)中存储和验证签名。
位于crypt32.dll(常通过WinVerifyTrust .aspx)间接调用)中的一个函数CryptSIPRetrieveSubjectGuid .aspx),用于发现与特定文件类型相关联的SIP的功能。 给定文件名和可选句柄,CryptSIPRetrieveSubjectGuid返回一个GUID,表示可以处理检索嵌入认证签名的SIP。 功能大致如下:
“CryptSIPDllIsMyFileType”的功能原型文档戳我 .aspx),“CryptSIPDllIsMyFileType2”的功能原型文档戳我 .aspx)。 如果有实现,“CryptSIPDllIsMyFileType” 函数首先被调用,如果其中一个函数返回“TRUE”,则返回处理签名的SIP GUID。 在实践中(至少在Windows 10上),没有SIP实现“CryptSIPDllIsMyFileType”,所以随后调用“CryptSIPDllIsMyFileType2”函数来尝试解决处理SIP。 例如,PowerShell(SIP GUID:603BCC1F-4B59-4E08-B724-D2C6297EF351)将CryptSIPDllIsMyFileType2实现为pwrshsip!PsIsMyFileType。 经过反汇编、反编译及整理输出,这里是PsIsMyFileType
函数的C语言版的原型:
从C代码中可以看出,如果任何文件有任何上述扩展名,那么PowerShell SIP将是用作代码签名的SIP。 “CryptSIPDllIsMyFileType2”不一定要检查文件扩展名,SIP还可以选择打开文件句柄并检查文件中的魔数值,以确定正确的文件/blob SIP处理顺序。
其他支持的SIP文件类型处理函数如下(非详尽列表):
对于读者来说, 逆向上面的某些函数来查看Windows所支持代码签名的文件或二进制blob的类型,这将是一个很有价值的练习。
一旦需要检索签名的软件获得该 SIP 的 GUID, 那它就可以继续提取该证书。
一旦负责处理特定文件/二进制Blob格式的签名的SIP通过其各自的GUID标识符被识别,WinVerifyTrust 就会知道如何从该文件中获取数字签名并验证其计算出的哈希对嵌入在数字中的签名哈希签名。为实现这一点, WinVerifyTrust 在注册表中调用以下函数:
SIP 签名检索功能位置:
SIP 哈希验证函数:
CryptSIPDllGetSignedDataMsg .aspx)和CryptSIPDllVerifyIndirectData .aspx)的函数原型在MSDN有文档,同样也存在于Windows SDK中的mssip.h头文件中。
SIP签名检索功能原型:
SIP 哈希验证函数原型:
SIP_SUBJECTINFO .aspx) SIP_INDIRECT_DATA .aspx) 1387/5000 提供给这些函数的参数由调用信任提供者负责填充(有关信任提供者架构的更多细节,请参见以下部分)。当CryptSIPGetSignedDataMsg被调用时,SIP将提取编码的数字签名(最常用的是CERT_SIGNED_CONTENT_INFO .aspx)结构体,ASN.1 PKCS_7_ASN_ENCODING和X509_ASN_ENCODING编码),并通过“pbSignedDataMsg”参数返回。CERT_SIGNED_CONTENT_INFO内容由签名证书(包括其发行链)、用于对文件进行哈希和签名的算法以及文件的签名散列组成。调用信任提供者然后对数字签名进行解码,提取散列算法和签名哈希值,然后将它们传递给CryptSIPVerifyIndirectData。在校验认证码哈希计算并与已签名哈希进行比较后,如果匹配,则CryptSIPVerifyIndirectData返回TRUE,否则返回FALSE,然后WinVerifyTrust将返回一个错误,表明哈希不匹配。
CryptSIPVerifyIndirectData是最重要的数字签名验证功能之一,但这将会犯错:因为攻击者可以将现有的合法数字签名应用于其恶意软件——这是一种在野 的攻击技术。 以下是一个适用于合法认证码签名的恶意软件示例的哈希失真的示例:在使用微软认证码签名的未签名文件上显示哈希不匹配错误的示例(注意相同的SignerCertificate指纹值)
未签名的文件在应用于签名文件的认证码签名时无法验证。微软就是这么设计的。
到目前为止,已经讨论了SIP的基本架构。如现在应该理解的,SIP仅负责数字签名应用、检索和散列计算、验证。应用于文件的数字签名的存在是无意义的,除非某些标准被实际验证。这就是信任提供者发挥作用的地方——除了内置到所需的信任提供者中的标准之外,还可以根据调用者指定的参数组合对WinVerifyTrust进行验证。
像SIP一样,信任提供者也由GUID唯一标识。 截至到Windows 10,有以下信任提供者存在:
信任提供者的部分组件的声明在MSDN和windowsSDk的SoftPub.h的文档中能找到,但是它们的实现并没有文档化。对开发人员而言,这就需要从信任证书、签名、信任链、吊销和时间戳正确执行验证。开发人员调用WinVerifyTrust使用的更常见的信任提供程序之一是WINTRUST_ACTION_GENERIC_VERIFY_V2以用来确认通用校验码签名。如果需要在用户模式下验证驱动程序的可信性任, 则应使用 DRIVER_ACTION_VERIFY。
与 sip 一样, 信任提供程序也在注册表中注册了以下项:
在"信任"键中,是一个子项列表,对应于可能发生的每个信任提供程序验证步骤:初始化(Initialization)、消息(Message)、签名(Signature)、证书(Certificate)、认证检查(CertCheck)、最终策略(FinalPolicy)、诊断策略(DiagnosticPolicy)和清理(Cleanup)。其中的每个密钥都是实现每个步骤的信任提供程序 guid (不是所有的都是必需的. 例如, 证书检查、诊断策略和清理)。在每个各自的 GUID 子项中, 都是由注册表里的 dll 和导出函数来实现信任提供程序步骤的$DLL
和 $Function
。在注册表中注册的信任提供者的示例
每个信任提供程序步骤的用途可以大致细分如下:
了解信任提供者和 sip 在注册表中注册的合法方法, 以便了解攻击者如何利用注册过程 (或完全颠覆它),这非常重要。
SIP通过调用DllRegisterServer .aspx)的导出函数“wintrust!CryptSIPAddProvider .aspx)”来完成注册。这使得SIP可以通过调用 “regsvr32.exe SIPfilename.dll” 来完成注册。CryptSIPAddProvider 需要 SIP_ADD_NEWPROVIDER .aspx) 结构体, 它由在实现签名功能的SIP DLL中的导出函数组成。需要以下SIP_ADD_NEWPROVIDER字段:
下列 SIP_ADD_NEWPROVIDER 字段是可选的:
在调用 CryptSIPAddProvider 时, wintrust.dll 将各自的导出函数名和实现 dll 添加到HKLM\SOFTWARE\[WOW6432Node\]Microsoft\Cryptography\OID\EncodingType 0
注册表子键中。 SIP dll 还应实现 DllUnregisterServer .aspx) 注销功能, 该函数调用 CryptSIPRemoveProvider .aspx) 删除所有相关的 SIP 注册表项。
信任提供者通过调用 DllRegisterServer的导出函数wintrustWintrustAddActionID .aspx)实现。这使得信任提供者可以通过调用 "regsvr32.exe TrustProviderfilename.dll" 来正式注册。 WintrustAddActionID 需要一个CRYPT_REGISTER_ACTIONID .aspx)——由在执行所有信任验证步骤的信任提供程序 DLL 中的导出函数组成的结构体,。信任提供程序注册功能可以与 SIP 注册的函数共享, 也可以在专用 DLL 中独立。
在调用 WintrustAddActionID 时, wintrust.dll 将各自的导出函数名和实现 dll 添加到HKLM\SOFTWARE\[WOW6432Node\]Microsoft\Cryptography\Providers\Trust
注册表子键中。 信任提供者通过调用DllUnregisterServer的导出函数 wintrust! WintrustRemoveActionID .aspx) 来取消注册。
最重要的信任提供者注册位于wintrust!DllRegisterServer中,执行以下注册步骤:
调用 WintrustDllRegisterServer a. 调用 wintrust!CryptRegisterOIDFunction函数,使用CryptEncodeObject 和 CryptDecodeObject注册 ASN.1编码/解码例程。这类的许多函数在创建数字签名时被调用。在分析数字签名以进行验证时, 通常会调用它们的解码对应函数。与 SIP 和信任提供程序注册一样, 这些实现函数也存储在注册表中:
所有这些编码函数都接受以下函数签名:
WintrustDllRegisterServer 注册以下编码/解码例程:
i. 1.3.6.1.4.1.311.2.1.15 (SPC_PE_IMAGE_DATA_OBJID) 函数: wintrust!WVTAsn1SpcPeImageDataEncode ii. 1.3.6.1.4.1.311.2.1.25 (SPC_CAB_DATA_OBJID) 函数: wintrust!WVTAsn1SpcLinkEncode iii. 1.3.6.1.4.1.311.2.1.20 (SPC_JAVA_CLASS_DATA_OBJID) 函数: wintrust!WVTAsn1SpcLinkEncode iv. 1.3.6.1.4.1.311.2.1.28 (SPC_LINK_OBJID) 函数: wintrust!WVTAsn1SpcLinkEncode v. 1.3.6.1.4.1.311.2.1.30 (SPC_SIGINFO_OBJID) 函数: wintrust!WVTAsn1SpcSigInfoEncode vi. 1.3.6.1.4.1.311.2.1.4 (SPC_INDIRECT_DATA_OBJID) 函数: wintrust!WVTAsn1SpcIndirectDataContentEncode vii. 1.3.6.1.4.1.311.2.1.10 (SPC_SP_AGENCY_INFO_OBJID) 函数: wintrust!WVTAsn1SpcSpAgencyInfoEncode viii. 1.3.6.1.4.1.311.2.1.26 (SPC_MINIMAL_CRITERIA_OBJID) 函数: wintrust!WVTAsn1SpcMinimalCriteriaInfoEncode ix. 1.3.6.1.4.1.311.2.1.27 (SPC_FINANCIAL_CRITERIA_OBJID) 函数: wintrust!WVTAsn1SpcFinancialCriteriaInfoEncode x. 1.3.6.1.4.1.311.2.1.11 (SPC_STATEMENT_TYPE_OBJID) 函数: wintrust!WVTAsn1SpcStatementTypeEncode xi. 1.3.6.1.4.1.311.12.2.1 (CAT_NAMEVALUE_OBJID) 函数: wintrust!WVTAsn1CatNameValueEncode xii. 1.3.6.1.4.1.311.12.2.2 (CAT_MEMBERINFO_OBJID) 函数: wintrust!WVTAsn1CatMemberInfoEncode xiii. 1.3.6.1.4.1.311.12.2.3 (CAT_MEMBERINFO2_OBJID) 函数: wintrust!WVTAsn1CatMemberInfo2Encode xiv. 1.3.6.1.4.1.311.2.1.12 (SPC_SP_OPUS_INFO_OBJID) 函数: wintrust!WVTAsn1SpcSpOpusInfoEncode xv. 1.3.6.1.4.1.311.2.4.2 (szOID_INTENT_TO_SEAL) 函数: wintrust!WVTAsn1IntentToSealAttributeEncode xvi. 1.3.6.1.4.1.311.2.4.3 (szOID_SEALING_SIGNATURE) 函数: wintrust!WVTAsn1SealingSignatureAttributeEncode xvii. 1.3.6.1.4.1.311.2.4.4 (szOID_SEALING_TIMESTAMP) 函数: wintrust!WVTAsn1SealingTimestampAttributeEncode
接下来, SoftpubDllRegisterServer 调用 WintrustAddActionID 来注册下列信任提供者: a. WINTRUST_ACTION_GENERIC_VERIFY_V2 b. WIN_SPUB_ACTION_PUBLISHED_SOFTWARE c. WIN_SPUB_ACTION_PUBLISHED_SOFTWARE_NOBADUI d. WINTRUST_ACTION_GENERIC_CERT_VERIFY e. WINTRUST_ACTION_TRUSTPROVIDER_TEST f. HTTPSPROV_ACTION. 下面的相关默认 "用法" .aspx) 也注册 (全部存储在注册表HKLM\SOFTWARE\[WOW6432Node\]Microsoft\Cryptography\Providers\Trust\Usages
中): i. 1.3.6.1.4.1.311.10.3.3 (szOID_SERVER_GATED_CRYPTO) Alloc/dealloc 函数: wintrust!SoftpubLoadDefUsageCallData ii. 1.3.6.1.5.5.7.3.1 (szOID_PKIX_KP_SERVER_AUTH) Alloc/dealloc 函数: wintrust!SoftpubLoadDefUsageCallData iii. 1.3.6.1.5.5.7.3.2 (szOID_PKIX_KP_CLIENT_AUTH) Alloc/dealloc 函数: wintrust!SoftpubLoadDefUsageCallData iv. 2.16.840.1.113730.4.1 (szOID_SGC_NETSCAPE) Alloc/dealloc 函数: wintrust!SoftpubLoadDefUsageCallData g. DRIVER_ACTION_VERIFY h. WINTRUST_ACTION_GENERIC_CHAIN_VERIFY
最后, mssip32DllRegisterServer 被调用来注册SIP。具体来说, 调用 CryptSIPAddProvider 来注册以下SIP: a. DE351A42-8E59-11D0-8C47-00C04FC295EE CRYPT_SUBJTYPE_FLAT_IMAGE b. C689AABA-8E78-11d0-8C47-00C04FC295EE CRYPT_SUBJTYPE_CABINET_IMAGE c. C689AAB8-8E78-11D0-8C47-00C04FC295EE CRYPT_SUBJTYPE_PE_IMAGE d. DE351A43-8E59-11D0-8C47-00C04FC295EE CRYPT_SUBJTYPE_CATALOG_IMAGE e. 9BA61D3F-E73A-11D0-8CD2-00C04FC295EE CRYPT_SUBJTYPE_CTL_IMAGE
mssip32DllRegisterServer 还显式注销了以下 sip (实际上, Java SIP 组件保留在windows默认的注册表中):
a. C689AAB9-8E78-11D0-8C47-00C04FC295EE
CRYPT_SUBJTYPE_JAVACLASS_IMAGE
b. 941C2937-1292-11D1-85BE-00C04FC295EE
CRYPT_SUBJTYPE_SS_IMAGE .aspx)
虽然不建议这样做, 但所有 wintrust 的信任提供程序和 SIP 注册都可以使用以下命令 (从提升权限的命令提示符中) 正式注销:regsvr32.exe /u C:\Windows\System32\wintrust.dll
运行上述命令将剥离 Windows 在用户模式下的 执行大多数数字签名检索和信任验证的用户模式的能力。
虽然在前面的 "消息" 信任提供者步骤中提到了 SIP 和信任提供者之间的交互, 不过按顺序说明所有步骤的图表应该更有用吧。WinVerifyTrust、信任提供者和SIP之间的交互说明 希望到目前为止, 对信任提供者和SIP的角色有一个基本的了解, 以及它们的体系架构在很大程度上是通过注册注册表来实现模块化的。在下一节中, 将讨论对 Windows 信任体系结构的模块化的攻击。
通过对 Windows 用户模式信任体系结构的基本了解以及较高的权限级别, 攻击者拥有了他需要破坏信任体系的武器。那么攻击者通过颠覆信任可以来实现什么呢?
此外, 如前所述, CryptSIPDllGetSignedDataMsg 函数具有以下原型:
任何熟悉 c/c++ 的攻击者都能够轻松地实现此类功能, 并将现有的 SIP 条目替换为其恶意功能。首先, 了解每个参数的含义是很重要的:
因此,如果攻击者要实现此功能并使用它作为示例,来覆盖可执行文件的SIP(C689AAB8-8E78-11D0-8C47-00C04FC295EE)的CryptSIPDllGetSignedDataMsg组件,则任何 PE 文件都可能返回攻击者选择的任意数字签名。
想象一下下面虚构的攻击场景:
攻击者在注册表中实现了可执行文件SIP的CryptSIPDllGetSignedDataMsg组件。
简单地说, 无论是否有嵌入的验证码签名, 为任何可执行文件返回相同的 Microsoft 证书。
为了确保返回适当格式的数字签名,最好在对其进行劫持之前在调试器中的合法CryptSIPDllGetSignedDataMsg上设置断点。这样做可以确保PKCS#7认证签名数据始终可以正确地返回。
a. 在 PowerShell 脚本中, 这涉及 base64 解码 "SIG # 开始签名块"(SIG # Begin signature block)。 b. 在带有嵌入验证码签名的PE文件中, PKCS #7 校验签名的数据存在于PE校验码规范(PE Authenticode specification) 中所记录的嵌入式WIN_CERTIFICATE .aspx)结构体的bCertificate 字段中。 c. 编录文件本身就是 PKCS #7 校验码签名的数据 (实际上可以在嵌入的 PE 校验码签名中使用)。
在这种攻击场景中,被劫持的CryptSIPDllGetSignedDataMsg可以返回用于签署许多系统组件(如notepad.exe)的目录文件的字节。为了方便地确定与已签名文件关联的编录文件,可以使用 sigcheck.exe:sigcheck -i C:\Windows\System32\notepad.exe
在当前的例子中,返回下面的编录文件路径:C:\WINDOWS\system32\CatRoot\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\Microsoft-Windows-Client-Features-Package-AutoMerged-shell~31bf3856ad364e35~amd64~~10.0.15063.0.cat
现在,攻击者实现只需要从该编录文件返回字节,使任何PE文件看起来都使用了与notepad.exe相同的证书进行签名。模块化设计方法是将所需的签名内容嵌入到攻击者提供的SIP DLL中的资源中。 下面的示例说明了 PowerShell SIP CryptSIPDllGetSignedDataMsg 组件是如何使用自定义的恶意 SIP来劫持的, 它将始终返回与 PowerShell 文件相同的合法 Microsoft 证书:PowerShell CryptSIPDllGetSignedDataMsg 劫持的演示
可以看出, 在劫持之前, 不出所料,test.ps1 显示为未签名。然而, 在劫持发生后,test.ps1 似乎是用 Microsoft 证书签名的:一个未经签名的 PowerShell 脚本, 似乎突然间就被微软给签名了
虽然未签名的 PowerShell 脚本看起来由 Microsoft 签名了, 但它的哈希验证并没有通过。
虽然看起来劫持是成功的, 但有一个缺陷-签名无法验证, 因为计算的哈希与数字签名中的已签名哈希不匹配。此劫持的另一个不良影响是,任何PowerShell代码都将使用相同的数字签名, 这将在大多数情况下导致哈希不匹配。
为了防止信任验证因哈希不匹配而失败, 还需要劫持CryptSIPDllVerifyIndirectData 。
正如前面的劫持场景中所解释的,劫持已注册SIP的CryptSIPDllGetSignedDataMsg组件允许未经签名的代码看起来像是被签名了。但是,考虑到哈希值不匹配,数字签名将无法在攻击者提供的代码上进行验证。然而, 再劫持 CryptSIPDllVerifyIndirectData 下函数就不存在这个问题了。 再次提醒下, CryptSIPDllVerifyIndirectData 实现存储在以下注册表值中:
函数原型:
调试CryptSIPVerifyIndirectData的合法实现,可以确认当计算出的验证码哈希与签名的哈希值匹配时,CryptSIPVerifyIndirectData返回TRUE。因此,所有恶意SIP需要做的就是为被劫持的相应SIP匹配生成,返回TRUE,从而使之看起来可以通过哈希验证。继续执行PowerShell劫持示例,恶意SIP仅为哈希验证例程返回true,将解决攻击者提供的代码无法正确验证的问题。
接下来, 劫持哈希验证处理程序 (连同以前的劫持签名检索功能) 将通过所有的检查, 将未经签名的 PowerShell 代码伪装为已签署Microsoft的代码:
劫持 PowerShell SIP 的 CryptSIPVerifyIndirectData 组件
现在, 未签名的 PowerShell 文件出现签名并经过正确验证
"数字签名" UI 选项卡显示一个未签名的 PowerShell 文件, 它显示签名并经过正确验证。
Sysinternals sigcheck 显示一个未签名的 PowerShell 文件, 它显示签名并经过正确验证。
一个更理想的劫持场景是甚至懒得劫持 CryptSIPDllGetSignedDataMsg 的目标 SIP。 相反, 只需应用合法的验证码签名 (例如 从 C:\Windows\System32\WindowsPowerShell\v1.0\Modules\ISE\ise.psm1) 到攻击者提供的代码, 并只劫持 CryptSIPVerifyIndirectData。这样做为攻击者提供了以下好处:
test.ps1 具有和ise.psm1相同的嵌入式校验码签名, 并且证书指纹值相匹配
虽然目前为止示例集中在 PowerShell SIP 上, 但这些劫持原则适用于所有SIP。下面是一个被劫持的可执行文件的SIP (C689AAB8-8E78-11D0-8C47-00C04FC295EE) 的示例, 它将合法的 Microsoft 数字签名应用于攻击者提供的二进制文件上:
notepad_backdoored.exe拥有应用于本属于notepad.exe (目录签名) 的数字签名
"数字签名" UI 选项卡还确认攻击者-suppled notepad_backdoored. exe 验证为已签名的 Microsoft 文件。
此劫持将骗过任何执行用户模式信任/签名验证的程序, 包括 Sysinternals 的Process Explorer:
notepad_backdoored.exe 在 Sysinternals 的Process Explorer中显示为“已验证签名"。
在应用程序白名单方案中, 使用未经签名的/未经批准的二进制文件来验证信任的机制,构成了一个 "鸡和蛋" 问题,即需要根据部署的恶意 SIP DLL 的信任来验证白名单政策。结果是, 至少使用设备保护,系统将无法加载恶意的 SIP DLL, 不过这将导致信任验证在许多情况下失败。这可以理解地有可能导致系统稳定性问题。理想情况下 (对于攻击者) 将有一个可以为 CryptSIPVerifyIndirectData 角色提供服务的签名 DLL。幸运的是,回想一下,CryptSIPVerifyIndirectData 函数接受以下函数签名:
此外, 为了通过验证检查, 函数必须返回 TRUE。因此, 我们面临以下要求, 以产生一个签名的CryptSIPVerifyIndirectData 函数:
毫无疑问, 这样一个查找候选函数的过程可以通过将函数转换为中间语言来进行自动分析, 而不需要很长时间就能找到候选输出函数-ntdll!DbgUiContinue:
ntdll!DbgUiContinue的反汇编及注释
只需将目标 SIP 的 CryptSIPVerifyIndirectData 注册表项设置为 C:\Windows\System32\ntdll.dll
和 DbgUiContinue
,就足以通过对任何应用了合法的嵌入校验码签名的代码进行哈希校验检查。实际上,在对强制启用设备保护的系统上的可执行文件 SIP 进行测试时, 攻击者提供的代码被阻止执行。但是, 劫持 PowerShell SIP 启用了受约束的语言模式绕过, 从而实现了任意的、无签名的代码执行。不过,对于使用可执行文件与 PowerShell 代码进行的其他 (可能是内核支持的) 信任断言, 还不清楚。也有可能存在比DbgUiContinue更好用的劫持函数 , 但这足以证明攻击者提供的无签名的 SIP DLL足够可以用来劫持。
下面的示例演示了PowerShell在启用了设备保护的约束语言模式下,防止在发生劫持事件之前执行添加类型,并防止 CryptSIPVerifyIndirectData 在被劫持之后进行后续旁路操作:
在劫持之前,由于受限的语言模式,test.psm1中的代码将被阻止执行
在 "签名代码重用" 攻击发生之后,将绕过受约束的语言模式。
尽管这种形式的劫持并不代表完全接管强制设备保护用户模式完整性 (UMCI),但它确实从隐形角度提出了一种良好的劫持方法,因为它不需要攻击者将任何恶意代码丢弃到磁盘-即攻击者提供 SIP。
正如在 "信任提供者体系架构" 部分中所描述的那样, 最终的信任决策由信任提供程序的 最终策略
组件进行。这是 FinalPolicy 的函数签名:
FinalPolicy 为各自的信任提供者实现功能,其位于这里:
HKLM\SOFTWARE\[WOW6432Node\]Microsoft\Cryptography\Providers\Trust\FinalPolicy\{trust provider GUID}
虽然攻击者可以选择实现自己的信任提供程序 DLL 来颠覆 FinalPolicy,但这需要攻击者将恶意代码在硬盘落地。此外, 与 SIP 相比, 完全实现信任提供者的接口比较复杂。不过,如前所述,可以用已签名代码来劫持 FinalPolicy,以此来模拟传递其所有检查。备选的已签名的劫持函数需要满足以下要求:
未实现的导出函数 wintrust!SoftpubCleanup 满足了执行劫持的所有要求。
SoftpubCleanup函数的反汇编
转化为C语言的话,等效于下面的内容:
例如, 设置 WINTRUST_ACTION_GENERIC_VERIFY_V2 (00AAC56B-CD44-11D0-8CC2-00C04FC295EE) 的 FinalPolicy 组件将导致许多签名验证工具 (如AuthenticodeSignature、sigcheck、signtool 等)认为未签名的代码或应用合法签名的代码作为受信任的。在实践中, 使用 SoftpubCleanup 执行此劫持会导致进程资源管理器 (procexp)必现崩溃。
将合法的 microsoft 校验码数字签名应用于攻击者提供的代码劫持目标 SIP 的 CryptSIPVerifyIndirectData 组件的额外影响是, 默认情况下, 它将从启动中隐藏, 而不显示 "microsoft" 或 "Windows"项。
随着可执行程序 SIP 劫持完成,默认情况下,一个持久的攻击者提供的 EXE 不会出现:
在Autoruns的默认视图中看不到notepad_backdoored.exe
但是, 当 取消选择"隐藏 Microsoft 条目" 和 "隐藏 Windows 条目" 时, Run 键中的恶意条目将变为可见:
确认只有在取消选择 "隐藏 Windows 条目" 时才会出现 notepad_backdoored.exe
了解了如何劫持 SIP和信任提供者,应该清楚的是,除了颠覆信任之外,这些劫持攻击还允许在执行代码签名或签名验证的任何应用程序的上下文中执行代码。通过实现 SIP 或信任提供程序,代码可能在下列列表的程序中执行:
可以通过在进程监视器(Process Monitor)中筛选下列注册表项路径来发现其他的代码执行和持久化:
在使用攻击者提供的代码劫持信任提供程序时,出于稳定性考虑,一种可能的是将恶意逻辑作为 "诊断策略(DiagnosticPolicy)" 组件的一部分来实现, 以不影响合法的信任功能。
当试图在 SIP 的上下文中获取代码执行时, 一种获取代码执行考虑可能是在 "CryptSIPDllIsMyFileType" 组件中实现恶意逻辑, 并返回 "FALSE",表示其他 "CryptSIPDllIsMyFileType" 和 "CryptSIPDllIsMyFileType2 "组件应该被调用,以确定哪个 SIP 代表了有问题的文件。然而, 要注意的是, 任何武器化方案都有它自己独特的一套指标或折中方案来确保恶意代码可以被签名。
最后一个考虑是, SIP 和信任提供者 dll 不需要在注册表中指定它们的完整路径。如果只指定了 SIP 或信任提供程序文件名, 则通过标准的 DLL 加载顺序来加载它。这使攻击者能够在不需要修改注册表的情况下劫持现有的 SIP/信任提供程序 dll。例如, 在 Windows 10 中, Microsoft Office SIP VBA 宏 sip (9FA65764-C36F-4319-9737-658A34585BB7) 只使用其文件名注册 (仅限 WoW64): mso.dll。此外, 仅在指定了 "mso.dll" 的文件名的情况下, 在执行用户模式信任验证的任何代码中都有可能出现泛型 dll 加载顺序劫持漏洞。
虽然默认情况下未启用,但是启用 Microsoft-Windows-CAPI2/Operational 事件日志可能是获取失败的信任验证相关的上下文信息的宝贵来源。每当调用 WinVerifyTrust 时,都会生成 EID 81,如果签名或信任验证失败, 则事件将被填充为错误。例如, 以下是与 "notepad_backdoored" 的失败的信任验证相关的事件详细信息,它具有合法的 Microsoft 认证数字签名 (相关部分加粗):
上面的事件是一个 "错误" 事件。在本例中, 如果可执行程序 SIP 的 CryptSIPVerifyIndirectData 组件被劫持, 则 WinVerifyTrust 事件仍将被记录, 但作为 "信息" 事件表示信任验证成功:
因此, 虽然 Microsoft-Windows-CAPI2/Operational 事件日志可以提供有价值的攻击上下文 (主要是文件路径和验证过程的名称)信息,但它的预期行为可以被信任验证攻击来破坏掉。
以下建议旨在帮助在实现恶意 SIP 时缓解检被检测到的可能性:
如果要注册一个新的 SIP GUID,请使用以前定义过的一个但是当前未注册的文件,并应用与所使用的SIP GUID 相同的文件名和导出函数名称。例如, Silverlight 具有以下 GUID 的 SIP: BA08A66F-113B-4D58-9329-A1B37AF30F0E
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!