你的代码安全吗?
(青苹果工作室2001年06月13日 01:10)
在VS.NET环境里使用Microsoft 中间语言为开发者创造了极大的优越性,但是却将VB.NET暴露在了桌面上。
在Visual Studio.NET 框架中,象 VB、Visual C++和C# 那样的编译器将源程序编译成Microsoft 中间语言 (MSIL),在执行之前这种中间语言被JIT(Just-In-Time)编译器进一步编译成为机器语言。但是,你可能还不完全了解当点击Build按钮时,在VS.NET中发生了什么?或者,当你将向客户端装载IL代码时,私用代码和信息是否安全?下面就进入.NET 框架工作的内部来向你展示有哪些新内容,并解释当你在VB.NET下使用MSIL时应该注意哪些问题。
首先应该清楚几点。第一,.NET是为客户/服务器和Web应用程序而设计的。软件已经转移到Internet领域并成为基于客户/服务器的应用程序,因此许多应用程序现在看起来都更象是浏览器,而不是旧款式的界面了。.NET 就顺应了这种潮流。
还要注意,如果你想要保护你的知识产权的话,.NET就不适用于桌面应用程序,因为你不能在桌面上保护以MSIL形式管理的代码。这很令人震惊!虽然 MSIL的目标很好,并且.NET框架和公共语言运行时间环境(CLR)也都很稳定,但是从安全的角度来说,在一个独立的桌面应用程序中它们是不实用的或不能工作的。作为一个 VB程序员,甚至一个C#程序员,在.NET 中,除了这种不受保护的可管理代码外,你没办法写出其它东西来。
由于这种局限性,如果你想保护桌面应用程序中的代码的话,就必须要写无管理的C++代码。唯一能够保护知识产权的方法是:将代码包装在无管理的C++组件中,在.NET可管理代码中使用 COM共用界面去调用它。不幸的是,对于VB或 C#程序员来说,这种选择是不奏效的。
你还应该知道,Active Server Pages.NET (ASP.NET)应用程序是安全的,因为它们完全在服务器上运行。这是.NET的天堂:运行中的代码被藏在一个受到保护的服务器上,没有办法能弄到代码。ASP.NET使得Web的开发变得出人意料得简单,而Visual Basic.NET (VB.NET)则是编写ASP.NET应用程序的好工具。
对VB.NET的学习会变得很容易上手,但是.NET作为一个整体则会经历一个缓慢的接受过程。从VB6 过渡到VB.NET并不容易,在此之前,你还需要支持VB6应用程序。
MSIL是旧的,也是新的
看看当在VB.NET中建立一个工程文件时会发生什么:创建一个样本工程文件以生成代码和汇编。打开VS.NET,创建一个新的Visual Basic 工程文件,向表单中增加一个 Label(标签)控件,将其Text属性改为"Good ByeVisual Basic 6.0 " :
注意:你将编写一个GoodBye VB6应用程序,而不是标准的 Hello World应用程序。
进入.NET之前,你需要了解一些规则和术语。首先,IL的概念并不新鲜。VB 和 C++ 编译器已经生成IL多年了,但是没人公开讨论过,也没有人将其记录在案。与你过去装运应用程序的方法唯一的不同就在于编译器生成的代码。除了名字以外,新的MSIL与VB6编译器的IL之间几乎没有什么相同点。因此,如果你过去已经使用过 IL ,那么要作好一种全新体验的准备。请看GoodByeVB6 应用程序中生成的一个MSIL片段:
这个代码设置了一个8个字节的堆栈,然后将this指针推入堆栈,并调用 get_Label1方法。接着将需要的标签文本推入堆栈并调用 set_Text 方法。
当运行编译器时,生成了一个并非我们今天了解的真正可执行程序。它被叫做一个汇编assembly (请参阅术语表)。一个汇编是被配置为一个单一文件的一组文件。今天,我们可以把一个可执行文件作为一个汇编。更准确地说,一个汇编将可执行文件和所有支持的DLL、图象、资源或帮助文件都组合在了一起。
通常,一个汇编都至少包含两个文件:可执行文件和清单。清单汇编中所有文件的一个列表。汇编中的可执行内容是单独作为一个模块引用的。从概念上说,模块对应于DLLs 或 EXEs,每个模块都包含元数据,另外还有其母汇编的元数据。汇编的格式是当前便携执行文件(PE:Portable Executable)格式的增强版本。
标准的 PE报头在文件的开始。在文件内部是CLR报头,后面跟着的是将代码装载到其程序空间所需要的数据,这里指的就是元数据:
元数据向执行引擎描述如何装载模块、它还需要其它什么文件、如何装载那些额外的文件以及如何与COM和 .NET 运行时间相互作用。元数据还描述模块或汇编中包含的方法、界面和类。元数据所提供的信息允许JIT编译器编译和运行模块。元数据部分暴露了大量应用程序内部的东西,使从分解的IL向有用代码的转化变得简单。
配置.NET代码的核心是有关可管理代码的问题:就是指完全为了在CLR控制下运行而编写的代码。你可以从VB.NET、C#或 C++创建可管理代码,但是C++ 是唯一能够在 .NET 平台上创建无管理代码的语言。不能用VB6为 .NET 平台创建无管理代码,因为你用VB6装运的是组合了i386指令而不是IL 的代码。当你使用可管理代码时,除了IL以外什么也不能装运,就同 VB.NET一样。
现在来看看使用新MSIL代码有哪些好处。当把代码停留在 MSIL阶段时,你可以在任何支持CLR的平台上安装和运行它。现在这对你来说可能意义不太大,因为目前支持 .NET的平台还很少:只有 32位 Windows。但是很快就会增加,其中包括Windows CE 设备(袖珍PC)的64位平台和.NET。今后,将代码保留为MSIL就可以使你能够在这些平台及其它新平台之间自如地转换。
MSIL的另一个优势是:JIT编译器将 MSIL转换成目标机器的本地代码。因此,JIT编译器可以利用特定的硬件,并为那个特定的平台优化代码。这来得很方便,例如,当优化某些寄存器的代码或某些有特殊处理器硬件的 op-代码时。请看看在VB6的工程文件属性中编译标签上的高级选项按钮。在汇编中使用元数据,JIT编译器将会知道代码做什么以及平台支持什么,这样就可以随时作出最优化的选择决定,从而增强代码的性能。
MSIL的另一个优势是:确认和校验。 确认是你在模块上实施的一系列测试,从而确保元数据、MSIL 代码和文件格式的一致性。不能通过这种测试的代码可能会与执行引擎或JIT编译器冲突。一旦确认了模块,代码就是正确的并且准备运行。
当JIT编译器将代码从MSIL转换到本地代码时,就对代码进行校验。校验包括检查元数据,以便确定程序不能对它没有权限的内存或其它资源进行使用。经过了校验的代码也是安全的。即便程序是直接从本地代码编译过来的也要进行检查,但是只有JIT编译器进行这个检查才能达到100%的准确,因为测试的结果要依赖于来自其它汇编的元数据。如果在装运之前就编译成本地代码的话,你就面临着在目标机器上进行另一次汇编修改的风险,这会使你的程序发生“类型不安全”错误。
使用JIT 编译器保证了在进行确认和校验时,将所有相关汇编的当前版本都考虑在内。这个过程确保了运行程序是类型安全的,并且它是在具有正确的安全许可的情况下运行的。你可以用.NET SDK 的PEVerify工具来对代码进行确认和校验。
反转工程很简单
也许将汇编作为MSIL而非经过编译的代码进行装运的唯一担心就在于安全性。要记住,汇编含有包装中所有模块的清单,元数据详细描述了各个模块。.NET SDK装运了一个名为ILDASM的程序,这是一个IL反汇编器,它获取模块信息并将格式化好的IL代码和所有应用程序模块的元数据描述都堆积起来。因此,反转代码变得不能再简单了:
.method private instance void LeavingMessage(class System.String& strText) il managed
{
// Code size 10 (0xa)
.maxstack 8
//000059: Private Sub LeavingMessage(ByRefstrText 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#或.NET beta 1的VB编写的桌面应用程序中,你是不能保护知识产权的。
但是还有希望。在最后的.NET侯选版本发布之前,Microsoft有可能还要引入一个obfuscator,它可以改变 MSIL的私用方法,从而使它对于除了CLR JIT编译器之外的任何人都不可读。但是,这并不能隐藏应用程序的公用方法或调用,必须要使用一个外部库。修改这些公用调用的名字或隐藏它们将使得CLR对外部函数的连接成为不可能。因此,当黑客在你的IL代码深处进行挖掘时,还是能发现你调用系统DLL时的一些蛛丝马迹。
现在,你只能用一种方法在一个桌面应用程序中保护知识产权。作为一个VB开发人员,你可能会觉得这很困难,但是你必须要在无管理的C++ 中编写你的重要代码,使用专为存取无管理的代码而提供的互用机制,并从你的VB.NET 应用程序中存取它。
在装运之前,你不能使用JIT编译代码,因为所有可管理代码都必须作为MSIL装运。但是当在目标机器上安装它时,可以将代码编译成汇编形式。最初这听起来很好。在安装盘上的代码仍然是IL,因此你可以手工从设置文件中将其提取出来,并分别从安装中将其反汇编。一旦图象被安装到用户的硬盘上,它就是汇编形式而非IL。除了安全性以外,这会使应用程序的运行速度稍有增加,因为这样一来,JIT 编译器就不需要编译IL代码了。
第三方厂商也许会担忧
作为一个桌面应用程序的销售商,你知道你必须要做什么。你可以编写无管理的C++,然后从有管理的 VB代码中使用它。你设计应用程序,并且觉得它是安全的,因为你知道这个代码是正确的,不会破坏它不该破坏的程序空间。虽然可管理代码被保证会管好它自己,无管理代码也稳定并可靠,但是,如果你是一个第3方销售商,在组件中选择了无管理代码而非可管理代码,你就会迫使消费者从 .NET的优势中倒退回去,并且使它们又面临着跟现在一样的问题。.NET的某些美好之处就是其编写可管理代码的能力,并且知道那样做不会破坏应用程序和其它应用程序所使用的那些内存。但是如果不知道的话,销售商就应该避免在.NET的可管理代码中编写控件,以限制客户对IL的访问,从而也就潜在地限制了对应用程序使用的算法的访问。
我爱VS.NET,尤其爱VB.NET。IDE对它们自身的加强提供了迁移到VB.NET的充足理由。语言的加强增加了编程效率,对潜在OS的简单访问又使Declare更容易。VB.NET 成为创建安全ASP.NET应用程序的一个很好工具。