配置 Visual Studio 进行混淆






4.96/5 (84投票s)
一篇入门文章,说明如何配置 Visual Studio 进行混淆
目录
- 引言
- 什么会被混淆?
- 程序集如何被混淆?
- 何时项目的程序集会被混淆?
- 哪种类型的程序集会被混淆?
- 何时以及何时不混淆?
- ObfuscatedRelease 解决方案配置
- 混淆工作流概述
- 示例解决方案中的项目
- 强名称密钥文件
- 项目设置
- 解决方案平台
- 生成后事件
- ConfuserEx
- 如何测试您的程序集是否已被混淆?
- 与混淆相关的疑难解答
- 构建示例解决方案的要求
- 历史
引言
这是一篇关于 Visual Studio 中混淆的入门文章。它解释了如何配置 Visual Studio 进行混淆。文章不讨论混淆背后的理论,也不讨论混淆在保护您的知识产权方面的有效性。
什么会被混淆?
我在多个论坛中看到一个常见的提问:什么会被混淆?是源代码还是项目构建时生成的程序集?答案是项目构建时生成的程序集。例如,本文附带的示例解决方案中的一个项目名为 PrivateAssembly
。当此项目被构建时,输出就是一个名为 PrivateAssembly.dll 的程序集。正是这个 PrivateAssembly.dll 程序集会被混淆。
程序集如何被混淆?
程序集使用一个名为混淆器的程序进行混淆。Visual Studio 中包含的混淆器是 Dotfuscator 的社区版。本文提供的示例解决方案不使用 Dotfuscator,而是使用混淆器 ConfuserEx,这是一个开源项目。
程序集可以通过 GUI 程序或命令行程序进行混淆——各有用途。我发现 GUI 程序可用于学习,而命令行程序可用于自动化构建过程。
ConfuserEx 混淆器的开发和支持已于 2016 年 7 月停止。但是,本文中的大部分信息适用于其他包含命令行程序的混淆器。
何时项目的程序集会被混淆?
在示例解决方案中,一个程序集在项目程序集构建完成后立即被混淆。更具体地说,当项目的 生成后事件 执行时,程序集就会被混淆。生成后事件是一个用户提供的脚本,用于执行一系列命令。在当前上下文中,该脚本执行命令来混淆项目的程序集。当需求非常简单时,可以使用 批处理命令和批处理文件 编写脚本,否则我建议编写 PowerShell 脚本。示例解决方案使用 PowerShell 脚本。
哪种类型的程序集会被混淆?
本文提供的示例解决方案演示了如何混淆私有程序集、强名称程序集和延迟签名程序集。本文假设您了解这三种程序集之间的区别。简而言之,强名称程序集使用来自加密强名称密钥文件的私钥令牌进行签名,延迟签名程序集使用来自加密强名称密钥文件的公钥令牌进行签名,而私有程序集则不签名。对于混淆新手,建议阅读文章 “为 .NET 程序集赋予强名称”,因为它描述了混淆强名称程序集与混淆延迟签名程序集之间的区别。其他值得阅读的文章包括 这篇 和 这篇。
何时以及何时不混淆?
默认情况下,Visual Studio 解决方案中的项目配置了两个解决方案配置——Debug 解决方案配置和 Release 解决方案配置。问题是,您会将混淆应用于哪个配置?
我还没有发现任何理由要混淆在 Debug 解决方案配置下构建的程序集。因此,当选择 Debug 解决方案配置时,我的测试(例如,单元测试、集成测试、规范测试)只针对非混淆的程序集运行。
另一方面,在 Release 解决方案配置下,我希望能够构建非混淆的程序集和混淆的程序集。这样做的原因是,如果使用混淆的程序集时 出现问题,我希望能够测试同样的问题是否发生在非混淆的程序集中。换句话说,问题是否与混淆过程有关? 因此,在 Release 配置下,我的测试(例如,单元测试、集成测试、规范测试)可以针对非混淆的程序集和混淆的程序集运行。要实现这一点,意味着我的 Visual Studio 解决方案必须配置为能够选择性地构建非混淆的程序集或混淆的程序集。问题是如何配置 Visual Studio 来做到这一点,即在一个运行时选择性地构建非混淆的程序集,然后在另一个运行时构建混淆的程序集? 答案是创建一个新的解决方案配置,我称之为 ObfuscatedRelease
。
ObfuscatedRelease 解决方案配置
当在 Visual Studio 中创建新项目时,Debug 和 Release 解决方案配置会自动添加到项目中。出于混淆目的,需要的是一个您必须添加到 Visual Studio 解决方案的新解决方案配置。在示例解决方案中,此新解决方案配置的名称是 ObfuscatedRelease
(参见下图)。Debug 和 Release 解决方案配置用于构建非混淆程序集,而 ObfuscatedRelease
解决方案配置用于构建混淆程序集。
关于何时何地混淆程序集的决定实际上发生在项目的生成后事件中。例如,在示例解决方案中,只有当生成后事件确定已选择 ObfuscatedRelease
解决方案配置时,它才会混淆程序集(参见下一个列表)。如果选择了 Debug 或 Release 解决方案配置,则项目的程序集不会被混淆。
列表 1
if /I $(ConfigurationName) neq ObfuscatedRelease goto lDoNotObfuscate
rem commands to obfuscate the assembly go here
:lDoNotObfuscate
因此,能够选择性地构建混淆和非混淆程序集的关键在于向您的 Visual Studio 解决方案添加新的解决方案配置,然后让项目的生成后事件仅在选择此新解决方案配置时才混淆项目的程序集。
有关如何向 Visual Studio 解决方案添加新解决方案配置的说明可以在 此处 找到。编辑新解决方案配置时,请确保项目的Configuration 设置与Active solution configuration 设置匹配。例如,下图说明了 PrivateAssembly
项目的配置已错误地设置为 Release
而不是 ObfuscatedRelease
。这意味着 PrivateAssembly
项目的程序集将永远不会被混淆。
下图说明了 PrivateAssembly
项目的配置设置已正确设置为 ObfuscatedRelease
。
混淆工作流概述
接下来的几张图说明了在示例解决方案中混淆程序集的基本工作流。每张图都显示了当解决方案配置设置为 ObfuscatedRelease
且解决方案平台设置为 AnyCPU
时,PrivateAssembly
示例项目的生成后目录结构。不同示例项目的混淆工作流类似,但请注意它们并不完全相同。例如,StrongNamedAssembly
项目的工作流将其混淆的程序集安装在全局程序集缓存 (GAC) 中,而 PrivateAssembly
和 DelaySignedAssembly
项目则没有此步骤。
步骤 1
Visual Studio 构建项目 PrivateAssembly
,并将非混淆的 PrivateAssembly.dll 程序集存储在 ObfuscatedRelease 目录中(参见下图)。
第二步
Visual Studio 运行 PrivateAssembly
项目的生成后事件。这会在 Confused 目录中创建 PrivateAssembly.dll 程序集的混淆副本(参见下图)。现在有两份 PrivateAssembly.dll 程序集的副本,一份非混淆副本在 ObfuscatedRelease 目录中,一份混淆副本在 Confused 目录中。
步骤 3
生成后事件将 ObfuscatedRelease 目录中的非混淆 PrivateAssembly.dll 程序集复制到 NonObfuscatedAssemblyBackup 目录(参见下图)。现在有三份 PrivateAssembly.dll 程序集的副本,一份非混淆副本在 ObfuscatedRelease 目录中,一份非混淆副本在 NonObfuscatedAssemblyBackup 目录中,一份混淆副本在 Confused 目录中。
步骤 4
生成后事件将 Confused 目录中的混淆 PrivateAssembly.dll 程序集复制到 ObfuscatedRelease 目录(参见下图)。现在有两份混淆的 PrivateAssembly.dll 程序集的副本,一份在 ObfuscatedRelease 目录中,一份在 Confused 目录中。现在只有一份非混淆的 PrivateAssembly.dll 程序集的副本,位于 NonObfuscatedAssemblyBackup 目录中。
既然现在有三份 PrivateAssembly.dll 的副本,那么问题是,其他项目引用的是哪一份副本? 答案是 ObfuscatedRelease 目录中的混淆版本 PrivateAssembly.dll。Visual Studio 会自动引用此副本,因此您无需进行任何额外配置。Confused 和 NonObfuscatedAssemblyBackup 目录中的 PrivateAssembly.dll 副本不再使用,可以删除。
示例解决方案中的项目
示例解决方案包含三个 C# 类库,名为 PrivateAssembly
、DelaySignedAssembly
和 StrongNamedAssembly
,以及一个名为 MyAppCS
的 C# 控制台应用程序。这些项目的名称表明了它们构建的程序集类型。StrongNamedAssembly
项目构建强名称程序集,DelaySignedAssembly
项目构建延迟签名程序集,而 PrivateAssembly
项目构建私有程序集。下图显示了这些项目之间的依赖关系。这些依赖关系没有特殊要求,它们仅反映了我选择如何实现这些项目的方式。当选择 ObfuscatedRelease
解决方案配置时,这些四个项目的程序集将被混淆。当选择 Debug 和 Release 解决方案配置时,这些程序集都不会被混淆。
我正在开发的项目使用 C++/CLI 调用 C# 程序集。因此,示例解决方案包含一个名为 MyAppCPP
的 C++/CLI 控制台应用程序,用于演示 C++/CLI 代码如何调用混淆的程序集。MyAppCPP
项目具有与上图所示的 C# 控制台应用程序相同的依赖关系。当选择 ObfuscatedRelease 解决方案配置时,C# 程序集将被混淆,但 MyAppCPP
程序集不会被混淆。当选择 Debug 和 Release 解决方案配置时,所有程序集都不会被混淆。
为 C# 类库提供了单元测试,以演示也可以针对混淆的程序集运行单元测试。当选择 ObfuscatedRelease
解决方案配置时,单元测试将针对混淆的程序集运行。当选择 Debug 和 Release 解决方案配置时,单元测试将针对非混淆的程序集运行。
要构建和运行示例解决方案,必须以管理员权限启动 Visual Studio。这是因为 StrongNamedAssembly
项目将程序集安装到 GAC 中,而执行此操作需要管理员权限。示例解决方案中包含一个批处理文件,用于从 GAC 中卸载 StrongNamedAssembly
程序集。在您完成使用示例解决方案后,应运行此批处理文件。此批处理文件的名称是 UninstallAssembliesFromGAC.bat,也必须以管理员权限运行。此批处理文件的文件路径是
Obfuscation\BatchFiles\UninstallAssembliesFromGAC.bat
强名称密钥文件
构建强名称程序集和延迟签名程序型的项目会使用存储在称为强名称密钥文件中的加密密钥。文章 “为 .NET 程序集赋予强名称” 描述了如何生成强名称密钥文件。示例解决方案包含两个强名称密钥文件,一个包含公钥和私钥令牌(即,PublicPrivateKeys.snk),另一个只包含公钥令牌(即,PublicKey.snk)。这两个密钥文件的文件路径是
Obfuscation\StrongNameKeyFiles\PublicPrivateKeys.snk
Obfuscation\StrongNameKeyFiles\PublicKey.snk
提供了一个示例批处理文件,说明了如何创建强名称密钥文件。此批处理文件的文件路径是
Obfuscation\StrongNameKeyFiles\createStrongNameKeyFiles.bat
如前所述,强名称程序集使用私钥令牌签名,延迟签名程序集使用公钥令牌签名,而私有程序集根本不签名。
项目设置
在示例解决方案中,有三个特别的设置需要您注意
- 项目的签名设置,
- 引用项目时Copy Local(本地复制)的设置,以及
- 示例 PowerShell 脚本中的 Windows SDK 目录路径
项目签名设置
项目的属性设置中的Signing(签名)选项卡用于指定程序集签名的设置。要显示项目的签名设置,在 Visual Studio 的主菜单中,点击[Project] [ProjectName Properties],然后点击Signing(签名)选项卡。下图说明了 StrongNamedAssembly
示例项目的签名设置。此图显示该程序集将使用私钥文件 PublicPrivateKeys.snk 进行签名,并且未选中Delay sign only(仅延迟签名)选项。这意味着该项目构建了一个强名称程序集。
Copy Local(本地复制)设置
第二个设置Copy Local(本地复制)是在一个类库被另一个项目引用时定义的。此设置可以设置为 True
或 False
。例如,下图显示类库 DelaySignedAssembly
被类库 PrivateAssembly
引用。 DelaySignedAssembly
程序集未安装在 GAC 中,必须复制到 PrivateAssembly
项目的输出目录。因此,被引用的 DelaySignedAssembly
项目的Copy Local(本地复制)设置为True。
下表显示了三种类型程序集的签名和Copy Local(本地复制)设置。
表 1 - 私有程序集的设置
设置 | 值 |
Sign the assembly | 未勾选 |
Choose a strong name key file | 未定义 |
Delay sign only | 未勾选 |
Copy Local | True |
表 2 - 强名称程序集的设置
设置 | 值 |
Sign the assembly | checked |
Choose a strong name key file | 选择一个包含私钥的强名称密钥文件 |
Delay sign only | 未勾选 |
Copy Local | 假 |
表 3 - 延迟签名程序集的设置
设置 | 值 |
Sign the assembly | checked |
Choose a strong name key file | 选择一个仅包含公钥的强名称密钥文件 |
Delay sign only | checked |
Copy Local | True |
Windows SDK 目录路径
要定义的第三个也是最后一个设置是示例 PowerShell 模块 Utility.psm1 中的 Windows SDK 目录路径。此目录路径指定了 GAC 工具程序 gacutil.exe 和强名称工具程序 sn.exe 的位置。Utility.psm1 模块的文件路径是
Obfuscation\PowerShellScripts\Utility\Utility.psm1
此模块定义了变量 $_windowsSdkDirectory(参见第 270 行)如下
$_windowsSdkDirectory = "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6 Tools\"
只有当 VS2015 的 Windows SDK 安装在您计算机上的不同目录时,您才需要更改此目录路径。
解决方案平台
示例解决方案配置了四个平台:Any CPU、Win32、x86 和 x64。C# 控制台应用程序和单元测试应在 Any CPU、x86 或 x64 平台配置下运行。C++ 控制台应用程序应在 Win32 或 x64 平台配置下运行。
生成后事件
示例项目在生成后事件中执行的操作序列因要混淆的程序集类型(私有、强名称或延迟签名)而异。以下各节将回顾这三种类型程序集的生成后事件以及它们调用的 PowerShell 脚本。
PowerShell 脚本
当我刚开始接触混淆时,我使用批处理命令和批处理文件编写了生成后事件。但是,正如在 ConfuserEx 项目文件模板 部分所述,我随后决定将模板文件集成到生成后事件中。问题在于,批处理命令和批处理文件不适合执行使用此模板文件的操作——可以做到,但实现过程awkward、易出错且调试极其困难。另一方面,使用 PowerShell 可以轻松完成这项工作。因此,您会发现示例项目中所有的生成后事件都调用 PowerShell 脚本。下图显示了包含在示例解决方案中的 PowerShell 脚本和模块。
下表描述了这些 PowerShell 脚本和模块。
表 4 - 示例解决方案中的 PowerShell 脚本/模块
GACAssembly 脚本 | - 用于在 GAC 中安装和卸载项目的程序集 |
ObfuscateAssembly 脚本 | - 用于混淆项目的程序集 |
SetAssemblyStrongNameVerificationState 脚本 | - 用于禁用延迟签名程序集的强名称验证 |
SignAssembly 脚本 | - 用于重新签名混淆的强名称程序集 |
Utility 模块 | - 一组由上述 PowerShell 脚本使用的 PowerShell 函数 |
示例解决方案中包含的 PowerShell 脚本执行的错误检查最少,以便读者更容易理解它们。
私有程序集的生成后事件
下一个列表显示了 PrivateAssembly
示例项目中的生成后事件。
列表 2
set powerShellFilePath=%systemroot%\sysnative\windowspowershell\v1.0\powershell.exe
if /I $(ConfigurationName) neq ObfuscatedRelease goto lDoNotObfuscate
rem obfuscate the assembly
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\ObfuscateAssembly\ObfuscateAssembly.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%" "$(SolutionPath)" "$(TargetPath)"
if errorlevel 1 exit 1
:lDoNotObfuscate
IF
语句检查 Visual Studio 中选择了哪个解决方案配置。如果解决方案配置不是 ObfuscatedRelease
,则不混淆项目程序集,否则混淆项目程序集。通过调用 PowerShell 脚本 ObfuscateAssembly.ps1 来混淆程序集。
混淆工作流中的主要步骤是
- 使用 ConfuserEx 模板文件创建 ConfuserEx 项目文件。
- 混淆项目程序集,并将混淆后的程序集存储在 Confused 目录中。
- 将非混淆程序集复制到 NonObfuscatedAssemblyBackup 目录。
- 将混淆后的程序集复制到项目的输出目录。
如果您打开 ObfuscatePrivateAssembly.ps1,您会看到它调用了四个函数(参见第 160 至 163 行),这些函数执行上述四个步骤(参见下一个列表)。
列表 3
CreateConfuserExProjectFileFromTemplateFile
ObfuscateProjectAssembly
BackupProjectOutputDirectory
CopyObfuscatedAssemblyToProjectOutputDirectory
强名称程序集的生成后事件
以下列表显示了 StrongNamedAssembly 示例项目中的生成后事件。
列表 4
set powerShellFilePath=%systemroot%\sysnative\windowspowershell\v1.0\powershell.exe
if /I $(ConfigurationName) neq ObfuscatedRelease goto lDoNotObfuscate
rem obfuscate the assembly
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\ObfuscateAssembly\ObfuscateAssembly.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%"
"$(SolutionPath)" "$(TargetPath)"
if errorlevel 1 exit 1
rem re-sign the obfuscated assembly
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\SignAssembly\SignAssembly.ps1
set keyFilePath=$(ProjectDir)PublicPrivateKeys.snk
"%powerShellFilePath%" -File "%powerShellScriptPath%"
"$(TargetPath)" "%keyFilePath%" "$(PlatformName)"
if errorlevel 1 exit 1
:lDoNotObfuscate
rem remove previously installed versions of the assembly in the GAC
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\GACAssembly\GACAssembly.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%"
"$(TargetName)" "$(PlatformName)"
rem now install current assembly in GAC
"%powerShellFilePath%" -File "%powerShellScriptPath%"
"$(TargetPath)" "$(PlatformName)" -InstallAssemblyInGAC
if errorlevel 1 exit 1
首先,此脚本仅当解决方案配置设置为 ObfuscatedRelease
时才混淆项目程序集。其次,如果程序集被混淆,它将使用强名称密钥文件 PublicPrivateKeys.snk 中的私钥令牌进行重新签名。第三,如果此项目的程序集以前安装在 GAC 中,则会从 GAC 中卸载以前的版本。最后,将当前版本的项目程序集安装到 GAC 中。
延迟签名程序集的生成后事件
以下列表显示了 DelaySignedAssembly 示例项目中的生成后事件。
列表 5
set powerShellFilePath=%systemroot%\sysnative\windowspowershell\v1.0\powershell.exe
rem Disable strong name verification of the project’s delay signed assembly.
rem Note: make certain to enable strong name verification of the project assembly once this assembly is ready for deployment.
set powerShellScriptPath=$(SolutionDir)
PowerShellScripts\SetAssemblyStrongNameVerificationState\SetAssemblyStrongNameVerificationState.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%"
"$(TargetPath)" "$(PlatformName)" -DisableStrongNameVerification
if errorlevel 1 exit 1
if /I $(ConfigurationName) neq ObfuscatedRelease goto lDoNotObfuscate
rem obfuscate the assembly
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\ObfuscateAssembly\ObfuscateAssembly.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%" "$(SolutionPath)" "$(TargetPath)"
if errorlevel 1 exit 1
:lDoNotObfuscate
首先,此生成后事件调用 SetAssemblyStrongNameVerificationState.ps1 来指示运行时禁用此延迟签名程序集的强名称验证。其次,如果解决方案配置设置为 ObfuscatedRelease
,则混淆项目程序集。请注意,示例项目不会将混淆的、延迟签名的程序集安装到 GAC 中。我尝试这样做时 出现了一个错误,我无法解决。我没有进一步研究此事,因为我在自己的工作中不使用延迟签名的程序集。
一旦延迟签名程序集经过全面测试,它将被交给拥有私钥令牌访问权限的人,使用私钥令牌签名,然后添加到部署项目中,以便在部署时安装到 GAC 中。
ConfuserEx
示例解决方案使用的混淆器名为 ConfuserEx
。大多数读者对 ConfuserEx
一无所知,所以我认为有必要介绍一下它在示例解决方案中的使用。
ConfuserEx 项目文件
在 Visual Studio 中,每个项目的各种设置都存储在扩展名为 CSPROJ(C#)和 VCXPROJ(C++)的文件中。类似地,ConfuserEx
将设置存储在扩展名为 CRPROJ 的项目文件中。ConfuserEx
项目文件指定了要混淆的程序集、如何混淆程序集以及混淆后的程序集存储在哪里。
创建 ConfuserEx
项目文件的最简单方法是使用 ConfuserEx
GUI 程序。ConfuserEx
二进制文件不包含在示例解决方案中,但会在首次构建示例解决方案时自动安装。构建完示例解决方案后,GUI 的文件路径将是
Obfuscation\packages\ConfuserEx.Final.1.0.0\tools\ConfuserEx.exe
ConfuserEx 项目文件作为参数传递给 ConfuserEx
命令行程序。命令行程序的文件路径是
Obfuscation\packages\ConfuserEx.Final.1.0.0\tools\Confuser.CLI.exe
ConfuserEx 项目文件可以指定一个或多个要混淆的程序集。示例解决方案采用的方法是每个 ConfuserEx 项目文件指定一个要混淆的程序集。这意味着示例解决方案需要处理许多不同的 ConfuserEx 项目文件。下一节将对此进行详细说明。
ConfuserEx 项目文件模板
本节解释了使用 ConfuserEx 项目文件模板的动机。此模板的使用特定于本文提供的示例解决方案。您在 ConfuserEx
GitHub 站点上找不到对此模板文件的任何引用。
示例解决方案配置了三种不同的解决方案配置(即,Debug、Release、ObfuscatedRelease)和四种不同的解决方案平台(即,any CPU、Win32、x86、x64)。当构建示例解决方案中的项目时,项目输出目录的部分由所选解决方案配置和解决方案平台组合确定。更具体地说,每个 C# 项目的输出可以存储在 12 个(即,3 个配置 x 4 个平台)不同目录中的一个中,具体取决于所选的解决方案配置和解决方案平台。例如,下一个列表显示了 PrivateAssembly
示例项目的部分输出目录。
列表 6
Obfuscation\Projects\PrivateAssembly\bin\Debug
Obfuscation\Projects\PrivateAssembly\bin\Release
Obfuscation\Projects\PrivateAssembly\bin\ObfuscatedRelease
Obfuscation\Projects\PrivateAssembly\bin\x86\Debug
Obfuscation\Projects\PrivateAssembly\bin\x86\Release
Obfuscation\Projects\PrivateAssembly\bin\x86\ObfuscatedRelease
Obfuscation\Projects\PrivateAssembly\bin\x64\Debug
Obfuscation\Projects\PrivateAssembly\bin\x64\Release
Obfuscation\Projects\PrivateAssembly\bin\x64\ObfuscatedRelease
ConfuserEx
项目需要知道这 12 个目录中的哪个目录包含要混淆的程序集。实现此目的的一种方法是使用 GUI 程序创建 12 个不同的 ConfuserEx 项目文件,其中每个 ConfuserEx 项目文件标识 12 个不同的项目输出目录之一。事实上,这正是我开始使用 ConfuserEx
时所做的。但这很快就会变成一项繁琐且易出错的任务。我需要一种更简单的方法来创建许多 ConfuserEx 项目文件。因此,我比较了这 12 个不同的 ConfuserEx 项目文件的内容,发现除了一些特定设置不同之外,所有文件内容都相同。例如,下一个列表显示了我最初使用的 ConfuserEx 项目文件集的内容。“XXX”标识的文本在项目文件之间有所不同,但所有其他内容都相同。
列表 7
<project outputDir="XXX" baseDir="XXX" xmlns="http://confuser.codeplex.com">
<rule pattern="true" preset="normal" inherit="false" />
<module path="XXX" />
</project>
很明显,与其处理 12 个不同的 ConfuserEx 项目文件(然后乘以 C# 项目的数量),不如创建一个模板文件,其中包含所有 ConfuserEx 项目文件共有的内容。然后,在项目的生成后事件中,我的 PowerShell 脚本可以从模板文件生成 ConfuserEx 项目文件,并提供正在构建的项目特有的设置。
所有示例 C# 项目共享的单个模板文件的文件路径是
Obfuscation\PowerShellScripts\ObfuscateAssembly\ConfuserEx.Template.crproj
当生成后事件运行 PowerShell 脚本 ObfuscateAssembly.ps1 时,将从 ConfuserEx.Template.crproj 模板文件创建 ConfuserEx 项目文件。ConfuserEx 项目文件保存在 C# 项目的输出目录中(即,与 C# 项目程序集构建在同一目录中)。
每个 C# 项目都应该创建自己的 ConfuserEx 项目文件,并且不应在 C# 项目之间共享 ConfuserEx 项目文件。此规则有助于避免潜在的资源冲突,如果多个 C# 项目共享同一个 ConfuserEx 项目文件,则可能发生这种情况。通过共享,我指的是每个 C# 项目使用相同的文件路径(即,相同的目录和相同的文件名)来创建 ConfuserEx 项目文件。现在,只要多个 C# 项目不同时尝试创建此 ConfuserEx 项目文件,就可以使用共享的通用 ConfuserEx 项目文件。但是,如果多个 C# 项目同时创建此 ConfuserEx 项目文件,那么一个 C# 项目就会覆盖另一个 C# 项目所需的 ConfuserEx 项目文件的内容。Visual Studio 可以并行构建项目,因此这种资源冲突几乎是不可避免的。通过让每个 C# 项目创建自己的 ConfuserEx 项目文件,可以避免这种资源冲突。
细心的读者会注意到,示例解决方案可以使用单个共享的 ConfuserEx 项目文件,并且上一段中描述的资源冲突不会发生。这是因为示例项目中的依赖关系迫使 Visual Studio 按顺序或一次构建项目。因此,在任何时候,单个共享的 ConfuserEx 项目文件只会由一个示例 C# 项目写入。但示例项目并未这样做。为了清楚起见,示例项目不共享 ConfuserEx 项目文件。相反,每个示例项目都创建自己的 ConfuserEx 项目文件。
使用单个共享的模板文件来创建 ConfuserEx 项目文件。此模板文件是只读的,因此可以与所有 C# 示例项目共享,而无需担心资源冲突。使用单个模板文件假定所有程序集都应用相同的混淆设置。如果这对您的需求不成立,您将需要提出另一种方法来创建 ConfuserEx 项目文件。
如何测试您的程序集是否已被混淆?
现在您已经配置了 Visual Studio 进行混淆,您如何确认您的程序集正在被混淆? 首先,当混淆器在生成后事件中执行时,它应该会在 Visual Studio 输出窗口中记录信息。因此,如果您在 Visual Studio 输出窗口中看不到此信息,则混淆器未执行,程序集也未被混淆。其次,您可以使用 ILDASM.EXE 程序查看程序集的易读信息。如果 ILDASM 显示此类信息,则程序集未被混淆。另一方面,如果 ILDASM 无法显示此类信息,则程序集已被混淆。
Visual Studio 输出窗口中的混淆信息
当混淆器在项目的生成后事件中执行时,它应该会将信息记录到 Visual Studio 输出窗口。例如,下一个列表说明了当 ConfuserEx
开始混淆 PrivateAssembly.dll 程序集时,在 Visual Studio 输出窗口中记录的信息的前几行。
列表 8
2>------ Build started: Project: PrivateAssembly, Configuration: ObfuscatedRelease x86 ------
2> PrivateAssembly -> F:\Visual Studio 2015\Projects\Obfuscation\Projects\PrivateAssembly\bin\x86\ObfuscatedRelease\PrivateAssembly.dll
2> [INFO] ConfuserEx v1.0.0 Copyright (C) Ki 2014
2> [INFO] Running on Microsoft Windows NT 6.1.7601 Service Pack 1,
.NET Framework v4.0.30319.42000, 64 bits
2> [DEBUG] Discovering plugins...
2> [INFO] Discovered 10 protections, 1 packers.
2> [DEBUG] Resolving component dependency...
提醒您,混淆仅在解决方案配置设置为 ObfuscatedRelease
时执行。因此,将解决方案配置设置为 Debug 或 Release 意味着上面列表中所示的混淆器信息不会出现在 Visual Studio 输出窗口中。
使用 ILDASM.EXE 反汇编程序集
ILDASM.exe 是随 Visual Studio 一起安装的程序。此程序用于反编译(即,逆向工程)程序集,并以您可以读取的格式显示有关程序集的信息。ILDASM 可用于快速检查您的程序集是否已被混淆。总的来说,如果 ILDASM 能够显示有关您程序集的信息,则您的程序集未被混淆。反之,如果 ILDASM 无法显示有关您程序集的信息,则您的程序集已被混淆。
这篇 旧博客文章 描述了您可以在计算机上找到 ILDASM 程序的位置,而 本旧教程 说明了它显示的信息。
下图说明了 ILDASM 为非混淆的 PrivateAssembly.dll 程序集显示的信息。
下图说明了当 ILDASM 尝试读取混淆的 PrivateAssembly.dll 程序集时会发生什么。ILDASM 无法显示混淆程序集的信息,而是显示一个或多个错误消息,例如下图所示。
与混淆相关的疑难解答
您应该会遇到与混淆相关的问题,我解决这些问题的一般方法如下:
- 步骤 1:将 Visual Studio 设置为 Debug 解决方案配置(即,完全禁用混淆),并测试问题是否可以重现。如果问题可以重现,则它与混淆无关。另一方面,如果禁用混淆后无法重现问题,则这是一个表明问题与混淆有关的良好迹象。在这种情况下,继续执行步骤 2。
- 步骤 2:将 Visual Studio 设置回混淆解决方案配置(例如,在示例解决方案中设置为
ObfuscatedRelease
)。接下来,禁用混淆器项目文件中的所有混淆设置,并尝试重现问题。如果所有混淆设置都已真正禁用,则您不应该能够重现问题。在这种情况下,继续执行下一步。 - 步骤 3:启用第一个混淆设置,并尝试重现问题。如果无法重现问题,则继续执行下一步。
- 步骤 4:启用第二个混淆设置,并尝试重现问题。如果无法重现问题,则继续执行下一步。
- 步骤 5:继续逐个启用混淆设置,直到您能够重现问题。当您最终能够重现问题时,最后一个启用的设置就是导致问题的设置。
下一节将举例说明如何使用上述故障排除步骤来解决特定的编译器错误。
调试与混淆相关的编译器错误
当我第一次尝试在 ObfuscatedRelease 解决方案配置下构建示例 C++/CLI 控制台应用程序时,我收到一个晦涩的致命错误 C1001(参见下一个列表)。
列表 9
3>MyAppCPP.cpp(38): fatal error C1001: An internal error has occurred in the compiler.
3> (compiler file ’f:\dd\vctools\compiler\cxxfe\sl\p1\c\cpimport.cpp’, line 17137)
3> To work around this problem, try simplifying or changing the program
near the locations listed above.
3> Please choose the Technical Support command on the Visual C++
3> Help menu, or open the Technical Support help file for more information
该问题在 Debug 和 Release 解决方案配置下均未发生。该问题仅在 ObfuscatedRelease
解决方案配置下构建时发生。这表明问题与混淆过程有关。
接下来,我使用 ConfuserEx
GUI 程序来了解如何禁用 ConfuserEx 项目文件中的特定设置。下一个列表展示了一个示例 ConfuserEx 模板文件,其中所有混淆设置都已禁用。通过向设置添加 action="remove"
属性来禁用 ConfuserEx
设置(更准确地说是规则保护)。
列表 10
<project outputDir="XXX " baseDir="XXX" xmlns="http://confuser.codeplex.com">
<rule pattern="true" preset="normal" inherit="false">
<protection id="anti ildasm" action="remove"/>
<protection id="anti tamper" action="remove"/>
<protection id="constants" action="remove"/>
<protection id="ctrl flow" action="remove"/>
<protection id="anti dump" action="remove"/>
<protection id="anti debug" action="remove"/>
<protection id="invalid metadata" action="remove"/>
<protection id="ref proxy" action="remove"/>
<protection id="resources" action="remove"/>
<protection id="rename" action="remove"/>
</rule>
<module path="XXX" />
</project>
当我禁用所有混淆设置时,我无法重现 C1001 致命错误。因此,我通过将 action
属性的值从 remove
更改为 add
来启用第一个设置“anti ildasm
”(参见下一个列表)。我仍然无法重现编译器错误。
列表 11
<project outputDir="XXX " baseDir="XXX" xmlns="http://confuser.codeplex.com">
<rule pattern="true" preset="normal" inherit="false">
<protection id="anti ildasm" action="add" />
<protection id="anti tamper" action="remove" />
<protection id="constants" action="remove" />
<protection id="ctrl flow" action="remove" />
<protection id="anti dump" action="remove" />
<protection id="anti debug" action="remove" />
<protection id="invalid metadata" action="remove" />
<protection id="ref proxy" action="remove" />
<protection id="resources" action="remove" />
<protection id="rename" action="remove" />
</rule>
<module path="XXX" />
</project>
我继续逐个启用其他设置,直到启用 ref proxy
设置重现了 C1001 错误。因此,如果您查看示例解决方案中的 ConfuserEx 模板文件,您会发现 ref proxy
规则保护已被禁用。您还会发现 ConfuserEx 模板文件中有其他几个设置已被禁用,这是成功构建所有示例项目所必需的。我使用了反复试验的方法来确定哪些 ConfuserEx
设置可以启用,哪些设置必须禁用。如果您使用的是商业混淆器,那么更好的方法可能是联系客户支持,以确定是否有其他解决方案,而不是仅仅禁用混淆设置。
Reflection(反射)
当您开始阅读关于混淆的内容时,您会发现论坛帖子说反射和混淆不能很好地协同工作。示例控制台应用程序 Reflection 演示了混淆您的程序集时可能发生的一些问题。此控制台应用程序使用反射来获取有关程序集 PrivateAssembly.dll 中存在的类型、类和方法的有关信息。下一个列表显示了为非混淆程序集生成的程序输出。输出包含 IMyClass
接口、MyClass
类及其方法名(如 ConvertStringToUpperCase
)的名称。
列表 12
============================================================
Assembly name: PrivateAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken
=null
type name: PrivateAssembly.IMyClass
method name: ConvertStringToUpperCase
type name: PrivateAssembly.MyClass
method name: get_MyString
method name: set_MyString
method name: get_MyInt
method name: set_MyInt
method name: get_MyDouble
method name: set_MyDouble
method name: ConvertStringToUpperCase
method name: ToString
method name: Equals
method name: GetHashCode
method name: GetType
下一个列表显示了为混淆程序集生成的输出。输出除了几个方法名之外别无他物,问题就在于此。具体来说,如果我编写 Reflection 控制台应用程序来搜索名称为‘IMyClass
’和‘MyClass
’的类型,那么该应用程序在尝试读取混淆程序集时就会失败。这是因为具有这些字面名称的类型根本不存在于混淆程序集中。要吸取的教训是,混淆可能会在您的反射应用程序中引入 bug。我对两者都没有太多经验,也没有什么建议可提供,只能说在使用反射和混淆时需要谨慎。
列表 13
Assembly name: PrivateAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken
=null
type name: ????????????????????????????????????????+????????????????????????????????????????
method name: Equals
method name: GetHashCode
method name: ToString
method name: GetType
type name: ????????????????????????????????????????+???????????????????????????????????????
method name: Equals
method name: GetHashCode
method name: ToString
method name: GetType
调试延迟签名程序集
我尝试将混淆的 DelaySignedAssembly.dll 程序集安装到 GAC 中时,收到了错误消息:“添加程序集到缓存失败:强名称签名无法验证。程序集是否是延迟签名的?” 我在自己的工作中不使用延迟签名的程序集。因此,在短暂但失败的故障排除后,我没有再花时间尝试解决此错误。
混淆的、延迟签名的程序集仍然可以通过将其视为私有程序集来测试和调试。具体来说,请确保将混淆的、延迟签名的程序集复制到应用程序所在的目录。因此,当延迟签名程序集被另一个项目引用时,请确认其Copy Local(本地复制)设置为True。例如,当构建示例控制台应用程序 MyAppCS
时,DelaySignedAssembly.dll 程序集项目将被复制到 MyAppCS
项目的输出目录。就是这么简单。
构建示例解决方案的要求
- Visual Studio 2015
必须以管理员权限运行/启动 Visual Studio。这是因为StrongNamedAssembly
项目将项目的程序集安装到 GAC 中,而执行此操作需要管理员权限。
如果您第一次尝试构建示例解决方案时收到以下错误消息,请阅读 这篇文章GACAssembly.ps1 cannot be loaded. The file ...\Obfuscation\PowerShellScripts\GACAssembly\GACAssembly.ps1 is not digitally signed. You cannot run this script on the current system.
- ConfuserEx
ConfuserEx
是一个开源混淆器。ConfuserEx
将在您第一次构建示例解决方案时自动安装。如前所述,ConfuserEx
的开发和支持已于 2016 年 7 月停止。 - NUnit
示例解决方案包含 NUnit 单元测试。NUnit 将在您第一次构建示例解决方案时自动安装。 - NUnit3TestAdapter
示例解决方案包含 NUnit3TestAdapter v3.4.1 测试运行器。NUnit3TestAdapter 将在您第一次构建示例解决方案时自动安装。必须先构建单元测试示例项目,然后 NUnit3TestAdapter 才能发现并列出示例单元测试。有关如何运行单元测试的说明,请阅读本文档。您还可以通过使用关键词“Visual Studio Test Explorer”进行 Google 搜索或查找使用相同关键词的 YouTube 视频来找到其他说明。
如果您升级到 NUnit3TestAdapter v3.5.0 并遇到 此处描述的问题,那么我使用的解决方法是降级回 v3.4.1。
- PowerShell V4
示例 PowerShell 脚本是在 PowerShell V4 下编写和测试的。通过安装 Windows Management Framework 4.0 来安装 PowerShell V4。请确保您的计算机上安装了 PowerShell V4 或更高版本。 - PowerShell Tools for VS2015
我建议您安装 PowerShell Tools for Visual Studio,因为它允许您在 Visual Studio 本身中编写和调试 PowerShell 脚本。或者,您可以使用任何 PowerShell 编辑器(例如, PowerShell ISE、 PowerShell Plus Professional 和 PowerGUI)来编写和调试您的 PowerShell 脚本。 - Internet 连接
示例解决方案不包含ConfuserEx
、NUnit
和NUnit3TestAdapter
的二进制文件。这些二进制文件将在您第一次构建示例解决方案时自动安装(这是 NuGet 的一项功能),因此您需要 Internet 连接。所以,如果您没有 Internet 连接,这些二进制文件将不会被安装,您也无法成功构建示例解决方案。
历史
- 2015 年 10 月
- 发布了原始文章,演示了如何使用 ConfuserEx v0.5.0 混淆私有程序集。
- 2015 年 11 月
- 更新了文章,以演示如何混淆私有、强名称和延迟签名的程序集。
- 2016 年 1 月
- 更新了
StrongNamedAssembly
项目中的生成后事件,以纠正编译/运行时 GAC 相关错误 - 将示例解决方案从 VS2013 更新到 VS2015
- 更新了
- 2016 年 10 月
- 将示例解决方案更新为使用 ConfuserEx v1.0.0 和 NUnit v3.5.0
- 添加了测试运行器 NUnit3TestAdapter v3.4.1。