Inside assembly
-研究Assembly的结构,强命名Assembly的生成以及Delayed signing的过程
By Alva Chien
File Version: 1.0
Released: 2007.6.4
.NET平台的Module文件
一个.NET上的Module(模块)文件由4个部分组成:
1. PE32(+) Header: 标准的Windows平台上的PE格式信息,通常包括文件的类型 (GUI/CUI/DLL),CPU平台(anycpu/x86/x64/IA64) 等。这里的信息可以被Windows Explorer查看。
2. CLR Header:这个Header包含了该Assembly被创建时的.NET Framework版本 (也就是该Assembly可以被运行的最低版本) ,Metadata的大小和起始位置,resources,入口地址 (如果是EXE格式) 和Strong name(强命名)所必须的数据。
3. Metadata:由二进制数字构成的一堆表。共有三种类型的表:Definition(定义),Reference(引用),以及Manifest表。
4. IL:该module中所包含的IL代码。
Module文件的Metadata中的Definition和Reference表使得一个Module可以自描述。属于Definition表有ModuleDef, TypeDef, MethodDef, FieldDef, ParamDef, PropertyDef, EventDef等,而Reference表则有AssemblyRef, TypeRef, MemberRef等。这完全避免了DLL的Lib库,COM中的IDL。Manifest表有AssemblyDef,FileDef,ManifestResourceDef以及ExportedTypesDef等表。
Assembly的概念
Assembly是.NET平台进行重用的单元,它完全是一个抽象的概念,类似于程序。通常说一个程序,可以是一个单一的EXE文件,但是更多的时候是指一大堆文件的组合,比如微软的Word,不是指一个WinWord.exe,而是指以WinWord.exe为入口的一系列文件的集合,因为仅仅一个WinWord.exe是无法正常运行的。Assembly也一样,它同样是一个或多个Module文件或者资源(也可能是数据)文件的组合。一个Assembly必须有一个 (且只有一个) module文件(本文中称之为主Module)中的Metadata中包含Manifest表,这个主module文件就是Assembly的入口。CLR装载Assembly时就装载该module,而该Assembly的其它module则只是在需要访问时才会装载。
在.NET中,Assembly只能是一个EXE或者是DLL文件,同时Assembly是拥有Security信息和版本信息的实体,这些信息被存储于拥有Manifest表的Module中。
Strongly named assembly(强命名Assembly)
一个Strongly named assembly拥有4个要素:名称,版本信息,Culture以及Public key,也正是这4个要素使得它们彼此唯一。其中,Public key是一个非常大的数字,所以通常使用的是Public key token,这个值是对Public key进行hash运算后取最后8位Byte(共64位)。
事实上,当一个Assembly引用别的Assembly,所有的上述4个要素都被存储于该Assembly的Metadata的AssemblyRef表中了。比如,基本上每个Assembly都要引用的mscorlib的信息(ILDaasm的显示结果):
AssemblyRef #1 (23000001)
Token: 0x23000001
Public Key or Token: b7 7a 5c 56 19 34 e0 89
Name: mscorlib
Version: 2.0.0.0
Major Version: 0x00000002
Minor Version: 0x00000000
Build Number: 0x00000000
Revision Number: 0x00000000
Locale: <null>
HashValue Blob:
Flags: [none] (00000000)
这些信息描述了这样的一个Assembly:“MSCorLib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089”。当然,只所以只存储Public key token,因为Public key实在太过巨大。根据这些信息,CLR在运行时会准确得从GAC中找到对应的Assembly。
创建一个Strongly named assembly
创建一个Strongly named assembly时对主Module所做的修改:
1. Manifest部分
FileDef表:重建整个FileDef表,跟非Strongly name assembly不同的是,不仅仅将Assembly中除主Module之外的Module文件或者资源数据文件的名称加入该表,而且将这些文件的hash value同样存放于该表,生成该hash值的算法可以由用户自定义(通过使用AL.exe的/algid或者在代码中指定Assembly的System.Reflection.AssemblyAlgorithmIdAttribute属性),默认情况下使用SHA-1算法。
AssemblyDef表,将嵌入该Assembly完整的Public Key(注意这里不是Public key token)。
2. CLR Header部分
当包含了Strong name assembly的Manifest信息被完整创建之后,整个PE文件(这里指的是主Module文件,该运算不包含Authenticode Signature用户签名部分,该Assembly的Strongly name数据以及PE Header中的checksum)将被运算hash值,使用的hash算法是SHA-1(无法更改),然后将得到的hash value使用发布者的private key签名(RAS数字签名算法),并将最终结果存储在CLR header的预保留空间中。其它三个构成Strong name assembly的要素:名称,Culture以及版本同样被存储在CLR Header中。
CLR对Strongly Named Assemblies的保护机制
1. 安装到GAC
当安装一个Strongly named assembly时,系统会重新对Assembly的主module文件进行Hash值运算,该运算过程跟创建CLR Header中的RAS签名数值完全一致,并将运算的RAS签名结果跟存储在CLR Header的结果进行比较,如果两个值不同,则安装失败。接着,系统会对该Assembly中其它文件取Hash值,整个过程也完全类似于创建FileDef中Hash值的过程,同样,系统会逐一比较文件的Hash值,如果出现值不同,则安装失败。
2. 运行时的查找和装载
当需要从GAC装载一个特定的Assembly时,系统根据AssemblyRef信息来定位该Assembly,如果GAC中存在该Assembly,则不会再进行校验,而直接将该Assembly返回。如果查找不成功,会在程序所在目录以及程序配置文件指定的probe路径查找,当找到该Assembly并发现该Assembly是Strongly named assembly时,系统会再次进行一系列的hash值的运算(跟安装到GAC完全相同)以及比较来确保该Assembly没被破坏。所以,把使用到的Strongly name assembly安装到GAC会提高程序性能。
Delayed signing
所谓的Delayed signing就是在没有private key(也就是只有public key)的情况下给文件签名的一种机制,它只针对strongly named assembly。根据上文的描述,没有private key,就没法对主module文件生成的hash值进行RAS数字签名,所以Assembly的主module文件的CLR Header也就没有该部分信息(该部分大小还是被预留)。同样,因为该部分RAS签名内容的缺少,所有CLR对文件的保护都将失效。注意,别的Assembly无法分辨该assembly是否是完整的strongly named assembly,因为该Assembly中AssemblyDef表中的Public key 同样被嵌入,还有FileDef表中的Hash值也一样被运算后嵌入。
创建一个Delayed signing的strongly named assembly,需要编译器的特殊编译开关。对C#编译器(CSC.exe)而言,该编译开关为/delaysign;对AL.exe而言,则为/delay[sign]。同样,安装一个Delayed signing的assembly到GAC也需要特殊的指令,使用SN.exe的-Vr命令行参数来指定需要忽略hash值校验的Assembly(该操作其实就是在注册表的HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/StrongName/Verification路径下增加该Assembly的信息)。当需要对该Assembly进行正确签名时(通常这个任务由掌握了private key的人实施),使用SN.exe的-R命令行参数并同时指定Assembly的名称以及private key文件(该操作将真正的向对应的Assembly嵌入CLR Header的RAS签名,当然该RAS签名是由private key对文件的hash值运算而来)。最后,要取消CLR对该Assembly在GAC的忽略检查机制,通过运行SN.exe的-Vu或-Vx参数(该操作其实就是到注册表中删除对应的Assembly的Key)。
其它
常用的几个任务:
使用SN.exe来创建public/private密钥对: sn -k mykeys.keys
从密钥对文件中得出public key文件:sn -p mykeys.key mypublickey.publickey
显示public key文件中的内容: sn -tp mypublickey.publickey