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

来源:互联网 发布:折半查找算法c语言 编辑:程序博客网 时间:2024/05/16 00:56

来源: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.        重新计算行号

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

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

赶时间之作,如有错误,请见谅。欢迎各位朋友进行讨论。
原创粉丝点击