您的位置:首页 > 其它

.NET体系中的源程序安全问题

2005-02-20 22:35 281 查看


在.NET平台上,代码以中间语言的形式运行,它是.NET众多优势的基础。但在独立桌面应用中,它给源代码的安全带来了威胁。本文探讨产生这个问题的原因,分析可能的解决办法。

  在Visual Studio.NET(VS.NET)体系中,VB、Visual C++以及C#之类的编译器把源程序编译成MSIL。MSIL即Microsoft Intermediate Language,或Microsoft中间语言,它在执行之前被即时(Just-In-Time Compile,JIT)编译成为机器语言。但是,你可能还没有深入了解当你在VS中点击Build按钮时发生了什么事情,或者你私有的源代码和信息是否在偷窥的眼光面前安然无恙、当你把IL代码发布给客户时是否能够保证代码不被篡改。下面我们将深入.NET的内部工作过程,探讨VB.NET体系MSIL的特点和一些你必须关心的问题。

  你必须搞清楚以下几个问题。首先,.NET是为客户机/服务器系统以及Web应用而设计的。软件开发正在逐渐向Internet以及基于客户机/服务器的应用发展,许多应用不再有传统风格的界面,而是提供类似浏览器的界面。.NET也同样追随着这个趋势。

  第二,在桌面应用中,我们无法保护以受管理的MSIL形式存在的代码,如果你觉得保护知识产权(即源代码)非常重要,那么.NET不适合桌面应用开发。虽然MSIL的承诺令人心动,虽然.NET平台和CLR(Common Language Runtime)很稳定,但从安全的角度来看,对于一个独立的桌面应用来说,这一切缺乏实际意义。在.NET中,作为一个VB程序员甚至是C#程序员,你只能编写受管理、不受保护的代码。

  由于存在这种限制,如果你要在桌面应用中保护代码,你必须使用非受管理的C++。保护知识产权唯一真正有效的方法是:用非受管理的C++组件封装代码,然后从.NET受管理代码中通过COM协作接口调用它。

  另外你还必须清楚的是,由于Active Server Pages.NET(ASP.NET)完全在服务器端运行,因此ASP.NET应用是安全的。实际上,这正是.NET最理想的境界——在受保护的服务器上运行代码,让代码远离任何想要研究它的人。ASP.NET把Web开发简化到了难以置信的程度,而Visual Basic.NET正是编写ASP.NET应用的优秀工具。

  掌握VB.NET需要经过艰苦的学习,而全面接受.NET更是一个缓慢的过程。从VB6迁移到VB.NET并非轻而易举,在把VB6应用移植到VB.NET之前的时间里,你仍旧需要提供对VB6应用的支持。在未来很长的时间内,许多开发者仍将使用VB6。

二、中间语言
 为了了解在用VB.NET构造工程的过程中发生了什么事情,我们需要创建一个生成代码和程序集时使用的示例工程:打开VS.NET,新建一个Visual Basic工程,在窗体中加入一个文本标签(Label),然后把文本标签的Text属性改成“Good Bye Visual Basic 6.0”(如图1),把这个应用命名为GoodByeVB6。

图1
  在深入.NET体系之前,我们需要了解一些有关.NET的边缘知识和术语。首先,IL(中间语言)并不是什么新概念,VB、C++编译器生成和利用IL已经有数年的历史,只不过很少有人公开谈论它或为它编写文档。与过去编译应用的方法相比,.NET最大的改变之一就在于编译器所生成的代码不同。除了名字之外,新的MSIL与VB6编译器的IL很少有类同之处。因此,如果你以前曾经接触过IL,现在还得从头开始学习。请参见图2,它是GoodByeVB6应用的MSIL代码片段:

图2
  这个代码片段设置了一个8字节的栈,然后把this指针压栈,调用get_Label1方法。接下来,代码把要设置的标签文本压入堆栈,然后调用setText方法。

  传统的CPU利用寄存器和栈完成所有工作。CLR所提供的执行引擎只有一个栈,它的操作过程非常类似于一个逆波兰表示法计算器。如果某个过程调用具有多个参数,执行引擎将在发出调用之前把参数压栈。函数调用的返回值也通过栈传递。

  MSIL中的局部变量很容易识别,它们用.locals关键词声明。如果符号存在的话,你将看到变量名字;否则,你看到的将是V_1、V_2之类的变量:

.locals init ([0] int32 x,
[1] int32 y,
[2] float64 z,
[3] class System.String Vb_t_string_0)

  ldarg指令把参数装入栈,ldc指令把数字常量装入栈,stloc指令把值保存到合适的局部变量:

//000064: Dim x As Integer = 100
IL_0001: ldc.i4.s 100
IL_0003: stloc.0

  在这个例子中,常量100被作为4字节整数压入栈,随后这个值被保存到第1个局部变量。关于MSIL指令的完整说明,请参见IL程序员参考的ILinstrset.doc文件。

  本文的所有MSIL输出都以GoodByeVB6应用的调试版本为基础。非调试版本虽然不带代码行和变量名字,但仍能够提供大量有用的信息。在查看MSIL代码的时候,调试符号虽然重要,但不是必不可少的。

  当我们运行编译器时,它生成的不是我们今天熟悉的执行文件,而是一个程序集(Assembly)。程序集是一个文件的集合,程序集中的文件可以作为单一整体进行部署。在当前的Windows体系中,我们可以把单个执行文件看成一个程序集。但从更严格的意义上来说,程序集聚合了执行文件和它的所有支持文件,包括DLL、图形、资源以及帮助文件。

  一般地,一个程序集至少由两个文件构成:执行部分,manifest(英文单词原意:载货清单,乘客名单)。manifest是程序集内所有文件的清单。程序集内的可执行部分又分开称为模块(Module)。从概念上说,模块对应着DLL或者EXE文件;除了父程序集所包含的元数据(Metadata)之外,每一个模块都包含元数据。程序集是当前可移植执行文件格式(Portable Executable,PE)的一个增强版本。

  如图3所示,文件的开头是标准的PE头。文件内部包含了CLR头,CLR头的后面是把代码装入进程空间所必需的描述数据——即元数据。元数据为执行引擎提供了大量信息,其中包括:如何装载模块,需要哪些支持文件,如何装载支持文件,如何与COM以及.NET运行时环境交互。另外,元数据还描述了模块或者程序集所包含的方法、接口以及类。元数据所提供的信息使得JIT编译器能够编译并运行模块。同时,元数据暴露了有关应用的大量内部信息,使得从反汇编IL获取有价值的代码更加方便。

图3
  使用.NET代码的核心问题在于受管理代码。受管理代码是专门为在CLR控制之下运行而编写的代码,它可以用VB.NET、C#以及C++等语言创建,但C++是唯一能够创建.NET平台非受管理代码的语言。我们无法用VB6为.NET平台创建非受管理代码,这是因为在VB6中我们把代码编译成i386指令而不是IL代码。正如使用VB.NET,如果你要使用受管理代码,你只能把代码编译成IL。

  现在我们来看看使用这种新的MSIL代码有哪些优点。如果代码编译成了MSIL,我们可以在任何支持CLR的平台上安装和运行这些代码。就目前来说,这一点可能不是很吸引人,因为当前支持.NET的平台还很少:只有32位的Windows。但不久之后,64位平台和.NET for Windows CE都将提供这方面的支持。把代码编译成MSIL格式使得我们能够无缝地把应用移植到所有这些平台和未来的新平台。

  MSIL的另外一个优点是:JIT编译器在安装应用的目标机器上把MSIL代码编译成机器指令,它能够利用目标机器的硬件特点,根据平台的具体情况对代码进行优化。这一点很有用,例如,它能够为目标机器的特殊寄存器优化代码,或为目标机器上带有特殊处理器的硬件设备优化操作代码。请点击VB6工程属性窗口Compile选项卡中Advanced Optimizations按钮了解更多信息。由于有了程序集中的元数据,JIT编译器知道代码做些什么以及它支持哪些平台,从而能够迅速地作出优化决定、提高代码的性能表现。

  还有一个优点涉及到.NET的两个V:Validation(检验),Verification(核查)。检验是对模块进行的一系列检查,确保元数据、MSIL代码以及文件格式的一致性。不能通过这些检查的代码可能导致执行引擎或者JIT编译器崩溃。一旦模块通过了检验,则代码是正确的且可以开始运行。

  JIT编译器把MSIL代码转换成机器代码时对代码进行核查,它是对元数据进行复查,保证程序不会访问它不具有相应许可的内存或其他资源。经过核查的代码是类型安全的(Type-Safe)代码。这种核查即使是在程序被直接编译成机器代码的时候也要进行,但除非由JIT编译器进行核查,否则这种核查不是100%精确无误,因为核查结果依赖于来自其他程序集的元数据。如果把源程序直接编译成机器代码,我们面临着这样一种危险:在目标机器上的其他程序集发生了变化,从而导致程序不再类型安全。

  使用JIT编译器保证了检验和核查是对所有相关程序集的当前版本进行。这些操作确保执行程序总是类型安全,程序总是以合适的安全许可运行。你可以用.NET SDK的PEVerify工具自己对代码进行检验和核查。

三、反向工程
 当程序集以MSIL而不是机器代码的形式发布时,最令人关心的问题应该就是安全。正如前面所介绍的,程序集包含了关于包里面所有模块的manifest以及详细描述各个模块的元数据。.NET SDK 提供了一个名为ILDASM的工具,它是一个IL反汇编程序,能够从模块反汇编出IL代码以及应用程序中各个模块的元数据说明。从Listing 1可以看出,利用ILDASM对代码实施反向工程是极为方便的。

【Listing 1】下面是IL反汇编程序ILDASM输出的部分结果。
它显示的是应用中一个名为LeavingMessage的私有方法,后
面部分是调用LeavingMessage方法的代码。CLR把参数压入
栈,执行调用,然后恢复栈为下一个操作做好准备。

.method private instance void LeavingMessage(class System.String& strText) il managed
{
// Code size 10 (0xa)
.maxstack 8
//000059: Private Sub LeavingMessage(ByRef strText As String)
IL_0000: nop
.line 60
//000060: debug.Write(strText)
IL_0001: ldarg.1
IL_0002: ldind.ref
IL_0003: call void [System]System.Diagnostics.Debug::
Write(class System.String)
.line 61
//000061: End Sub
IL_0008: nop
IL_0009: ret
} // end of method Form1::LeavingMessage

Code to call LeavingMessage Sub

finally
{
IL_002d: nop
.line 73
//000073: LeavingMessage("Goodbye Dear Friend")
IL_002e: ldarg.0
IL_002f: ldstr "Goodbye Dear Friend"
IL_0034: stloc.3
IL_0035: ldloca.s _Vb_t_string_0
IL_0037: callvirt instance void
GoodbyeVB6.Form1::LeavingMessage(class System.String&)
IL_003c: endfinally
.line 74
//000074: End Try
} // end handler

  人们已经认识到了这个问题,一个常见的反驳意见是:在现实中,应用的规模很大,IL反汇编输出结果的规模将超过可以忍受的限度。但是,它可能使一个业余爱好者望而却步,却不能阻止一个真正对代码感兴趣的人。实际情况是:与机器代码的反汇编结果相比,ILDASM的反汇编结果要容易阅读得多,任何对此感兴趣的组织都能够从IL反汇编结果了解到大量有关应用的信息。

  按照Microsoft的意见,要保证企业机密安全,我们应该把所有包含企业机密的模块放到受保护的服务器上。对于ASP.NET客户机/服务器应用来说这没问题,但对于标准的桌面应用来说它行不通。那么,如何才能对知识产权进行保护呢?MSIL汇编程序文档提到了一个命令行参数/owner:

ilasm ... /owner
ilasm ... /owner=fergus

  这个选项用密码加密代码,防止代码被反汇编。问题在于Microsoft准备取消这个选项,因为它看起来不是一种好方法。这样,对于用受管理的C++、C#或VB为.NET Beta 1编写的桌面应用来说,要保护知识产权将非常困难。

  但希望仍旧存在。在.NET最终发布之前,Microsoft可能提供一个模糊器(Obfuscator)程序,它能够修改MSIL的私有方法,使得除CLR JIT编译器之外没有人能够阅读这些私有方法。但是,它不会隐藏应用的公用(全局)方法以及对外部库的调用,这是因为:如果修改全局调用的名字或者隐藏这些调用,CLR将不能再链接到外部函数。因此,黑客们仍旧能够通过查看IL代码,找出应用调用系统DLL的各种信息。这样,现在我们只能用一种方法对桌面应用的知识产权进行保护,即用非受管理的C++编写关键性代码,然后从VB.NET通过为访问非受管理代码提供的交互机制访问它。当然,对于VB开发者来说,这可能比较困难。

  由于所有受管理代码必须以MSIL形式发布,所以在发布之前代码不能进行JIT编译。但是,在目标机器上安装应用的时候,我们可以把代码编译成汇编形式。从表面上看来这很不错,但代码在安装盘上仍旧是IL形式,我们可以手工从安装盘提取出代码,然而分别对它们进行反汇编。由于应用安装完成后以编译代码而不是IL的形式存在,除了安全之外,它还能够少量地提高应用运行的速度,因为此时我们不再需要JIT编译器编译IL代码。

四、结束语
  如果你是一个桌面应用的供应商,你清楚自己应该怎么做。你可以用非受管理的C++编写代码,然后从受管理的VB调用它。用这种方法设计应用,你能够确信代码的安全。然而,如果你是一个第三方供应商,而且准备在组件中用非受管理的代码替代受管理的代码,那么,你是在强迫用户放弃.NET的优势,重新让他们面对他们今天所面临的问题。受管理代码能够防止对应用本身或者其他应用所使用的内存空间进行破坏性操作,对受管理代码的支持正是.NET吸引人的原因之一。某些用户可能会查看受管理代码的IL程序,甚至还有可能分析应用的算法实现,如果不能正确地认识.NET的优势所在,第三方供应商可能会为了防止用户分析代码而拒绝用受管理代码编写各种软件部件。

  VB.NET/VS.NET有着许多优点,仅仅是对IDE(集成开发环境)的改进就足以成为我们升级到VB.NET的理由;语言方面的增强为我们带来许多新的编程支持,对底层OS访问的简化使得我们声明变量、对象以及调用低层功能更加方便。VB.NET是一个创建安全ASP.NET应用的优秀工具;但是,如果你的主要目标集中在客户端或者是桌面应用,你应该慎重考虑可能出现的问题。Microsoft准备为桌面应用开发者提供哪些帮助?我们将拭目以待。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: