处理 DCOM 加固,第二部分:拆卸和重组 .NET DLL





5.00/5 (8投票s)
通过逆向工程不兼容的 .NET 应用程序来缓解 DCOM 加固引起的问题
引言
在我之前的文章中,我解释了你可以缓解 DCOM 加固客户端影响的非侵入性方法。但是,在某些情况下可能无法做到这一点,这时就需要你作为开发人员介入。
在我们开始之前,我确实想提一下,这涉及到我的孩子们称之为“黑客行为”的东西,因此出于显而易见的原因,你不应该在有供应商支持的稳定生产系统上这样做。我们讨论的是一种没有其他可能支持的情况,只有两个选择:a) 保持停滞直到整个系统迁移完成,或者 b) 勉强维持直到整个系统迁移完成。
在这种情况下,我们在 Windows 2008R2 系统上有一个 .NET 应用程序,该应用程序无法通过 DCOM 连接到服务器。这是一个非常小的 EXE 文件,其中包含大量库和组件,这些组件在启动时会被加载。这对于业务应用程序来说很典型,因为业务应用程序通常是模块化的,并且设计为与大量可能适用于或不适用于此特定系统的不同接口协同工作。
我还应该指出,没有任何保证。让应用程序以非预期的方式工作是一项没有说明手册的任务。我们一开始不知道应用程序是如何设计的,所以我们必须首先尝试弄清楚这一点,然后看看我们是否能做些什么来缓解。
背景
在我之前的文章中,我解释了如果操作系统仍然受支持,或者应用程序使用默认设置,这个问题就可以解决。考虑到我们有一个不工作的应用程序,我们可以安全地假设的一件事是“某处”正在调用一个具有非默认身份验证规范的 win32 COM API,因为我们已经知道更改 DCOM 设置(无论是默认设置还是通过 `DCOMCnfg` 进行组件特定的设置)都没有起作用。最可能的嫌疑是某处有人调用 `CoInitializeSecurity` 时参数错误。建立这一点最简单的方法是使用 SysInternals 的 `strings` 工具在文件上查找,并使用 powershell 自动化此过程以处理整个文件夹。
Get-ChildItem C:\temp\Lib -Filter "*.dll" -Recurse |
foreach {$_.FullName; c:\temp\strings $_.FullName |
where {$_ -match "CoInitializeSecurity"}}
事实上,在这种情况下,确实找到了匹配项。有一个正在使用的组件会显式调用 `CoInitializeSecurity`。
为避免麻烦,我决定隐藏该特定组件的名称。在本文的其余部分,我将将其重命名为 *DodgyDll.dll*。在确定了罪魁祸首之后,我们需要更详细地研究它。我们可以通过几种方式做到这一点。一种方法是使用 reflector。
如果我们查看 `CoInitializeSecurity` 的声明
HRESULT CoInitializeSecurity(
[in, optional] PSECURITY_DESCRIPTOR pSecDesc,
[in] LONG cAuthSvc,
[in, optional] SOLE_AUTHENTICATION_SERVICE *asAuthSvc,
[in, optional] void *pReserved1,
[in] DWORD dwAuthnLevel,
[in] DWORD dwImpLevel,
[in, optional] void *pAuthList,
[in] DWORD dwCapabilities,
[in, optional] void *pReserved3
);
我们看到身份验证级别设置为 1 (`RPC_C_AUTHN_LEVEL_NONE`),模拟级别设置为 2 (`RPC_C_IMP_LEVEL_IDENTIFY`)。根据服务器供应商的文档,我们需要将它们分别设置为 5 (`RPC_C_AUTHN_LEVEL_PKT_INTEGRITY`) 和 3 (`RPC_C_IMP_LEVEL_IMPERSONATE`)。
确定了问题所在,现在我们必须实现它。
拆卸和重组 DLL
如果你安装了 Visual Studio,你的系统上已经有了 `ildasm`。如果你没有,你可以通过安装 Windows SDK 来免费获取。`ildasm` 是一个反汇编工具,可以将 .NET 模块转换回 .NET 中间语言。它还会提取嵌入的资源文件。
你可以在记事本中打开 il 文件,找到 `CoInitializeSecurity` 函数调用。基本上,你会看到在调用函数之前,按照 `CoInitializeSecurity` 期望的顺序,将不同的值加载到堆栈上。堆栈上的第 5 个和第 6 个值是导致问题的原因。由于 IL 相对容易阅读和理解,因此也很容易编辑。
以 `ld` 开头的指令会将某项内容加载到堆栈上。简单地计算加载到堆栈上的次数,就可以轻松识别出导致问题的 1 和 2 这两个值,需要将它们改为 5 和 3。我们只需要编辑这些值。
就是这样。现在,我们必须反转过程,再次创建 DLL。但是,请注意,我们需要在此 DLL 的目标系统上执行此操作。原因是 ilasm 会创建对 .NET 版本的依赖,使用错误版本的 ilasm 会创建与使用它的应用程序不兼容的 DLL。幸运的是,当 .NET Framework 安装在系统上时,ilasm 和其他工具会随之安装。
这将把中间语言文件转换回 DLL。但是,当我们尝试这样做时,会遇到一个障碍。
我们使用的 `ilasm` 版本无法理解该特定行(精确地说,是 `-nan`)。这种情况并不完全出乎意料。在使用来自不同工具链的工具时,可能会发生这种情况。通过将 `ldc.r8 -nan(ind)` 修改为 `ldc.r8 (00 00 00 00 00 00 F8 FF)`,可以轻松解决这个问题。顺便说一句,这是 撰写本文时已知的问题,并且是由 `ildasm` 中的回归引起的。
在我们进行必要的更改之后,命令成功完成!在此基础上,我们替换 DLL,启动应用程序,然后……什么也没发生。或者更确切地说,发生了以下情况:
这当然是有道理的。原始供应商使用了强命名签名,虽然我们可以逆向工程 DLL 并进行一些更改,但重新签名不是一个选项。
移除签名验证
我想重申我一开始说过的话:我们正在处理一个过时的系统,这个系统——希望——已经处于迁移过程中。我们已经处于一种情况,即没有最佳或良好的选择,我们已经在尽最大努力应对最坏的情况。
我之所以再次提到这一点,是因为在断绝了供应商支持的所有希望之后,我们又向前迈进了一步,还禁用了强命名验证并削弱了应用程序安全性。换句话说,我们将告诉系统我们不再关心程序集签名。很明显,在任何情况下,这都不是一个好主意,除非不这样做会造成更大的灾难。
事实证明,这相对简单,可以通过注册表完成。你需要创建以下键:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\StrongName\Verification\*,*]
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\StrongName\Verification\*,*]
然后你创建以下值:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework]
"AllowStrongNameBypass"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework]
"AllowStrongNameBypass"=dword:00000001
可以使用这些注册表键来仅允许具有特定签名的程序集,但在我们的例子中,我们选择了全面方法。
注意:在我们的例子中,仅仅禁用强命名是足够安全的,因为该服务器位于一个隔离的 VLAN 中,该 VLAN 只允许点对点连接,并且只有管理员和相关的服务帐户才能登录到该系统。如果你的设置安全性较低,最好仅针对相关程序集选择性地禁用强命名签名。
我没有找到微软的官方文章,但 这个链接 展示了应该如何操作。
<code>[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\StrongName\Verification\MyAssemblyName,publicKeyToken]</code>
成功
在绕过强命名验证后,我们可以启动应用程序,然后……
成功!我们的应用程序终于可以连接到 DCOM 服务器,业务也可以继续进行。
关注点
在本文中,我展示了如何拆卸和重组 .NET DLL 以进行参数更改。在这种情况下,是为了处理 DCOM 加固,但该原理更具普遍性。
这种方法绝对不是解决你问题的最佳方法,应该被视为“最后的手段”,但了解某些内容并有一个额外的工具放在工具箱中总是有益的。
历史
- 2023 年 11 月 15 日:初版
- 2023 年 12 月 12 日:第二版。添加了选择性强命名禁用