zoukankan      html  css  js  c++  java
  • .NET安全策略与优化

    实施优化与安全策略的必要性

    .NET具有较多的优点,如:标准集成,简化应用,对移动设备的支持等。但使用.NET编写的程序有个致命的缺点:易被反编译,且运行时占用较大的资源。因此,为了更好的体现.NET的优势,我们必须对在.NET下编写的程序进行优化;与此同时,为了所编程序不被反编译,必然要实施有效可行的安全策略。

    那么为什么.NET易被反编译呢?

    因为.net在编译的过程中,不是将源代码直接编译成机器码,而是将其编译为IL,即中间语言。IL是一种介于源代码与机器码间的代码,其中包含了大量的可理解的标识符,正是因为这个原因.net实现了良好的可移植性。但也正是这一点使.net更易被反编译。

    第一章 源码混淆

    1.1 源码混淆

    源码混淆指在不影响功能的前提下,将代码变得模糊难以理解。有效的混淆一般即增加了代码的复杂度,又使变换后的代码不可逆转。当然,混淆的目的不是一定要达到不可被反编译,而是要使反编译的花费比从中得到的利益多。

    void primes(int cap) {

    int i, j, composite;

    for(i = 2; i < cap; ++i) {

    composite = 0;

    for(j = 2; j * j < i; ++j) 

    composite += !(i % j);

    if(!composite)

    printf("%d\t", i);

    }

    }

    int main() { 

    primes(100);

    }

    void _(int __, int ___, int ____) {

    ((___ / __) <= 1) ? _(__,___+1,____) : !(___ % __) ? _(__,___+1,___ % __) : 

    ((___ % __)==(___ / __) && !____) ? (printf("%d\t",(___ / __)), 

    _(__,___+1,____)) : ((___ % __) > 1 && (___ % __) < (___ / __)) ? 

    _(__,___+1,____ + !((___ / __) % (___ % __))) : (___ < __ * __) ? 

    _(__,___+1,____) : 0;

    int main()_(100,0,0);

    1.1源码方法

    代码混淆的具体实施方法有哪些呢?

    1. 名称混淆

    即对程序中的变量名,常量名,类名,方法名称等标识符做词法上的变换改名。如:

    Create table()X4b56eb59db89b78()

    Licensekey  459b%*(#!X!56eb)d      

    2. 控制流混淆

    即打乱某段代码本身的逻辑关系,把相关的语句分散到程序不同位置并实现某项功能的次序混淆和隐藏真实执行路径的混淆。简单的说就是打乱程序的原有顺序,然后使用跳转语句连接原来的流程逻辑,并达到正确执行的目的。如:

    public int CompareTo(Object o) {

        int n = occurrences – ((WordOccurrence)o).occurrences;

        if (n == 0) {

            n = String.Compare(word, ((WordOccurrence)o).word);

        }

        return(n);

    }

    public virtual int _a(Object A_0) {

    int local0;

    int local1;

    local0 = this.a – (c) A_0.a;

    if (local0 != 0) goto i0;

    goto i1;

    while (true) {

    return local1;

    i0: local1 = local0;

    }

    i1: local0 = System.String.Compare(this.b, (c) A_0.b);

    goto i0;

    }

    3. 数据混淆

    即对程序中的数据结构进行混淆。它又可细分为:

    ① 存储混淆——改变数据在内存中存储方式。如,将局部变量变为全局变量。

    ② 混淆编码方式——改变数据的解析方式。比如,可以将变量a解析为m*2+n。

    ③ 聚集方式混淆——改变原有数据分组的方式。最简单的例子是:将一个长数组拆分为多个短数组。

    25

    56

    78

    2

    123

    148

    12

    14

    29

    25

    56

    78

    2

    123

    148

    12

    14

    29

                      ④ 次序混淆——改变数据的位序。还拿数组举例,比如可以通过使用函数F(i)来确定数组中第i个元素的新位置。

    4. 预防混淆

    主要针对一些专用反编译器的设计缺陷,以主动的方式防止反编译器反向还原混淆后的代码。

    1.1.2 产品举例

    了解什么是代码混淆,以及代码混淆的方法后,我们将以Dotfuscator,Xenocode Postbuild为例进一步说明代码混淆。

    Dotfuscator支持名称混淆和控制流混淆,相比其他混淆器,使用Dotfuscator混淆的代码是不可逆转的。因为它在名称混淆时,会将方法名,类名缩成1~2个字符,这大大的缩小了整个文件的大小。同时,Dotfuscator采用重载归纳,更好的增强了混淆效果。

    Xenocode Postbuild较Dotfuscator相比,在名称混淆时可以选择要混淆的方法或标识符;控制流混淆时可以选择混淆的等级。Xenocode Postbuild最大的优点是,它可以使.NET程序脱离.NET框架运行。但混淆后会使程序启动变慢,文件变大。

    1.1.3 判断标准

    Dotfuscator和Xenocode都可以实现代码混淆,那么该如何判定哪个产品更好或者说更适合我们呢?首先,我们需要知道的是:混淆效果一般通过强度、耐受性、开销、隐蔽性四个方面进行判断。

    ① 强度:指混淆变换后的程序相对原始程序对恶意用户理解程序造成的困 难程度或复杂度。

    void primes(int cap) {

    int i, j, composite;

    for(i = 2; i < cap; ++i) {

    composite = 0;

    for(j = 2; j * j < i; ++j) 

    composite += !(i % j);

    if(!composite)

    printf("%d\t", i);

    }

    }

    int main() { 

    primes(100);

    }

    _(__,___,____,_____){___/__<=_____?_(__,___+_____,____,_____):!(___%__)?_(__,___+_____,___%__,_____):___%__==___/

    __&&!____?(printf("%d\t",___/__),_(__,___+_____,____,_____)):___%__>1&&___%__<___/__?_(__,_____+

    ___,____+!(___/__%(___%__),_____)):___<__*__?_(__,___+_____,____,_____):0;}

    main(){_(100,0,0,1);}

    ② 耐受性:指混淆变换后的程序对使用自动去混淆工具进行攻击的反抗度。

    ③ 开销:指经过混淆变换后的程序在执行时由变换所带来的额外的执行时间和所需存储空间的开销。

    可见,经过Xenocode Postbuild混淆后字符变长,需要更多的存储空间;且经过Xenocode Postbuild混淆的程序可以脱离.NET框架运行,这在一定的程度上也增加了额外的执行时间。

    ④ 隐蔽性:尽量使用与源代码类似的语法结构进行混淆,以增强隐蔽性。

    void primes(int cap) { void primes(int m, int t, int c) {

    int i, j, composite; int i,j;

    for(i = 2; i < cap; ++i) { i = t / m;

    composite = 0; j = t % m;

    for(j = 2; j * j < i; ++j)  ( i <= 1) ? primes(m,t+1,c) : (!j)

         composite += !(i % j); ? primes(m,t+1,j) : (j == i && !c)

    if(!composite) ? (printf("%d\t",i),

         printf("%d\t", i); primes(m,t+1,c)) : (j > 1 && j < i)

    } ? primes(m,t+1,c + !(i % j)) :

    }                                             (t < m * m)

    ? primes(m,t+1,c) : 0;

    int main() {  }

    primes(100); }                                int main() {

    primes(100,0,0);

    }

    void _(int __, int ___, int ____) {

      ((___ / __) <= 1) ? _(__,___+1,____) : !(___ % __) ? _(__,___+1,___ % __) : 

      ((___ % __)==(___ / __) && !____) ? (printf("%d\t",(___ / __)), 

      _(__,___+1,____)) : ((___ % __) > 1 && (___ % __) < (___ / __)) ? 

      _(__,___+1,____ + !((___ / __) % (___ % __))) : (___ < __ * __) ? 

      _(__,___+1,____) : 0;

    int main(){ 

         primes(100); }

    通过以上介绍,不难看出,强度和耐受性是必须保证的。因为混淆的目的就是增加程序的复杂度,防止恶意用户的使用。在保证前两个标准的前提下,再考虑开销和隐蔽性。

    第二章  加 壳

    加壳即对可执行程序资源压缩与加密,是保护文件的常用手法。经过加壳的程序可以直接执行,但不可查看源码。只有成功脱壳后,才能查看源码。

    2.1 原理

    加壳实际上是利用特殊的算法,实现对EXE,DLL文件中的资源进行压缩,加密。类似于使用WINZIP压缩文件,只不过它是个可独立执行的文件,且解压过程隐秘,全部在内存中完成。它们附加在原程序上通过Windows加载器载入内存后,先于原始程序执行,得到控制权,执行过程中对原始程序进行解密、还原,还原完成后再把控制权交还给原始程序,执行原来的代码部分。加上外壳后,原始程序代码在磁盘文件中一般是以加密后的形式存在的,只在执行时才在内存中还原,这样就可以比较有效地防止破解者对程序文件的非法修改,同时也可以防止程序被静态反编译。

    2.2 壳的分类

    现在加壳的产品较多,又都有各自的特点。但总的来说主要分为以下几类:

    1. 压缩壳——减小软件的体积,加密保护不是重点

    但目前支持.NET较少,当前较流行且稳定的,如:ASPack,它支持Microsoft Visual C++,Visual Basic,Delphi 和其它win32编译器,压缩率可达50%以上。

    2. 加密壳——使用了反跟踪调试及脱壳技术,而对文件的体积不做过多的关注。

    目前加密壳较多,而且能实现较好的压缩功能,如:Winlicense , AsProtect。

    3. 伪装壳——将程序的OEP换成其他代码,使查壳软件查不出是什么壳以及用什么语言编写的。如,Winlicense。

    4. 多层壳 ——在壳相互不冲突的前提下,对程序加多层壳。如:AsPack。

    2.3 产品举例

    Winlicense 由Oreans公司开发,它集加壳与授权管理等功能为一体。从攻击者的角度看,Winlicense使用了完全不同于常规的保护机制。它采用虚拟机保护机制,将关键代码用虚拟机保护,因此国内还未出现相应的脱壳工具。但目前Winlicense版本只可对.net的exe进行加壳,暂不支持对.dll加壳(近期推出支持对.NET中DLL加壳的版本)。

    AsProtect是由Star-force开发的,注重兼容性及稳定性,并且集成了ASpack的强压缩功能。它拥有压缩,加密,反跟踪,反汇编代码,CRC校验和花指令等保护措施,除此还使用RSA1024作为注册密钥生成器。正因为它功能强大,使用广泛,所以研究者也很多,目前针对其较早版本已有相应的脱壳软件。

    下面对以上两款产品做具体的比较,如表 3-1:

    支 持 的

    功     能

    产        品

    Dotfuscator

    Xenocode Postbuild

    X86和X64支持

    开发平台

    Visual Studio (2003, 2005, and 2008) and 早期 VS2010

    Visual Studio(2005,2008)

    .NET框架

    支持.NET1.0到3.5版本和早期的4.0

    支持.NET2.0,.NET3.5及新.NET3.5

    命令行支持

    名称混淆

    控制流程混淆

    数据混淆

    预防混淆

    ×

    ×

    脱离.NET框架

    ×

    2.4 加壳误报

    谈到 Winlicense加壳采用虚拟机机制的话,必然要讨论另一问题:执行加壳程序时,杀毒软件为何会误报?

    首先需要清楚杀毒软件的工作原理:杀毒软件一般通过使用杀毒引擎和病毒库查杀病毒。杀毒引擎的功能和任务是判断指定文件或程序是否合法,当其无法进行判定时,再将文件与病毒库进行对比,确定其合法性。

    而病毒为了躲避杀毒软件的查杀,充分利用虚拟机加壳后难脱壳这一特点,将自己加壳。这样一来杀毒软件无法判定哪些是病毒,但为了实现保护功能,只能将采用同一机制的文件全部认为为病毒。

    那么该如何解决呢?

    据了解目前有两种解决方法:

    1.与杀毒软件公司协调,消除误报;

    2.在杀毒软件公司拒绝消除误报的情况下,加壳软件更换算法。

    2.5 总结

    源码混淆与加壳都是提高程序安全性的措施,那么他们之间又有哪些区别呢?

    源码混淆是对代码本身进行修改,增加程序的复杂度,使恶意用户无法理解原始代码;而加壳是对整个程序而言,相当于给程序加了层外壳,让恶意用户根本无法看到程序的原本面貌。两者的侧重点不同,效果也不同。源码混淆使恶意用户无法理解但仍可看到原始代码;加壳则只允许执行应用程序,不可查看原代码。

  • 相关阅读:
    Ubuntu 12.04 git server
    Moonlight不再继续?!
    Orchard 视频资料
    一恍惚八月最后一天了
    Box2D lua binding and Usage
    50岁还在编程,也可以是一种成功
    DAC 4.2 发布
    再次祝贺OpenStack私有云搭建成功
    vue项目快速搭建
    pdf.js使用详解
  • 原文地址:https://www.cnblogs.com/cwfsoft/p/1680689.html
Copyright © 2011-2022 走看看