上面,我们分析了一段IL方法,相信大家对什么是IL有一个认识了,如果想流畅的阅读IL,那么还需要把官方的资料读懂,记牢!
今天我们来讲讲混淆基础。
什么是混淆,故名思意,就是混杂,使界限不分明。这个词相当妙,特别是用在计算机界。
我们知道NET程序集中有一个重要特性叫做MetaData(元数据),它是NET的特性,它记录了相关程序集的一切信息,正因为这样的特性,NET程序集才有了跨平台的可能,才可以在网上传送运行,才不会出现DLL HELL。但同时,由于它记录了所有的信息,使得程序集中的信息可以完全还原至初始状态,使得编译过后的程序集,依然拥有良好的“可读性”。在某些特殊的情况下,这是一件很糟的事情。
对此,我们该怎么做?我们的知识产权无法保障!
正在这样的需求之下,混淆器横空出世。它对MetaData的某些相关信息进行了处理,达到了混淆的作用,在某种意义上讲,它的确能起到一些作用。
最简单的混淆是名称混淆,即将 命名空间名、类名、方法名、字段名等统统换成特殊符号或其它符号,目的就是让你不能与以前的名称建立关联。达到把你弄糊涂的目地。
比方如下代码所示:
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedBlockEnd.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/InBlock.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/ExpandedBlockEnd.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
这样,如果你是Cracker,你想破解这个程序,当你看到Register的时候,你就差不多知道这是你要的目标了,但你只看到了 a 你知道是什么吗?所以,在某此情况下,混淆还是有一定作用的。
混淆名称是一件必要的工作。
我们现在深入的谈谈它的优点:
1. 名称混淆,如果使用短名称及不可见字符,将会缩小程序集的大小
2. 名称混淆,使你的程序咋一看上去,更加难以理解。为什么用“咋”这个词?因为名称混淆也只能骗骗门外汉和小孩
那么它有什么缺点呢?
3. 名称混淆的缺点并不多,只有一个,而且非常致命,这就是有时候,当修改了类名之后不能执行的问题。
一般来说,这种情况在DLL身上发生的更多,但在EXE身上也经常发生。
因为DLL的某些Public方法是对外的结口,在程序开发和调试的时候使用的源名称,当混淆以后,天知道把这些方法改成了什么名称,所以调用肯定报错。处理办法:不混淆对外提供的Public方法
EXE和DLL还有一个共同的容易出错的地方就是资源,混淆器也可以混淆资源名称,这样,就存在的同样的问题――“无法找到资源而报错(动态Load资源的时候)”,处理方法:不混淆程序内部调用的东西。
不知道反射可不可以混淆,理论上。。。应该可以的,不过,好象也听过反射混淆后不能运行的案例。
好,总结一下,最简单的混淆就是名称混淆。你也可以自己在程序开发的过程中就这样做,不过。。。很可惜,它起不了多大作用。它的强度远不足以挡住想得到你源代码或侵害你知道产权的人。
那么有没有更好的办法呢?当然有,就是强度更高的流程混淆,感觉和移形换位、乾坤大挪移有点象。为什么这么说?别急,让我喝口水,接着讲。这样的代码在程序编译后,名称完全被保留,但如果经过名称反混淆以后,它将变成这样:好,水喝完了,呵呵,可能时间有点久……
现在继续来讲混淆,我们讲到那了?? 哦,流程混淆~~
流程混淆感觉和移形换位、乾坤大挪移有点象……好象已经说过……
为什么这么说呢?因为,流程混淆就是移来移去,达到让你看不懂流程的原理来进行的。
在此,我还要介绍一些其它的知识。由于NET的特性,所以,动态调试NET的全部过程几乎是不可能的,所以,静态分析成为了NET的首选。那么,对付静态分析最好的办法是什么呢?在远古的C时代就已经有这种方法了(混淆其实一点也不新鲜,都是旧技术换个名称而以),那时,这种技术叫作花指令。当然流程混淆和花指令还是有区别的,不过我想,基础的原理也算是差不多了。
什么是花指令?
好,我用汇编构建一段代码如下:
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
![](http://writeblog.csdn.net/Images/OutliningIndicators/None.gif)
这就叫做花指令,花指令就是利用跳转或其它的一些指令,并在这此指令中间制造一些无法看懂的代码,使反汇编出来的东西摸不着头脑,并且产生错误的句语(动态跟踪就不会受花指令的影响)从而达到混淆静态反汇编的功能。
那么流程混淆到底是什么呢?
原理基本上是一样,即把方法中的代码分为几段,并把每一段都错开,然后利用“跳转”语句连接原来的流程逻辑,并达到执行正确的目地。原理图如下表所示:
块编号 | 块代码 |
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
}
修改了一下:
明天来讲讲反流程混淆,其实人人都可以做到同时,有说得不对之处,还请高手赐教
由于昨天发布MaxtoCode,所以没有时间写随笔。
本来是没有这一篇的,但想了想,觉得自己讲得太肤浅,怕有的朋友听不懂,所以决定在流程混淆里再讲一篇。这次我们拿XenoCode的混淆算法来进行一次详细的讲解。
XenoCode可能是需要保护自己软件的朋友最常用的混淆工具,他的流程混淆算法是怎样的呢?(有的叫做 控制流程模糊,其实原理都一样)
首先,我再次申请,制造混淆最常用的方式是跳转指令。它就是把原有的代码结构错位,再用跳转指令把原有的执行逻辑连接起来。见上一篇文章的表。而跳转指令有强形跳转如:C#中的goto,也有逻辑跳转,如C#中的 if (a==0){goto ?}等,如果在混淆中充分利用这些技术,混淆的程序将相当复杂,反混淆器将更加困难。还好XenoCode使用的仅仅是goto,而没有包含逻辑跳转在其中。(当然,如果有逻辑跳转,也可以写出反混淆器,因为必须模式是一样的,总要有条件,比方说: a==0才跳,这一句就必须跳,所以a必须恒等于0,那么在逻辑处理的前面肯定有a置0的语句,满足这两个条件,我们就可以判断这是一个破坏条件的条件,名进行恢复)
好,这次我们主要分析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. 重新计算行号
这样,你就能拥有自己的流程混淆了。如果你加入真真假假的逻辑跳转来混淆,强度将会更大。
下一篇,我们讲讲反流程混淆的工作,其实,反流程混淆就是从混淆中找到共同点,并对其进行重新整理,而混淆都有共同点,即使存在特殊情况,也可以用手工来辅助处理。混淆安全吗?你马上就可以知道结果。
赶时间之作,如有错误,请见谅。欢迎各位朋友进行讨论。1. 名称混淆 - 反混淆
名称混淆返混淆,基本上是不太可能的事,因为以前的名称已经换掉了,也没有第二个名称备份表,所以根本无法还换。
不过,可以把不可见字符转换为可见字符,长字符串换成短字符串。
有两种方法可以做处理:
1. 在MetaData中有一个区域叫做 _STRING 它存放了所有名称字符串,只要修改这里的内容,即可,此方法需要对元数据结构特别熟悉
2. 如果你对元数据不了解,没关系,你可以用ILDasm把混淆后的程序集反编译,然后一个一个的对应改过来,再用ilAsm编译,一样可以达到反混淆的作用
其实,对名于名称来说,真的没有多大用处,不用反混淆也行,免得浪费自己的时间。
2. 流程混淆 - 反混淆
流程混淆,在上面已经给出例子。它才是有用的一种混淆方式。它改变流程的存放序顺,从而达到静态反编译的功能。(名称混淆还是可以反编译)
不过,不管怎样,他没有办法去阻止读取IL,这就是流程混淆的天生不足。我们来看看如何对流程反混淆吧。
还是以上面的例子进行操作。
首先特别说明一下: br.s 行号 br 行号 都是强行跳转指令,而流程混淆主要是得用这样的语句进行逻辑连接的。
所以,我们就需要对照着被混淆过的代码,跟着一句一句的逻辑关系,把语句拉出来重新组合。
组合出来后,代码如下:
L_0000: newobj instance void [mscorlib]System.Random::.ctor()
L_0005: stloc.1
L_0006: ldstr ""
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_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_0032: ldloc.0
L_0033: ldarg.3
L_0034: ble.s L_0026
L_0036: ldloc.2
L_0037: ret
其实,反流程混淆也相当的容易,只要按照执行流程加入特定的条件即可以得到代码的序顺。
为此,我特别写了一个反流程混淆的工具(Deflow)。将上面的代码反混淆后,得到如下代码:
L_0000: newobj instance void [mscorlib]System.Random::.ctor()
L_0001: stloc.1
L_0002: ldstr ""
L_0003: stloc.2
L_0004: ldc.i4.0
L_0005: stloc.0
L_0006: br.s L_0017
L_0007: ldloc.2
L_0008: ldarg.1
L_0009: ldloc.1
L_000A: callvirt instance float64 [mscorlib]System.Random::NextDouble()
L_000B: ldarg.2
L_000C: conv.r8
L_000D: mul
L_000E: conv.i4
L_000F: ldelem.u2
L_0010: box char
L_0011: call string string::Concat(object, object)
L_0012: stloc.2
L_0013: ldloc.0
L_0014: ldc.i4.1
L_0015: add
L_0016: stloc.0
L_0017: ldloc.0
L_0018: ldarg.3
L_0019: ble.s L_0007
L_001A: ldloc.2
L_001B: ret
反流程混淆,并不难,说句实话,它只是一个特征的积累,你不断的分析被混淆后的特征,然后进行分析,写出反混淆的算法,即可开发出反混淆工具。上面的程序并不长,你可以手工的进行反混淆,但如果很长,那么反混淆就是一件痛苦的事了,但我只用Deflow,请请点一下按钮,就可以得到我感兴趣的代码。而且,只要是编程的人,不要你全面撑握NET什么的知识,只要你对字符串操作特别熟悉,再加上一些些聪明和经验,反流程混淆不是一件难事。
下面,我给大家介绍我反混淆的步骤:
1. 拿到混淆过后的程序集(当然,是流程混淆过的)
2. 使用我修改过的Ildasm进行反编译
3. 得到一个IL文件(明文格式的文本文件)
4. 打开文件,找到自己感兴趣的代码,把那一段方法取出
5. 使用Deflow反混淆
6. 把反混淆后的代码Copy回去
7. 使用ilasm 重新编译il文件
8. 使用Reflector查看,ok,已经可以显示C#或VB的代码了。
由于,反编译出来的il是明文,所以,只要你对字符串操作比较熟,你就可以做一个非常完善的针对工程的反混淆工具。反混淆原理就是如此,不管反混淆强度如何改变,它也无法脱理这个原理,所以即使是流程混淆,也无法保证程序代码的安全性。
这里特别说明一下:Deflow纯属研究性质,所以功能非常有限,我也不会发布和公开,在研究的过程中发现有些混淆器相当狡猾,它把程序正的跳转和混淆跳转再次混淆,这种情况,我们有时候必须要手动干涉,不过,这种情况并不会带来多少工作量。至少我现在还没有手工改过。
总之,反流程混淆的工具,是每个程序员都可以自己行开发的,因为起点太低,任何人了解原理后都可以对其进行研究,并开发出反混淆的工具。所以,流程混淆并不理想!
更重要的是,即使流程混淆了,也可以进行反编译,从而,关键代码还是会被修改并再次发布,危险性依然存在。我就不明白为什么MS要提供ilDasm和ilAsm。这不是制造木马的好工具吗?把别个的程序反编译,在里面某个铵钮中加入自己的代码。再编译,打包,当正版发布,神不知,鬼不觉的让你中马,结果机器的主人还要找软件作者去算账……呵呵,怕怕啊。
下面,我们将介绍一下最新的MaxtoCode的原理。有兴趣的朋友不要错过。