SUMTEC -- There's a thing in my bloglet.

But it's not only one. It's many. It's the same as other things but it exactly likes nothing else...

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

上面一节里面讲到一些总体的东西,比如CLI包括什么,CIL是什么样的东西等等。这一次我准备讲一些完全不同的内容——强命名、元数据以及文件结构。

其实这次要将的东西主要就是围绕着强命名讲的,因为最近有一些需要,所以对强命名在可执行文件里面的位置以及怎么程序化的产生强命名进行了一些比较深入的研究。整个研究过程其实是比较痛苦的,虽然资料齐全,但是一方面是资料太多了需要慢慢整理,另一方面资料里面有些不正确的地方需要试验判断。这么痛苦的一个过程当然不希望有人再来一遍,所以就公开整个的研究过程,希望能够对有需要的人提供一定的帮助。

好了,什么是强命名——StrongName呢?嗯,这个问题我一开始也非常的不清楚,甚至非常不想搞清楚。不可否认,MS的文档自从7.0以来已经改善了很多了(其实我觉得VB的文档一直都比较清晰的,只是不知道为什么VC的文档一直都那么糟糕,我几乎就没有一次能够直接找到我想要的东西),但是有些文档确实依然比较糟糕,也不知道是不是MS故意的。就比如说强命名的文档,在7.0的时候关于他的介绍可以说有点比较随意,虽然我知道他和安全有关,也大概知道是某种加密方式,可是这个东西有什么实际的用处,还是到MSDN上面看别人的文章才逐渐有所了解的。
首先说一下什么是强命名。强命名就是通过某种方式,使得运行环境或者任何个人可以验证某个可执行文件是否完整并且没有被修改过,并且这种验证方式是可以信赖的。如果我们确实可以做到可信赖的验证,那可以给我们带来哪些好处呢?很明显的,第一条就是别人不能够随意修改我的程序,比如把一个许可验证的代码修改成不需要验证,这样可以在一定程度上面防止盗版。第二条是,别人不能够冒充我的程序,因为冒充的程序虽然能够通过验证,但是却不太可能和我的程序有同样的“指纹”,这个问题稍后在讨论。做到了这一点意味着某种木马程序比较容易被识别出来。还有第三条,就是能够唯一的标识你的程序集,即使别人有和你一样的文件名,甚至连命名空间或者类名称等有部分或者全部相同(也就是冲突),也不会让CLR误解为这两者是同一个东西。

乍一看来,第一条和第二条很吸引人,第三条却没有什么作用。其实第三条的作用是非常大的,而且甚至更经常被是用到。很多时候,很多人都会创建了相同的东西,比如说CoolBar。现在假设Sumtec写了一个CoolBar,Ninputer也写了一个CoolBar,然后Vincent用Sumtec的CoolBar写了一个程序A,pine ant用Ninputer的CoolBar也写了一个程序B。这个时候恰好IceSharK同时需要A和B,结果都装完之后发现,不是A用不了就是B用不了。当然,现在的COM+不太可能出现这种问题,但是想对于StrongName来说,其实是相当的脆弱的。这个问题也需要到后面再做出解释。

嗯,那么StrongName是怎么保证上面的三条呢?我们先来学习一下应用密码学的相关内容。呵呵,不要还怕,其实不难。


Bruce Schneier的这本书非常值得一看,传闻当初第一版的时候让NSA非常头痛,甚至差点被列为禁书。最后被列为“军需品”,因为这本书的出口需要通过军需品控制委员会的申请。申请的结果是,书本身可以出口,但是磁盘去不能够出口。现在我们看到的这一本书是第二版(中文,翻译的不错),增加了不少内容,也修正了一些错误,同时附赠了软盘。这个软盘我不知道现在有没有,我买的时候好像是没有的,因为被禁止出口到中国,当时带128位加密的操作系统都是被禁止在中国出售的,所以才有了后来的128位加密补丁(IE6和Win系列),还记得吗?所以这张盘应该是能够火取得了,如果你买的书里面不附带,也很正常,不过应该也可以向本书的作者或者USENET新闻组请求获取。

其实如果你看过这本书,你就会知道:哦,原来什么密码算法还不是最重要的,最重要的是协议。呵呵,协议的东西咱们暂不讨论。先说说加密算法本身的知识。

  1. 加密算法可以完成那些任务?
    1. 加密/解密。也就是间谍电影里面最经常见到的镜头:我不想让别人知道我们两个之间在说什么,所以我把说的话用密语的方式发出,对方用相应的密码本解开。
    2. 签名。就是说我能够证明这是我的东西,而你却不能
    3. 交换。包括怎么安全的交换密钥,或者是其他信息。一般来说前者的应用比较多,而这个过程和签名正好是相反的操作方式。
    4. 验证。就是证明这个东西原来就是这样的,没有被任何人修改过。签名能够达到验证的目的,但是还有一些不能用来签名的方法却可以用来验证,比如说CRC32是一种,虽然是毫无安全可言的。
  2. 加密算法分那几个大类?机制是什么?
    1. 对称加密算法。这类的算法加密和解密都用同一个钥匙,就和我们普通的门锁和钥匙一样。通常用于双方都处于安全并且可信的状态下进行信息交换的场合,例如两个间谍需要互通信息,却担心别人知道。这种算法有一个问题:假如其中一个人被CIA抓了,供出了密钥,同时CIA截获了所有加密过的文本并保存下来了,那么就算这个被抓的声称自己忘了发送过什么内容,那样也没有任何关系,只要用供出来的密钥对截获的文本进行解密就OK了。
    2. 非对称加密算法。这类算法需要两把钥匙,一把用于加密,另一把用于解密。这类算法主要是用于弥补对称算法的不足的。假设还是前面那个情况,但是用的是非对称的算法。那么就算被抓的供出了自己的两把钥匙以及对方给他的一把用于加密的钥匙,CIA还是无法通过这些钥匙把这个人发送出去的信息还原。事实上非对称算法一般都比较慢,所以主要用这个方法交换对称加密算法的密钥(这个密钥就可以每次都不一样而且是一个随机数)。实际上非对称算法的这个特点,还经常被倒过来用,比如说签名。
    3. 哈希散列算法。这类算法一般只有一把钥匙,同时加密过之后的数值通常远远短于原文,并且不可恢复。这种算法的不可恢复性注定了他不能够被用来交换信息,但是却可以简便快速的进行验证。
  3. StrongName又是什么?他是如何工作的?

好,终于进入正题了。在VS.NET 2003\SDK\v1.1\bin里面有一个用来“制作”强命名的工具sn.exe,用/?运行这个程序,就会看到很多的“签名”。这个强命名实际上就是一种签名协议,这个协议包含了两种加密应用、两种加密算法以及若干步骤。下面就开始这个强命名是如何运作的。

首先,需要产生一对RSA的密钥,一个自己使用的叫做私钥private key(简称kd),另一个是公开的叫做公钥public key(简称ke)。然后根据ke产生SHA的密钥ks,接下来计算:
hsh = SHA(CLI, ks)
上面的CLI指的是可执行文件中的CLI部分(不包括DOS头、PE头等),hsh是用SHA算法和ks计算出的文件散列值。然后就开始进行签名:
sn = RSA(hsh, kd)
也就是说,强命名sn实际上是用私钥kd,把文件的散列值加密得到的密文。除了这个sn会被记录在这个被强命名的可执行文件当中之外,还有以下两样东西被记录在同一个文件中:
token = last8bytesOf(hke
), ke   其中 hke= SHA(ke, ks)
前面一个是公钥的散列值的最后八个字节,后者就是公钥。需要把公钥放在文件当中很好理解:因为验证的时候需要用公钥将sn解开,然后和文件当前计算出来的hsh进行比较。那么前者有什么用呢?有两点作用:官方的说法是用来简化验证过程,因为ke不同则token必然不一样,很多时候只需要证明token不一样就能够证明两者是不一样的;但是我觉得这里面还有一层作用,就是验证ke本身。

到这里,制作强命名的过称就结束了,这个时候文件里面会包含三样东西:sn、token、ke?。现在我们看看如何用这三样东西证明那个CoolBar是我Sumtec的,并且没有被修改过的:
首先,
ke本身就代表着Sumtec,因为这个东西至少有1024bits长,要想产生一样的公钥几乎是不可能的。通过ke我们同样能够获得原来的ks,接着就可以计算:
hsh1 = SHA(CLI, ks)
hsh2 = RSA(sn, ke)
比较hsh1hsh2,如果相同就可以证明没有被修改过,不同就肯定被修改过了。为什么这么说呢?现在假设Ninputer是黑客,想要把我的CoolBar里面的版权信息和注册页面去掉,于是他不得不修改SUMTEC的软件。一旦文件被修改了,那么hsh1* = SHA(CLI*, ks)就不可能等于hsh1 ,与是.NET CLR的验证就不会通过,系统直接抛出一个异常而根本就不运行这个修改过的程序。这个时候Ninputer也许就在想,可以修改一些无关紧要的数据比如一个内嵌的图片中的几个字节使得hsh1** = SHA(CLI**, ks)和hsh1 相等。问题是SHA算法本身根本就没有办法让你分析出到底修改那几个字节成什么才能够达到这个效果,如果通过暴力破解时间上的花费将会非常的大。如果要让.NET CLR允许运行这个程序,则必须让hsh1hsh2相等,使得hsh1*保持不变似乎是不太可能了,那可不可能让hsh2*变得和hsh1*呢?可是问题是Ninputer并不知道kd,因此也就不可能用sn* = RSA(hsh*, kd)这个公式伪造签名。希望通过尝试计算hsh2* = RSA(sn*, ke)并使得hsh2* = hsh1*的方法进行暴力破解是徒劳的,sn的长度是1024bits即使你的计算机能够一秒钟计算264hsh2* = RSA(sn*, ke),平均也需要
5.6396493121527772455748740670574e+283  天的时间去计算,也就是大约
1.5451094005898019850890065937144e+281  年……最后Ninputer不得不放弃这种荒唐的想法。

那么StrongName是不是就没有漏洞呢?不是,只是这个漏洞相对来说并不是很大。什么漏洞呢?Ninputer想了想,干脆我连kd/ke也伪造好了。不过即使是这个伪造也并不是那么容易的,至少你如果想用伪造的ke*重新签名那绝对是不可能成功的,因为重新签名本身不包括token(好像是的,这些都是我的估计,没有仔细差过资料),新的ke*即使写到里面token仍然是原来的,那么关于ke的验证就不能通过,程序照样启动不了。具体应该怎么伪造,我就不说了,因为这个可能会威胁到我们公司的产品。不过就算被你伪造成功了,那么整个程序的ketoken必然和原来的完全不一样了,如果我们想要判断这个东西是否是伪造的是一个很简单的事情。此外,被伪造了的exe和dll在StrongName上面是完全不同的,那么就必然导致不能够接受升级服务——因为升级包所包含的exe或者dll在StrongName上面和Ninputer破掉的不一样。

让我们回过头来看一下以前的“安全措施”。如果大家是从DOS时代过来的,并且稍微看过MZ文件格式,就知道有一个叫做CheckSum的字段,甚至在PE Optional里面也有这个字段(和MZ里面的互相独立)。可是说我们以前的校验措施基本上就是用的CheckSum这个思路,这个思路有什么问题呢?可是说CheckSum只能够防止自然灾害的破坏——比如硬盘上面的小磁极受到附近的音箱影响改变了一位,或者软盘光盘表面划伤某一位读不出来了。但是如果面对有预谋的修改,CheckSum就像从来就不存在一样了。因为CheckSum本身的计算不依赖于任何的密钥,并且一般来说整个运算过程是可推导的。这样Ninputer完全可以推导一下,看看修改什么无关数据保证整个CheckSum在修改之后不会改变,或者偷懒一点就去找找这个CheckSum算法自己算一遍。对于前者,你根本就不可能证明这个程序是否被修改过,除非你有这个程序的整个副本——着对于人来说办得到,但是对于程序来说就很不现实了,因为这个副本最终会被破解的人发现并且修改成和被破解的一模一样,类似的办法都是行不通的。对于后者,实际上仍然是无能为力的,因为一个程序总需要升级打补丁,这样的话每个版本的CheckSum都不一样,如何证明这个CheckSum改变是正常的补丁造成的还是Ninputer修改出来的呢?所以现在PE里面的CheckSum根本就是一个摆设,只要你在这个字段里面填写0000就能够通过操作系统验证。微软的文档里面关于PE里面的CheckSum,根本就没有提是怎么算出来的,文档说只有核心组建或者系统组建才会使用这个字段,实际上我怀疑用了和没用根本就没有任何区别,而这个算法实际上也不是什么秘密了,无论用imghelper.dll这个工具,还是自己写写都能够制造(伪造)CheckSum。

说了半天,不知道你有没有打算在产品当中使用StrongName呢?甚至有没有打算利用StrongName去防止盗版呢?敬请关注下半部分。


文章来源:http://dotnet.blogger.cn/sumtec/articles/555.aspx
posted on 2004-03-30 10:44  Sumtec  阅读(2212)  评论(2编辑  收藏  举报