由浅至深 谈谈.NET混淆原理(三)-- 流程混淆

来源:http://mchong.bokee.com/

好,水喝完了,呵呵,可能时间有点久……

 

         现在继续来讲混淆,我们讲到那了?? 哦,流程混淆~~

 

         流程混淆 感觉和移形换位、乾坤大挪移有点象……好象已经说过……

 

     为什么这么说呢?因为,流程混淆就是移来移去,达到让你看不懂流程的原理来进行的。

 

     在此,我还要介绍一些其它的知识。由于NET的特性,所以,动态调试NET的全部过程几乎是不可能的,所以,静态分析成为了NET的首选。那么,对付静态分析最好的办法是什么呢?在远古的C时代就已经有这种方法了(混淆其实一点也不新鲜,都是旧技术换个名称而以),那时,这种技术叫作花指令。当然流程混淆和花指令还是有区别的,不过我想,基础的原理也算是差不多了。

 

     什么是花指令?

 

     好,我用汇编构建一段代码如下:

 

 

修改了一下:

00410070  >/ $ 8BEC           MOV EBP,ESP

00410072    | . 6A  00           PUSH  0                                    ;  / pModule  =  NULL

00410074    | . E8 310A0000    CALL  < JMP. & KERNEL32.GetModuleHandleA >     ; /GetModuleHandleA

00410079    | . A3 B0004200    MOV DWORD PTR DS:[4200B0],EAX

0041007E  
| . A3  24004200     MOV DWORD PTR DS:[ 420024 ],EAX

00410083    | . E8 160A0000    CALL  < JMP. & KERNEL32.GetCommandLineA >      ; [GetCommandLineA

00410088       90              NOP          《- 注意这里

00410089       90              NOP

0041008A     
90              NOP

0041008B     
90               / NOP

0041008C     
90              NOP

0041008D     
90               | NOP

0041008E     
90               | NOP

0041008F     
90              NOP

00410090       90               | NOP

00410091       90              NOP          《- 还有这里

00410092    | . C705  20004200  >| MOV DWORD PTR DS:[ 420020 ],loaddll. 00420 > ;  ASCII  " Missing DLL name "

0041009C  
| . E9 EB010000     | JMP loaddll.0041028C

004100A1  
|>  3C  22            | CMP AL, 22

004100A3  
| . ^ 75  E6          /JNZ SHORT loaddll.0041008B

004100A5  
|>  8A06            / MOV AL,BYTE PTR DS:[ESI]

004100A7  
| . 3C  20            | CMP AL, 20

004100A9  
| 75  03            | JNZ SHORT loaddll.004100AE

004100AB  
| 46               | INC ESI

004100AC  
| . ^ EB F7          /JMP SHORT loaddll.004100A5

 

 

00410070  >/ $ 8BEC           MOV EBP,ESP

00410072    | . 6A  00           PUSH  0                                    ;  / pModule  =  NULL

00410074    | . E8 310A0000    CALL  < JMP. & KERNEL32.GetModuleHandleA >     ; /GetModuleHandleA

00410079    | . A3 B0004200    MOV DWORD PTR DS:[4200B0],EAX

0041007E  
| . A3  24004200     MOV DWORD PTR DS:[ 420024 ],EAX

00410083    | . E8 160A0000    CALL  < JMP. & KERNEL32.GetCommandLineA >      ; [GetCommandLineA

00410088      EB  08           JMP SHORT loaddll. 00410092                    《- 注意 已经没有00410092

0041008A     
2910            SUB DWORD PTR DS:[EAX],EDX

0041008C     F8             CLC

0041008D     
60              PUSHAD

0041008E     
99              CDQ

0041008F     F8             CLC

00410090      E8 E8C70520     | CALL 2046C87D

00410095       0042  00         ADD BYTE PTR DS:[EDX],AL

00410098    |?  59              POP ECX

00410099    |?  0142  00         ADD DWORD PTR DS:[EDX],EAX

0041009C  
| . E9 EB010000     | JMP loaddll.0041028C

004100A1  
|>  3C  22            | CMP AL, 22

004100A3  
| . ^ 75  E6          /JNZ SHORT loaddll.0041008B

004100A5  
|>  8A06            / MOV AL,BYTE PTR DS:[ESI]

004100A7  
| . 3C  20            | CMP AL, 20

004100A9  
| 75  03            | JNZ SHORT loaddll.004100AE

004100AB  
| 46               | INC ESI

004100AC  
| . ^ EB F7          /JMP SHORT loaddll.004100A5
 

 

这就叫做花指令,花指令就是利用跳转或其它的一些指令,并在这此指令中间制造一些无法看懂的代码,使反汇编出来的东西摸不着头脑,并且产生错误的句语(动态跟踪就不会受花指令的影响)从而达到混淆静态反汇编的功能。

 

那么流程混淆到底是什么呢?

 

原理基本上是一样,即把方法中的代码分为几段,并把每一段都错开,然后利用“跳转”语句连接原来的流程逻辑,并达到执行正确的目地。原理图如下表所示:

块编号
块代码
1
第一个功能
2
第二个功能
3
第三个功能
4
第四个功能

 

块编号
块代码
跳转
1
第一个功能
Jmp 2
4
第四个功能
 
3
第三个功能
Jmp 4
2
第二个功能
Jmp 3

 

 

基本流程混淆原理即是上表所示,总结就以下这么几个字:破坏原有程序结构,并利用 Jmp 语句接连原有流程。

 

基于上面原理所说,所以流程混淆是肯定会耗费资源的。而且,有些特殊的过程,可能在混淆后不能正常使用了,我以前就碰上一个,具体情况记不太清楚,不过第一次运行结果正确,第二次就不正确了,使用 DBGCLR 跟踪去看,发现第一次执行正常,而第二次则未执行。由于时间非常紧迫,所以未更深入的研究原因所在,当不混淆此方法后,一切正常。

 

流程混淆是目前各大厂商的混淆利器的最高境界,带有流程混淆的混淆器,基本售价都是上千美元,合人民币近万元。这么高的金额代价之下,它的强度是不是已经能够达到我们的需要了呢?呵呵。这个问题,我们在下一章里再来讨论吧。

 

附一段 IL 流程混淆前后的代码,明天拿它开刀。

 

C# 源:

 

          private string CreatePassword(char[] passwords,int arraylenghts,int lenghts)

 

         {

 

              int i;

 

              Random RndNumber = new Random();

 

              string return_value="";

 

               for(i=0;i<=lenghts;i++)

 

              {    

 

          return_value=return_value+passwords[(int)(RndNumber.NextDouble()*arraylenghts)];

 

              }

 

              return(return_value);

 

         }

 

IL源:

 

.method private hidebysig instance string CreatePassword(char[] passwords, int32 arraylenghts, int32 lenghts) cil managed
{
      // Code Size: 50 byte(s)
      .maxstack 4
      .locals (
            int32 num1,
            [mscorlib]System.Random random1,
            string text1)
      L_0000: newobj instance void [mscorlib]System.Random::.ctor()
      L_0005: stloc.1 
      L_0006: ldstr ""
      L_000b: stloc.2 
      L_000c: ldc.i4.0 
      L_000d: stloc.0 
      L_000e: br.s L_002c
      L_0010: ldloc.2 
      L_0011: ldarg.1 
      L_0012: ldloc.1 
      L_0013: callvirt instance float64 [mscorlib]System.Random::NextDouble()
      L_0018: ldarg.2 
      L_0019: conv.r8 
      L_001a: mul 
      L_001b: conv.i4 
      L_001c: ldelem.u2 
      L_001d: box char
      L_0022: call string string::Concat(object, object)
      L_0027: stloc.2 
      L_0028: ldloc.0 
      L_0029: ldc.i4.1 
      L_002a: add 
      L_002b: stloc.0 
      L_002c: ldloc.0 
      L_002d: ldarg.3 
      L_002e: ble.s L_0010
      L_0030: ldloc.2 
      L_0031: ret 
}
 

 

 

 

 

 

 

 

 

 

IL 混:

 

.method private hidebysig instance string CreatePassword(char[] xb97f21c4af3d3653, int32 x37f140bfe992d2c4, int32 x6ad44599b278247e) cil managed
{
      // Code Size: 56 byte(s)
      .maxstack 4
      .locals (
            int32 num1,
            [mscorlib]System.Random random1,
            string text1)
      L_0000: newobj instance void [mscorlib]System.Random::.ctor()
      L_0005: stloc.1 
      L_0006: ldstr ""
      L_000b: br.s L_0021
      L_000d: mul 
      L_000e: conv.i4 
      L_000f: ldelem.u2 
      L_0010: box char
      L_0015: call string string::Concat(object, object)
      L_001a: stloc.2 
      L_001b: ldloc.0 
      L_001c: ldc.i4.1 
      L_001d: add 
      L_001e: stloc.0 
      L_001f: br.s L_0032
      L_0021: stloc.2 
      L_0022: ldc.i4.0 
      L_0023: stloc.0 
      L_0024: br.s L_0032
      L_0026: ldloc.2 
      L_0027: ldarg.1 
      L_0028: ldloc.1 
      L_0029: callvirt instance float64 [mscorlib]System.Random::NextDouble()
      L_002e: ldarg.2 
      L_002f: conv.r8 
      L_0030: br.s L_000d
      L_0032: ldloc.0 
      L_0033: ldarg.3 
      L_0034: ble.s L_0026
      L_0036: ldloc.2 
      L_0037: ret 
}
明天来讲讲反流程混淆,其实人人都可以做到
同时,有说得不对之处,还请高手赐教
由浅至深 谈谈.NET混淆原理(三)-- 流程混淆(续)

由于昨天发布MaxtoCode,所以没有时间写随笔。

本来是没有这一篇的,但想了想,觉得自己讲得太肤浅,怕有的朋友听不懂,所以决定在流程混淆里再讲一篇。这次我们拿XenoCode的混淆算法来进行一次详细的讲解。

XenoCode可能是需要保护自己软件的朋友最常用的混淆工具,他的流程混淆算法是怎样的呢?(有的叫做 控制流程模糊,其实原理都一样)

首先,我再次申请,制造混淆最常用的方式是跳转指令。它就是把原有的代码结构错位,再用跳转指令把原有的执行逻辑连接起来。见上一篇文章的表。而跳转指令有强形跳转如:C#中的goto,也有逻辑跳转,如C#中的 if (a==0){goto ?}等,如果在混淆中充分利用这些技术,混淆的程序将相当复杂,反混淆器将更加困难。还好XenoCode使用的仅仅是goto,而没有包含逻辑跳转在其中。(当然,如果有逻辑跳转,也可以写出反混淆器,因为必须模式是一样的,总要有条件,比方说: a==0才跳,这一句就必须跳,所以a必须恒等于0,那么在逻辑处理的前面肯定有a0的语句,满足这两个条件,我们就可以判断这是一个破坏条件的条件,名进行恢复)

好,这次我们主要分析XenoCode是如何来进行流程混淆的,你也可以手工校仿,不过效率并不高。

我们还是来看上篇文章的代码:

.method private hidebysig instance string CreatePassword(char[] xb97f21c4af3d3653, int32 x37f140bfe992d2c4, int32 x6ad44599b278247e) cil managed 
{
      // Code Size: 56 byte(s)
      .maxstack 4
      .locals (
            int32 num1,
            [mscorlib]System.Random random1,
            string text1)
      L_0000: newobj instance void [mscorlib]System.Random::.ctor()
      L_0005: stloc.1 
      L_0006: ldstr ""
      L_000b: br.s L_0021
      L_000d: mul 
      L_000e: conv.i4 
      L_000f: ldelem.u2 
      L_0010: box char
      L_0015: call string string::Concat(object, object)
      L_001a: stloc.2 
      L_001b: ldloc.0 
      L_001c: ldc.i4.1 
      L_001d: add 
      L_001e: stloc.0 
      L_001f: br.s L_0032
      L_0021: stloc.2 
      L_0022: ldc.i4.0 
      L_0023: stloc.0 
      L_0024: br.s L_0032
      L_0026: ldloc.2 
      L_0027: ldarg.1 
      L_0028: ldloc.1 
      L_0029: callvirt instance float64 [mscorlib]System.Random::NextDouble()
      L_002e: ldarg.2 
      L_002f: conv.r8 
      L_0030: br.s L_000d
      L_0032: ldloc.0 
      L_0033: ldarg.3 
      L_0034: ble.s L_0026
      L_0036: ldloc.2 
      L_0037: ret 

}

 

分析一下其中的 br.s 指令,(br.s指令是强跳指令)我们可以得出一个结论:

      L_000b: br.s L_0021
      L_001f: br.s L_0032
      L_0030: br.s L_000d

这三个是重要跳转指令,其算法如下:

序号

源代码块

序号

新代码块

1

1

1

1

2

2

2

4

3

3

3

3

4

4

4

2

这是什么意思呢?

从方法的头尾开始分析,直至中间分析完毕。尾部的基本上就是头部的代码,头部的也有尾部的代码,互相交错,从而实现混淆,一般的反编译器只能对此Say No了。

如果你要手工混淆你的代码,你需要做以下几件事:

1.        把源代码分成几块

2.        把这么几块的顺序打乱

3.        用br.s对这几块的顺序进行连接,并保护执行达到原来的逻辑

4.        重新计算行号

这样,你就能拥有自己的流程混淆了。如果你加入真真假假的逻辑跳转来混淆,强度将会更大。

下一篇,我们讲讲反流程混淆的工作,其实,反流程混淆就是从混淆中找到共同点,并对其进行重新整理,而混淆都有共同点,即使存在特殊情况,也可以用手工来辅助处理。混淆安全吗?你马上就可以知道结果。

赶时间之作,如有错误,请见谅。欢迎各位朋友进行讨论。
特别说明 -------- 新版本请访问网站www.bluefishes.net. 考虑到稳定性,新版本不支持Visual Studio.NET 2002. 产品名称 -------- SharpRefactor(C#代码重构工具) 产品简述 -------- 本工具用于代码重构和代码自动生成。现阶段主要用于C#代码重构。 所谓重构也就是“保持软件的外在功能不变,重新调整其内部结构”。 关于每种重构模式的含义,请参见http://www.refactoring.com/ 具体功能参见具体版本的特性列表。 对重构很感兴趣或是很关注使用效率的用户,希望[使用指南]一节对你有所助益。 版本 ---- 1.0.0(BETA). 发布日期 -------- 2003/6/13 作者 ---- C# Refactor Team. 制作 ---- Blue Workshop. 环境要求 -------- Visual Studio.Net 2003 Windows 2000 + SP2 + SMTP Service 特别提示 -------------- 本插件使用了异常处理和报告机制。 一般而言,环境、代码以及其他原因都会导致程序出错。因此,在您使用本插件的过程中,可能会弹出错误报告。一部分错误不会影响使用,另一部分会影响使用。 C# Refactor Team愿意随时提供技术支持,及时为你解除问题。 版本1.0.0特性 ------------- Rename Parameter Rename Local Variable Rename Field Rename Property Rename Class Rename NameSpace Safe Delete Parameter Safe Delete Local Variable Safe Delete Field Safe Delete Property Safe Delete Method Safe Delete Class Safe Delete NameSpace Extract Interface Undo/Redo Preview usage before refactor(重构前预览) Auto build after refactor(重构后自动生成) Options(工具选项) User feedback(用户反馈) 使用指南 -------- 所有功能暂不支持静态成员。 尽量使用鼠标右键菜单。 尽量使用快捷方式,比如:单击鼠标右键,弹出菜单后再连续按‘R’键和‘C’键就可以调用[Rename]菜单下的[Rename Class]命令。 在使用Rename系列命令时,需要先转到定义代码元素的地方。此时,可以先使用右键菜单中的[转到定义]命令。 在Option中可以设置首选项。 由于Visual Studio在生成较大的解决方案时有时会不成功,所以Auto build after refactor通常用于较小的解决方案。 Rename NameSpace与Move Class不同。Move Class的焦点在Class,即改变类所在的NameSpace。而Rename NameSpace的焦点在NameSpace,即改变指定NameSpace的名字,并更新该NameSpace的所有引用(Usages)。 错误报告以及建议功能需要网络连接和Windows自带的SMTP服务。因为发送速度很快,所以不会占用您宝贵的时间。 可以使用User feedback功能提出您睿智的建议、批评、任何意见。 技术支持 -------- Tiger.BlueWorkshop@163.net 下载 ---- www.csdn.net 版本 发布日期 ----------------------------- 1.0.0(Beta) 2003/6/13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值