我们知道,Java编译源程序得到的是字节码,VS.NET编译源程序得到的是MSIL(Microsoft中间语言),这种编译方式称为“不完全编译”,特别容易被反编译或实施反向工程。与本机代码不同,不完全编译得到的中间代码完整地保留了变量、过程名称,从而使反编译得到的程序几乎与原始程序完全一样,只缺少原始程序的注释,其余内容差不多可以原封不动地还原出来。
对于商品软件的开发者,高质量的反编译代码带来了很大的风险:算法可能被窃取和改造,程序代码可能被复制和更改。(即使单位内部使用的非商品软件,由于反编译造成的源代码泄漏也带来很大的威胁。例如,用户将很容易看到访问数据库的密码或程序嵌入的SQL命令。同样地,使用外单位托管服务器的网站也面临风险,一旦上载了ASP.NET应用的代码,托管单位的人就可能随意查看和更改程序代码)。
更让人担心的是,现在黑客或好奇的用户能够方便地得到各种反向工程工具。Microsoft本身就免费提供了一个MSIL反汇编器,称为ILDASM;这里有一个源代码开放的.NET反编译工具Anakrino;当然,还有其他许多厂商提供了商品化的反向工程工具。
一、修改变量名称
为防止这类反向工程的威胁,最有效的办法是模糊。(据《美国传统辞典》,“模糊”的意思是“使混乱,使糊涂迷惑,使过于混乱或模糊,使得难于感觉或理解”)。模糊工具运用各种手段达到这一目标,但主要的途径是让变量名字不再具有指示其作用的能力、加密字符串和文字、插入各种欺骗指令使反编译得到的代码不可再编译。
一个即将发布的Visual Studio版本(称为VS.NET 2003,代码名称Everett)将集成一个模糊工具,Microsoft建议用这个工具对.NET中间代码进行最后的处理。这个模糊器是另一个工具Dotfuscator的所谓Lite版。由Preemptive Solutions公司出品的Dotfuscator功能更强大,这家位于美国俄亥俄州东北部克利夫兰的公司最初开发Java代码的模糊技术。Dotfuscator模糊器利用一系列卓越的技术使反向工程徒劳无功,或者至少说使得反向工程很难进行。
Preemptive Solutions公司给它修改中间代码中变量名称的专利技术取了一个叫“超载感应”(Overload induction)的名称,VS.NET 2003带来的Lite版只有这种模糊功能。(模糊器永远不会改动原始的源代码,甚至根本不需要用源代码作为参考。)这种技术充分运用了VS.NET代码的特点:同一个标识符可同时运用于具有不同特征的类和方法;在不同的名称空间中,变量可以有相同的名字而不会冲突。
Dotfuscator充分运用VS.NET这些符号学上的特点,把尽可能多的符号改成字母“A”。据该公司说,某些代码大约有33%的引用可以改成“A”,还有10%可以改成“B”。经过模糊器这一处理,反向工程得到的代码将很难理解。下面来看一个例子。
对未经模糊处理的代码执行反向工程:
private void CalcPayroll(SpecialList employeeGroup) {
while (employeeGroup.HasMore()) {
employee = employeeGroup.GetNext(true);
employee.updateSalary();
DistributeCheck(employee);
}
}
同样的代码,经过模糊处理再执行反向工程:
private void a(a b) {
while (b.a()) {
a = b.a(true);
a.a();
a(a);
}
}
显然,两段代码的处理逻辑相同。但是,要说清楚第二段代码到底在做些什么极其困难,甚至要判断它正在访问哪些方法、哪些变量也很困难。
这种改变变量名称的功能是可配置的,例如,假设你正在构造一个DLL,可以要求不改动API。有趣的是,这一处理过程显然只是简单地把大量变量的名称简缩成单个字符,但获得了非常好的模糊效果。
二、加密字符串
字符串加密对付另一个安全问题,其实这一安全问题在本机代码中也同样存在——从二进制代码提取字符和文字是一件很简单的事情。例如,用UNIX的strings工具处理任何二进制文件,可以迅速得到该二进制文件包含的ASCII文本清单。
在最简单的情形下,这个清单只会泄露版权信息和二进制执行文件引用了哪些库。但是,如果程序要访问数据库,这个清单将包含所有的SQL命令;如果代码模块中嵌入了密码,密码也将不密。
对于中间代码,未加密的字符串还会带来一层额外的风险。黑客们通过分析对特定字符串的引用,可以判断出从哪里开始代码受到密码保护,然后加上补丁使代码绕过密码验证部分。
为了解决字符串明文带来的安全问题,大多数模糊器运用了加密字符串的技术。由于解密操作需要一定的开销,所以运行时访问字符串的性能肯定会有所降低。有趣的是,在这方面本机代码反而不占便宜,因为如果要达到同样的效果,对于本机代码开发者必须手工加密和解密每个字符串,而对于中间代码这些工作却可以由模糊器代劳。
三、隐藏执行流程
控制流程模糊是一种用来误导反编译器的技术,它在原始的代码中插入许多goto指令,虽然程序最终执行的指令序列仍跟原来的一样,但太多的“迂回动作”使得分析程序实际的逻辑流程非常困难。下面来看一个例子。
对没有经过控制流程模糊处理的中间代码实施反向工程:
public int CompareTo(Object o) {
int n = occurrences - ((WordOccurrence)o).occurrences;
if (n == 0) {
n = String.Compare(word, ((WordOccurrence)o).word;
}
return (n);
}
同样的代码,经过控制流程模糊处理后再执行反向工程:
public virtual int a(object A_0) {
int local0;
int local1;
local0 = this.a - (c) A_0.a;
if (local0 != 0)
goto i0;
goto i1;
while (true) {
return local1;
i0: local1 = local0;
}
i1: local0 = System.String.Compare(this.b, (c) A_0.b);
goto i0;
}
可以看到,经过控制流程模糊处理后,代码被插入了一个伪条件检测语句,接着又执行了一个goto指令。在goto的目的地,原来的语句(以经过模糊处理后的形式)被执行,接着又是一个goto语句将控制转到原先的逻辑流程分支。注意while()循环未被执行,它只是起到一种误导的作用。这个代码片断很小,即使没有原始的代码可供比较和参考,识别出程序实际流程的可能性仍存在。然而,对于一个规模较大的过程,如果没有源代码可供参考,那些专门用来搅浑程序正常执行流程的指令将使分析代码的人疲于奔命,最终不得不放弃。
也就是说,这种模糊处理的根本思想是让恢复原始代码变得极其困难,迫使黑客改变主意,也许是换做轻松一点的,例如“自己写代码算了”。
对控制流程进行模糊处理要在二进制文件中插入一些代码,因而增加了一些运行时间开销。如果代码对运行时间的要求非常苛刻,可以只给那些特别重要的部分加上这一层额外的保护。
█ 模糊器和反编译器大展播:
▲模糊器:
LSW DotNet IL Obfuscator
Demeanor for .NET
Salamander .NET Obfuscator
dotfuscator
Aspose.Obfuscator
.NET IL-Obfuscator
Deploy .NET
Salamander .NET Protector
Thinstall
XenoCode
▲ 反编译器:
Salamander .NET Decompiler
Exemplar/Anakrino