现在我们使用ILAsm重新编译修改后的il代码,验证确认可以正常运行。如图9-17所示。
图9-17 重新编译修改过的IL
如图9-17,我重新编译修改过的IL文件,并在命令行下运行生成的exe文件,程序正常运行。但是这样的跳转对保护程序有什么意义吗?我们带着这个疑问使用Reflector打开生成的exe文件。当我们尝试将Main方法转成C#代码时,Reflector抛出了异常。如图9-18所示。
图9-18 Reflector打开代码段易位的程序集报错
图9-18中,我使用Reflector报的竟然是为将对象引用设置到对象实例的错误,看了是引发了Reflector的内部错误。
结果是这样的,但是原因呢,为什么IL运行运行的代码,Reflector却在反编译的时候出错呢?实际上这和我们设置的跳转点有关系。CLI规定程序走入任何分支时,要保持堆栈为空,如果我们在堆栈不为空的时候跳转,反编译器依照CLI进行代码反编译时就会出错。如果我们的跳转点设置在堆栈为空的地方,那么反编译是不会出错的。
q 连续跳转
基于上面提到的代码易位,很有效的达到了阻止反编译的目的,但是,在自动化程序中如何有效的去设置其跳转点呢?针对大块代码的跳转点设置,目前仍没有好的解决方案。于是有人提出了针对每一条IL指令(或者很少的几条)做跳转。这种方法被称作连续跳转。
很显然,连续跳转的保护强度要高于代码块易位的方法。想要手工修复连续跳转,对于大型软件来说几乎是不可能的。
连续跳转还有一种“变体”。我们事先设置好一连串的跳转指令,这样我们可以在需要跳转的地方每次自动跳转固定次数然后再转到目标点。
q 逻辑跳转
上面的各种跳转方法都是直接跳转,为了增加跳转的复杂度,我们可以对各个跳转增加逻辑判断。当然我们的逻辑判断只能是恒真或者恒假,虽然如此但是其保护强度却大大加强了。
现在我们对代码清单9-12作少量的修改,给两处跳转指令加上条件判断。修改后的代码如代码清单9-13所示。
代码清单9-13 逻辑跳转示例
.method private hidebysig static void Main(string[] args) cil managed
{
//第一段开始
.entrypoint
// 代码大小 49 (0x31)
.maxstack 2
.locals init ([0] string h,
[1] bool CS$4$0000)
IL_0000: nop
IL_0001: ldstr "hello"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "hell0"
IL_000d: call bool [mscorlib]System.String::op_Equality(string, string)
Ldc.i4.1
Ldc.i4.0
Bgt IL_0012
//第一段结束
//第三段开始
IL_0029: call void [mscorlib]System.Console::WriteLine(string)
IL_002e: nop
IL_002f: nop
IL_0030: ret
//第三段结束
//第二段开始
IL_0012: ldc.i4.0
IL_0013: ceq
IL_0015: stloc.1
IL_0016: ldloc.1
IL_0017: brtrue.s IL_0023
IL_0019: nop
IL_001a: call void FlowObufscation.Program::OutString()
IL_001f: nop
IL_0020: nop
IL_0021: br.s IL_0030
IL_0023: nop
IL_0024: ldstr "There is no hello!"
sub
brtrue IL_0029
ret
//第二段结束
} // end of method Program::Main
在代码清单9-13中,我们对两处跳转指令做了修改,第一处修改为:
相当于C#中的c#中的:
if(1>0)
{goto IL_0012;}
第二处跳转被修改为:
相当于C#代码的:
if(1-0==true)
Goto IL_0029;
}
将直接跳转改为逻辑跳转,为反混淆增加了代码识别的难度,因为有时候很难判断代码if条件是源代码就有的还是后来为了混淆才添加上的。当然我们也可以仿造直接跳转的方式,增加多层条件判断来增强保护强度。
增强逻辑跳转的迷惑性的另一个方法是添加临时变量,并在条件判断中使用临时变量。
但是在IL中是允许的,那么如果我们在IL中加入这种判断,反编译时一定会报错的。
q Switch跳转
Switch跳转的基本基本原理和上面的提到的方法大同小异。基本方法为把程序中那个的每条指令都放在switch的判断中。这样就需要建立多个局部变量来进行判断。这种方法无疑会增加太多的垃圾代码。
Switch跳转的例子这里就不做演示了。如果您感兴趣可自行实践。
q 利用语言差异
我们还有一个更强的保护手段,就是利用IL语言和高级语言之间的差异性。我们知道,并不是IL语言所有的特性都会反映在高级语言中。高级语言只是IL的子集而已。
比如在c#中这样的代码是不允许的:
if(12.32)
//do something
关于代码混淆的技术不只这些,由于篇幅所限我们暂且讨论至此。在实际应用中我们也不可能手动的去做代码混淆,有很多成熟的工具可以供选择