许多厂商选择隐藏他们的代码,以试图防止软件机密落入黑客之手。但是隐藏这些信息并不总象看上去那么容易,即使成功隐藏了信息,黑客也还有许多其它方法找到安全性弱点。
企业技术部门十分注重保密:不公布设计文档、将代码视为商业机密、有时算法本身也保密。软件经常成为防止攻击者和竞争对手触及机密的机制;由于采用方法的不同而造成许多差异也就不足为奇了。在本文中,我们将讨论试图使用软件来保密所涉及的问题。
保密有充足的理由。每家公司都有要保护的知识产权,通常包括完全构建到正销售给客户的软件中的算法。公司还有密钥,密钥必须保持专用以便保持其效用。尽管目前流行的趋势是趋于开放(包括开放源码运动),但对于自己的计算机程序,许多公司仍然信奉保密的原则。问题在于,保密通常作为辅助手段使用,其效率可能不高。
也许最流行的保护代码中机密的方法是隐藏源代码,并且只发行机器代码形式的可执行版本。不发行源代码确实可以防止黑客窃取您的机密。然而,这样做远不如许多人相信的那么高效。这种技术(通常被称为“通过模糊实现安全性”)有许多问题,但是主要的问题来自于人们错误的认识:只是因为源代码无法访问,就认为将代码编译成二进制可以保持机密。这是错误的。简单来说,只要您的代码运行,下定决心的人最终就可以准确地发现它正在做什么。
观察软件行为
黑客不总是需要研究任何代码(二进制或源代码)才能找到安全性弱点。通常对程序行为的受控观察就足够了。最糟糕的情况是,可以在正常使用的过程中注意到安全性问题的症状。
例如,考虑一下 1999 年底在 Netscape Communicator 的安全性中发现的问题。这个特殊问题影响了那些选择使用邮件客户机保存其 POP 邮件密码的 Netscape 邮件用户。在本例中,事实证明,放置在 Netscape 中的一个方便用户的“特性”引入了一个很大的安全性问题。
显然,保存一个用户的邮件密码意味着在某个地方永久存储它。问题在于,在哪里以及如何存储该信息?软件安全性攻击者总是随时在问这种问题。
显然,Netscape 的程序员需要确保临时用户(包括攻击者)不能直接从磁盘读取密码,同时又要向必须在 POP 协议中使用密码的程序提供对密码的访问。为解决该问题,Netscape 的开发人员尝试在存储密码之前对它加密,使之不可读(无论如何,至少是理论上不可读)。程序员们选择了一种在他们看来足够好的“密码加密算法”,认为这样别人就无法得到源代码了。(当然,最终以 Mozilla 形式得到大部分源代码,但我们在这里讨论的密码存储代码没有在这一发行版中出现)。不幸的是,Communicator 所采用的算法有严重缺陷。
事实表明,在 Windows 机器上“加密”的密码存储在注册表中。这样不好。对 Windows 程序员的一个相关软件安全性提示是,总是假设别人可以阅读您放置到注册表中的每一项!如果您选择在那里存储信息,请确保它得到功效强大的密码术的良好保护。
当对一个被设计成用异或方法来查找隐藏的密码(也就是说,密码用一种 1 和 0 的简单模式进行异或运算)的简单程序进行试验时,Cigital 的软件安全性专家注意到,存储在注册表中的 Netscape 邮件密码的类似明文的各个版本在加密格式下看上去彼此类似。这种情况通常是糟糕密码术的一个征兆。使用好的密码术,对密码的一个字符的更改至少要影响加密版本中一半的位。在本例中,没有观察到这种更改。
从这一点开始,对数百个测试密码的有计划的更改暴露了加密版本更改方法的模式。通过仔细研究模式,最终收集了足够的信息来构建(或者进一步,重构)一个和 Netscape Communicator 加密例程以完全相同方式执行的算法。完成所有这些都不用看任何实际代码。
给定一个加密算法副本,很容易写出一个对 Netscape POP 实际密码解密的小程序。该产品中使用的加密算法很差劲,它是内部开发的,并且显然没有得到密码术专家的帮助。与任何能够得到真正的密码术专家认可的算法相比,Communicator 算法是可笑的。
在本例中,也许 Netscape 的人员觉得他们的算法已经足够好了,因为它永不发布并因此“机密”。也就是说,他们依赖于通过模糊实现安全性!但是对于密码术,这是个坏主意。好的密码算法即使在其他人确切知道它们做什么的情况下,也能保持良好,而坏的算法即使从未直接公布,也将被攻破。所有这些都归结为数学。
考虑一下同盟国在二战中制造的 Enigma 机器。包括 Alan Turing 在内的密码术专家,仅仅通过仔细观察编码消息,就计算出关于德国密码算法的的一切。直到完全攻破代码之后,同盟国才看到一台真实的 Enigma 机器(而且,那时他们已经不需要了)。
即使是以二战时期的标准来判断算得上是良好的算法,今天看来也是很糟糕的。实际上,曾经被那些攻破德国 Enigma 机的人看成是不可攻破的密码,如今也可以使用现代密码分析技术轻易攻破。最终,只有真正的密码术专家才有可能设计出相当好的算法。负责 Netscape 算法的人不是优秀的密码术专家。问题在于为什么 Netscape 不选择使用一种象 DES 或 Blowfish 那样“真正”的加密算法。
实际上一个非常类似的 Netscape POP 密码加密算法,在 Cigital 重新发现该问题之前一年多就被攻破了。有人明确地向 Netscape 指出了原始算法中的错误,并且在 Cigital 测试过的浏览器版本中,Netscape“修复”了该问题。修复本应该包括对真正算法的使用。但是,Netscape 选择对他们存在根本性缺陷的算法进行肤浅的更改,只是使它略微复杂了一些。但是,Netscape 不应该再次依赖于通过模糊实现安全性;而是应该求助于专业密码术专家。
我们故事的寓意很简单:无论何时,当某些恶意的黑客在使用您的软件时发现任何异常,他们将感到好奇并开始研究。关于弱点存在的任何线索都已经足够了。例如,一次简单的程序崩溃经常可能成为一个可利用弱点的标志。当然,黑客不必被动地观察。对于攻破您的软件感兴趣的人将使用一些您可能未曾预料的输入来刺探它,希望发现表明某种安全性问题的行为。
一种常见的方法是,在任何可以接受输入的地方,都提供一个很长的输入。这种测试经常导致程序崩溃。如果崩溃以一种特别明确的方式影响机器,黑客可能发现一个可利用的缓冲区溢出情况(请参阅参考资料)。通常,攻击者不必直接看您的软件来知道问题的存在。Dr. Watson 的输出就足够了(单击 Windows 98 程序崩溃对话框底部的“More Details”按钮也足够了)。攻击者可以聪明地从那里构造用于您程序的输入,来收集更多信息,最终产生漏洞检测 — 不用看您的代码一眼,就可以完成所有这些!
严重的安全性问题始终是以这种方式发现的。例如,最近 Microsoft Outlook 中的一个缓冲区溢出就是以这种方式发现的,eEye 用相同技术在 Microsoft 的 IIS Web 服务器中发现了一些错误。可以从这里吸取的经验是,在编写代码的时候总要关注安全性,即使您不打算向任何人展示代码。攻击者经常能够只是通过观察代码的行为就推断出它是做什么的。
逆向工程
许多人认为编译成二进制形式的代码得到了足够好的保护,得以免遭攻击者和竞争对手的攻击。这完全是不对的。尽管潜在的坏家伙可能无法访问原始源代码,但实际上,他们确实能获得一切必要的信息,来进行一次复杂的分析。
有些黑客能够阅读机器代码,但即使那也不是必需的的技能。很容易获得并使用逆向工程工具,将标准机器代码转换成更容易理解的代码,譬如汇编代码或者很多情况下是 C 源代码。
反汇编程序是一种很成熟的技术。实际上,大多数调试环境都具有优秀的反汇编程序。反汇编程序读取机器代码并将它转换成等价的汇编代码。确实,汇编丢失了许多使 C 代码容易阅读的高级信息。例如,在汇编中,通常将循环构造转换成计数器和跳转语句 — 和原始代码在可理解方面相去甚远的构造。尽管如此,还是有少数优秀的黑客能够象 C 程序员阅读和理解 C 代码一样阅读和理解汇编。对于大多数人来说,理解汇编是一个比理解 C 缓慢得多的过程,但这只不过是个时间问题罢了。关于您的程序做什么的所有信息都在那里等着潜在的攻击者去看。那意味着只要投入足够的精力,就能够揭示您试图在代码中隐藏的任何机密。
反编译程序甚至比反汇编程序更能帮攻击者的忙,因为它们被设计成将机器代码直接转换成某种高级语言形式的代码,譬如 C 或 Java 语言。但是,反编译程序技术还不象反汇编程序技术那么成熟。它们通常有用,但是有时不能将构造转换到高级语言代码,尤其是在机器代码是以汇编语言而不是某种高级语言手工编写时。
用于高级机器的机器代码经过逆向工程工具处理以后,很可能变得易于理解。例如,我们经常可以使用逆向工程工具来将运行在 Java 虚拟机上的程序恢复成某种非常类似于其原始源代码的代码,因为在从 Java 源代码编译成程序的过程中只丢弃了很少信息。相反,通过反编译程序产生的 C 程序看上去通常和原始代码不同,因为编译过程往往丢弃很多信息。如果程序员启用调试选项来编译,反编译性能可以提高。
通常,您在编码时总是应该注意安全,设想攻击者将使用欺骗手段访问源代码。请总是牢记:通常,研究二进制代码完全相当于掌握着源代码。
在未来的专栏文章中,我们将花费更多时间研究逆向工程工具。
代码模糊
您时常无法避免在代码中放置一些机密。例如,考虑 Netscape POP 密码的示例。即使 Netscape 使用了象 DES 那样“真正”的加密算法,还是需要采用密钥来实现加密和解密。这种密钥必须保持绝对机密,否则密码的安全性可能轻易泄露。将密钥移至磁盘上的某些文件中是不够的,因为代码还是保持了一个机密 — 密钥的位置,而不是密钥本身。
在这些情况下,没有绝对安全的方法可以保护您的机密,防止泄露给能够访问二进制代码的人。当您开发客户机软件时,请牢记这一点:攻击者能够读取客户机并随心所欲地修改它!如果您可以使用客户机-服务器体系结构,请尝试将机密卸装到服务器上,在那里可以更好地保护它们的安全。
但是,这有时不够灵活。Netscape 不愿意将 POP 密码存储到某些中央服务器(并且如果他们决定这样做,人们可能会觉得很失望,因为这种转移可以轻易地解释为对隐私的侵犯)。
在这些情况下,程序员所能尽力的也就是将(安全方面的)门槛尽量提高。通用的思想,称为代码模糊,就是以某种方式转换代码,使它对于攻击者变得难以阅读和理解。有时模糊可以对付逆向工程工具(通常是反编译程序,但很少是反汇编程序)。
模糊的一种常见形式是以任意的名称重新命名代码中的所有变量。但是,这种模糊不太有效;事实证明它不能将对付攻击者的门槛提得很高。代码模糊是一个相对未知的领域。标识程序转换时并不做很多工作。大多数已标识的转换都不太好 — 也就是说,它们没有制造太多障碍,或者极易撤消。少数现有的模糊转换可以显著地提高门槛,但是通常需要大量应用它们。不幸的是,目前可用于自动代码模糊的工具不执行这类转换。相反,它们只执行简单的模糊,熟练的攻击者可以轻易克服。
我们将在未来的专栏文章中讨论代码模糊的技术发展水平。
COTS 安全性
如果您在自己的产品中使用商业现货(COTS)软件,您可能无法访问源代码,并应该注意我们在这里讨论的问题。有些人愿意相信 COTS 软件是安全的,因为没人能访问源代码,或者因为供应商说它是安全的。但是这两种论据都不严密。当您的设计需要使用 COTS 时请牢记这些风险。
结束语
不提供您应用程序的源代码是正确的。这里要说的是不要让别人可以过于轻易地更改专用程序,或者窃取您的算法。从直接的安全性角度来看,源代码可用性有时会很重要,尤其是有人研究您的代码的弱点,并报告他们所发现的一切的时候更是如此。然而,就和通过模糊实现安全性是错误的一样(主要是“既然它已经编译,就可以保密了”),通过源代码可用性实现安全性(或者通过开放源码实现安全性)也是错误的。尤其是,即使源代码是可用的,我们发现用户也往往不寻找安全性弱点。如果您的程序具有任何商业性质,则用户发现安全性问题的几率更低。
大体上,您是否提供源代码的决定应该基于业务因素,而非安全性考虑。当涉及安全性时,不要用源代码的可用与否,或不提供源代码原谅自己,认为自己已经充分努力了。
|