现在我们已经很清楚,托管PE文件可以轻而易举的被反编译,如果您想源代码不被使用者通过反编译的方式获得,该使用哪种保护手段呢?

借鉴传统Windows应用程序防止被反汇编的做法,我们也可以采用代码混淆和对应用程序集加壳的方法。关于程序集加壳的内容我会在下一篇文章中讨论。

代码混淆,简单的说就是使用名称替换、字符串加密等手段使得我们最初的代码面目全非,从而使破解者即使能能成功获得IL代码也很难得到想要的源代码。代码混淆常用的方式有名称混淆、流程混淆和语法混淆。

9.3.1 名称混淆

在讲解名称混淆的原理之前,我们先建一个用于测试的控制台程序,如代码清单9-8所示。

代码清单9-8 名称混淆测试代码

class Program
    {
static void Main(string[] args)
        {
string userID="asldjf3333djf";
string pwd = GetPassword(userID);
            ChangeInfo(userID, pwd);
        }
public static string GetPassword(string userID)
        {
return "123456";
        }
public static void ChangeInfo(string userID,string pwd)
        {
        }
    }

代码清单9-8中的代码很简单,包含了三个函数Main函数、GetPassword函数和ChangeInfo函数。从函数的名称和参数中,我们很容易想到其含义。接下来,编译项目,然后使用Reflector打开生成的EXE文件,查看Program类的IL代码。如图9-14所示。

图9-14 Program类的IL代码

下面我们使用PE文件查看工具CFF载入可执行文件。定位到#Strings流,如图9-15所示。当前程序集的类型、引用类型、成员等的定义都在该字符串中。

图 9-15 Program.exe 的#Strings流

下面我们使用CFF来修改#Strings流的内容。查找到字符串“ChangeInfo”和字符串“GetPassword”,随意替换,然后运行程序验证是否有问题。

那么这样的修改有什么作用呢,我们保存修改后,使用Reflector重新打开exe文件,如图9-16所示。

图9-16 修改#Strings流后的IL代码

从9-16中,我们可以看到,先前的ChangeInfo和GetPassword方法名已经被替换成不可识别的乱码。

在完整的实践上面的演示之后,我想告诉你的是,你已经明白了名称混淆的原理。现在简单的总结一下,所谓.NET名称混淆就是修改#Strings流内特定字符串,使其不能 被轻易辨认。上面的例子只修改了两个方法的名称,当然我们可以修改包括在变量在内的所有名称来迷惑“对手”。如果您要问我,如果一个大型的软件项目,这样手动修改可行吗?当然不可行,但是原理知道了,编写这样一个工具并非难事。当然现在已经出现很多成熟的名称混淆工具,这里就不给您一一介绍了。

明白了原理之后,我们再看字符串替换的方式到底有哪些。

第一种替换方法为无意义替换。我们知道在实际的开发过程中,我们都必须遵守一定的命名规范来为类型、属性、字段命名。但是当我们对编译成功的代码进行名称混淆的时候就是要将这些规范的、规律的名称变得毫无意义,毫无规律可循。

第二种替换方法称为不可打印字符替换。在UNICODE字符集中,一些特殊字符,目前无法得到正确的显示,比如从0x01到0x20之间的字符。如果把名称替换成这些字符,显示出来的就是奇怪的乱码。

第三种替换方法为空字符替换。空字符替换就是把名称替换为空串。但是这种方法并不适合实际的应用,因为如果名称都为空,那么势必要产生二义性。

在实际环境中,我们常常要引用其他程序集。如果被引用的程序集的方法名称没有被混淆的话,那么在本程序集中混淆引用的方法名是无效的,会引发调用异常。这是名称混淆最大的局限性。

9.3.2 流程混淆

流程混淆是指打乱方法的流程,让反编译软件无法将代码正确的反编译为高级语言。流程混淆即可保护代码又可增加破解者分析代码的难度。

目前流程混淆基本上都是基于跳转实现的,我们在程序中使用的跳转有如下三种方式:

1) goto跳转;

2) if-else跳转

3) switch跳转。

.NET的流程混淆和传统windows应用程序的流程混淆本质上是有区别的。传统的流程混淆的目的是防止反汇编,而.NET流程混淆的目的是防止反编译。传统的流程混淆是基于汇编指令进行的,操作层次较低,可更改堆栈调用,可操作方式较多;.NET流程混淆是基于IL指令进行的,操作层次较高,不可触及堆栈调用,可操作方式较少。

下面我们针对代码清单9-9和代码清单9-10的示例程序,来实践流程混淆的不同方式。

代码清单9-9 测试流程混淆代码

class Program
    {
static void Main(string[] args)
        {
string h = "hello";
if (h == "hell0")
            {
                OutString();
            }
else
            {
Console.WriteLine("There is no hello!");
            }
        }
public static void OutString()
        {
Console.WriteLine("hello");
        }
    }

现在编译项目,然后使用ILDasm导出改程序的IL代码(只截取Class IL代码部分),如代码清单9-10所示。

代码清单9-10 代码清单9-9的IL代码(Class IL代码部分)

// =============== CLASS MEMBERS DECLARATION ===================

.class private auto ansi beforefieldinit FlowObufscation.Program

extends [mscorlib]System.Object

{

.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)

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!"

IL_0029: call void [mscorlib]System.Console::WriteLine(string)

IL_002e: nop

IL_002f: nop

IL_0030: ret

} // end of method Program::Main

.method public hidebysig static void OutString() cil managed

{

// 代码大小 13 (0xd)

.maxstack 8

IL_0000: nop

IL_0001: ldstr "hello"

IL_0006: call void [mscorlib]System.Console::WriteLine(string)

IL_000b: nop

IL_000c: ret

} // end of method Program::OutString

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// 代码大小 7 (0x7)

.maxstack 8

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: ret

} // end of method Program::.ctor

} // end of class FlowObufscation.Program

// =============================================================

// *********** 反汇编完成 ***********************

// 警告: 创建了 Win32 资源文件 D:\FlowObufscation.res

代码清单9-10为代码清单9-9中整个Program类对应的IL代码。下面结合这两段代码进行流程混淆的实践。

q 代码块易位

Main方法内,代码从IL_0000一直到IL_0030,下面我们将这段代码分成三段,如代码清单9-11所示。

代码清单9-11 把Main方法分成三段

.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)

//第一段结束 

//第二段开始 

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!"

//第二段结束

//第三段开始

IL_0029: call void [mscorlib]System.Console::WriteLine(string)

IL_002e: nop

IL_002f: nop

IL_0030: ret

//第三段结束

} // end of method Program::Main

如代码清单9-11,我将Main方法的IL代码分成了三段,分段并没有什么依据,完全是随意的。下面我们将这三段代码重新组合,按照第一段、第三段、第二段的顺序排列,然后在第一段的结尾加上跳转语句“br IL_0012”,在第二段代码的后面加上跳转语句“br IL_0029”。修改之后的代码如代码清单9-12所示。

代码清单9-12重新排列的Main方法

.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)

br 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!"

br IL_0029

//第二段结束

} // end of method Program::Main

现在我们使用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!"

Ldc.i4.1

Ldc.i4.0

sub

brtrue IL_0029

ret

//第二段结束

} // end of method Program::Main

在代码清单9-13中,我们对两处跳转指令做了修改,第一处修改为:

Ldc.i4.1

Ldc.i4.0

Bgt IL_0012

相当于C#中的c#中的:

if(1>0)

{goto IL_0012;}

第二处跳转被修改为:

Ldc.i4.1

Ldc.i4.0

sub

brtrue IL_0029

ret

相当于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

}

关于代码混淆的技术不只这些,由于篇幅所限我们暂且讨论至此。在实际应用中我们也不可能手动的去做代码混淆,有很多成熟的工具可以供选择。

.net 代码混淆原理性实践相关推荐

  1. Android开发实践:利用ProGuard进行代码混淆

    由于Android的代码大都是Java代码,所以挺容易被反编译的,好在Android ADT为我们集成了混淆代码的工具,一来可以混淆我们的代码,让程序被反编译后基本看不懂,另外还能起到代码优化的作用. ...

  2. Java后端知识之代码混淆-避免反编译工具获取原码

    java, 代码混淆, 编译, 反编译 本文是向大家介绍java后端小知识,它能够实现编译后的class代码加密,能够避免使用反编译工具获取源码. 本文介绍java代码编译成class后,怎么避免用反 ...

  3. .NET代码混淆学习和解决视频批量转换中.wmv转换出错问题

    现在开始对自己一天的工作进行一下大致总结,上午主要是研究ArcGis API For Flex 1.3官方文档和实例的学习啦,不管版本是1.2还是1.3,自己之前对ArcGis Flex API认识比 ...

  4. iOS代码混淆原理初探

    我们在手游平台SDK的iOS版本中, 除了AppStore官方支付之外还集成了第三方支付(微信支付H5和支付宝支付H5版本). 如果用于企业签,不需要做处理,直接使用即可. 但是如果需要上架AppSt ...

  5. python混淆ios代码_XSDK——iOS代码混淆原理

    我们在XSDK的iOS版本中, 除了AppStore官方支付之外还集成了第三方支付(微信支付H5和支付宝支付H5版本). 如果用于企业签,不需要做处理,直接使用即可. 但是如果需要上架AppStore ...

  6. 被黑客们使用的代码混淆技术

    长久以来,代码混淆技术一直都被认为是不能登大雅之堂的奇巧淫技,没有哪个学者会拿正眼瞧它一眼.国际C语言混乱代码大赛(International Obfuscated C Code Contest,IO ...

  7. iOS应用安全之代码混淆实现篇

    1.iOS应用安全之代码混淆设计篇 2.iOS应用安全之代码混淆实现篇 针对设计篇描述的大致思路,现在针对各个问题点,给出实现方法 该脚本大致使用的工具如下:vi.grep.sed.find.awk. ...

  8. 《代码阅读方法与实践》阅读笔记一

    第三本书我选择了代码阅读方法与实践,说实话,觉得三本书里面最好的就是这一本书了,每一段话,每一段代码打偶让我受益匪浅.下面是我的收获: 1.1为什么以及如何阅读代码  将代码作为文献:要养成一个习惯, ...

  9. 最佳实践系列:前端代码标准和最佳实践

    最佳实践系列:前端代码标准 @窝窝商城前端(刘轶/李晨/徐利/穆尚)翻译于2012年 版本0.55 @郑昀校对 isobar的这个前端代码标准和最佳实践文档,涵盖了Web应用开发的方方面面,我们翻译了 ...

最新文章

  1. 其他系统 对外接口设计_设计模式分类及设计原则
  2. Linux服务器日常巡检脚本分享
  3. adb隐藏状态栏图标_[应用]华为手机怎么设置隐藏状态栏上的图标
  4. oracle查询报错clb,Oracle RAC 负载均衡与故障转移(三)
  5. 【转】SSH中 整合spring和proxool 连接池
  6. 一个女人在公司做领导是如何在4年内做到年薪200万的?
  7. oracle分页查询过程的简单实现
  8. 圆弧周长公式_弧长的计算公式是什么?
  9. 计算机左侧没有桌面菜单栏,教您电脑菜单栏不见了
  10. Oracle中的序列,同义词
  11. mysql参数化查询 in_mysql in 查询参数化
  12. B站首页内容运营分析
  13. Java写时复制CopyOnWriteArrayList
  14. android 第三方扇形图,Android扇形图(饼状图)
  15. 第六章 Caché 设计模式 原型模式
  16. 【esp8266】①烧录指南
  17. 这款开源的人脸生成器,我爱了!
  18. Win32多线程调用gdal库接口
  19. java输入商品价格 求总价_Java三种商品总价格大于3500或者某种商品总价大于5000,则打三折,否则八折怎么编写...
  20. Infiniband技术

热门文章

  1. 信息学奥赛一本通 1061:求整数的和与均值 | OpenJudge NOI 1.5 04
  2. 信息学奥赛一本通(1012:计算多项式的值)
  3. 病毒侵袭持续中(HDU-3065)
  4. 训练日志 2018.12.6
  5. 联络员(信息学奥赛一本通-T1393)
  6. 九州云腾双因素认证系统_阿里云全资收购九州云腾,加速构建云上零信任体系...
  7. PyTorch是个啥玩意儿?
  8. 操作系统(李治军) L11内核级线程
  9. Bootstrap-CSS-按钮-图片-辅助类-响应式
  10. python 最小二乘法_最小二乘法及其python实现详解