2024年1月28日发(作者:)
word格式-可编辑-感谢下载支持
.NET环境下实现程序集的动态替换
.NET程序集是包含一个或者多个类型定义文件和资源文件的集合,它是 .NET
Framework 编程的基本组成部分。也可以通俗地描述它为由.NET生成的.exe文件或者.dll文件。.NET环境下支持两种程序集:弱命名程序集和强命名程序集。 弱命名程序集和强命名程序集在结构上是相同的。他们都采用PE文件格式,包含PE表头,CLR表头,元数据和清单表。区别在于:强命名程序集拥有一个发布者的公钥/私钥签名对,他们用于唯一的标识程序集的发布者。通过公钥/私钥对,我们可以对程序集进行唯一的标识,安全策略和版本策略。
一、需求背景
在传统的win32环境下,我们只需要将程序集覆盖同名程序集,便可以实现对程序集的动态替换。但是这种替换方式,很容易导致DLL Hell问题。DLL Hell问题是指两个不同的公司可能开发处具有相同名称的程序集,如果将相同名称的程序集放置到同一个目录下,则会出现程序集覆盖现象,最后安装的程序集会覆盖前面的程序集,从而可能导致应用程序不能正常运行。为了解决这一问题,在.NET环境下,CLR采取了强命名程序集的方式来唯一的表示程序集。强命名程序集包含四个标识:名称,版本号,语言文化标识和一个共有/私有密钥对。显然地,在.NET环境下,仅程序集名称相同的两个程序集,是不会发生动态替换的情况的。那么,在.NET环境下,我们如何才能实现强命名程序集的动态替换呢?
下面我们假想这样一个应用场景:我们熟悉的就是一个.NET程序集,该程序集是微软官方提供的数据提供程序(.NET Data Provider),它是.NET应用程序与数据库交互的桥梁。图一展示了常见.NET应用系统的结构。一般地,.NET应用系统包括应用程序,.NET Data
Provider 以及数据库。应用程序需要通过.NET Data Provider才能向数据库中访问和存储数据。
图一:.NET应用系统结构图
正常情况下,上面介绍的应用系统可以很好地服务于企业日常运作,满足企业需求。但是在某些特殊情况下,出于对成本和业务扩展等因素的综合考虑,企业需要更换应用系统的后台数据库,比如,将后台数据库由SQL Server更换为神通数据库。自然地,我们想到要将数据提供程序由更换为,然后,重新收集企业的业务需求,最终重新编写应用程序。但是,如果为了节省成本,企业强烈要求不能更换应用程序,仍然使用现有的应用程序,作为一个程序员,你是否会觉得该需求不可能实现?
让我们重新梳理一下需求:后台数据库由SQL Server更换为神通数据库,应用程序保持不变,即:不能修改应用程序源代码,并且不能对应用程序进行重新编译,我们看到应用程序只是一些程序集文件(.exe文件或.dll文件)。如果想完成该项目,看来只能从.NET Data
word格式-可编辑-感谢下载支持
Provider入手了,但是,看起来这也是不可能完成的任务,因为是经过强命名的程序集,它的公钥标记是b77a5c561934e089,这个程序集已被调用它的应用程序所记住,在应用程序不作任何变动的情况下,即使我们基于仿冒出一个,但是因为公钥标记不匹配,上层应用程序仍不识别该程序集,应用系统也将无法正常运行。
如果能修改上层应用程序所记住的公钥标记,用一个新的公钥标记来代替,并将该标记设置为仿冒出来的的公钥标记,问题便可迎刃而解。要修改强命名程序的公钥标记,调用
组件是唯一的选择。
二、简介
是Mono的组件之一,它是一个强大的MSIL的注入工具,利用它可以实现动态创建程序集,也可以实现拦截器横向切入动态方法,甚至还可以修改已有的程序集。我们可以用它来打探一个.NET程序集内部的结构,就像反射那样,只不过并不需要将程序集加载进来,只是读取文件物理内容而已。更重要的是,可以修改并保存程序集,这便可以让我们实现各种各样的要求。另外,它支持多个运行时框架如:.NET2.0、.NET3.5、.NET4.0以及silverlight程序。显然地,有了,我们可以容易地实现程序集的动态替换,的功能非常强大,本文只涉及了很少的一部分。
三、解决方案及步骤
将应用于本项目的主要思路是用加载一个程序集,使用微软提供SN工具来生成新的公钥标记替换掉原来程序集的公钥标记,然后对仿冒的进行重新签名,最后是修改调用的外部程序所记录的公钥标记,最终实现应用程序的顺利执行。下面对这一过程作具体描述。
1.使用SN工具生成私钥文件(.snk),获取公钥标记。
以VS2008为例,生成.snk文件的过程如下:
1).打开SDK Command Prompt或者VS2008中的Visual Studio 2008 Command Prompt
2).输入sn -k ,得到的私钥文件在命令提示符的当前文件夹下
3).输入sn -p
4).输入sn -t 得到公钥标记(Publickeytoken)。
经过上述步骤,我们可以获取到私钥文件和公钥标记。公钥标记是一个16位的数字和字母混合而成的数,例如,本例中获得的公钥标记为:37a1891d587db1fe。
2.使用私钥文件(.snk),对仿冒的程序集进行重新签名。
到这里就需要使用组件了。为了配合使用,我们还需要调用另外一个资源:StrongName库。StrongName库作为一项资源包含在 中。在c#中调用它的声明如下:
[DllImport("", EntryPoint = "StrongNameKeyDelete", CharSet = )]
public static extern bool StrongNameKeyDelete(string wszKeyContainer);
[DllImport("", EntryPoint = "StrongNameTokenFromPublicKey", CharSet = )]
word格式-可编辑-感谢下载支持
public static extern bool StrongNameTokenFromPublicKey(byte[] pbPublicKeyBlob, int cbPublicKeyBlob, out
IntPtr ppbStrongNameToken, out int pcbStrongNameToken);
[DllImport("", EntryPoint = "StrongNameFreeBuffer", CharSet = )]
public static extern void StrongNameFreeBuffer(IntPtr pbMemory);
[DllImport("", EntryPoint = "StrongNameKeyInstall", CharSet = )]
public static extern bool StrongNameKeyInstall([MarshalAs()] string wszKeyContainer,
[MarshalAs(y, SizeParamIndex = 2, SizeConst = 0)] byte[] pbKeyBlob, int arg0);
[DllImport("", EntryPoint = "StrongNameSignatureGeneration", CharSet = )]
public static extern bool StrongNameSignatureGeneration(string wszFilePath, string wszKeyContainer, IntPtr
pbKeyBlob, int cbKeyBlob, int ppbSignatureBlob, int pcbSignatureBlob);
其中,StrongNameKeyDelete是删除指定的密钥容器。StrongNameTokenFromPublicKey是获取表示公钥的标记,StrongNameFreeBuffer是释放上一次调用强名称函数(如
StrongNameGetPublicKey、StrongNameTokenFromPublicKey 或
StrongNameSignatureGeneration)时分配的内存,StrongNameKeyInstall是向容器中导入一个公钥/私钥对,StrongNameSignatureGeneration是生成指定程序集的强名称签名。
实现重签名的代码如下:
public static byte[] GetNewKey(string keyFileName)
{
using (FileStream keyPairStream = ad(keyFileName))
{
return new StrongNameKeyPair(keyPairStream).PublicKey;
}
}
public void ReSign()
{
AssemblyDefinition asm = embly(ASSEMBLYNAME);
Key = GetNewKey(KEYFILENAME);
sembly(asm, ASSEMBLYNAME);
//用KEY文件建立密钥容器
byte[] pbKeyBlob = lBytes(KEYFILENAME);
string wszKeyContainer = d().ToString();
StrongNameKeyInstall(wszKeyContainer, pbKeyBlob, );
//使用新建的密钥容器对程序集进行签名
StrongNameSignatureGeneration(ASSEMBLYNAME, wszKeyContainer, , 0, 0, 0);
//删除新建的密钥容器
StrongNameKeyDelete(wszKeyContainer);
}
其中,keyFileName参数既是私钥文件,ASSEMBLYNAME是仿冒的。上述重签名过程的实质就是首先建立一个密钥容器,使用密钥容器对程序集进行重签名。经过以上程序处理可将仿冒的的公钥标记从9a875100e271928d修改为37a1891d587db1fe。
3.修改调用程序集的外部应用程序所记录的公钥标记。
示例代码如下:
private static byte[] tryGetPublicKeyToken(string keyFileName)
{
word格式-可编辑-感谢下载支持
byte[] newPublicKey;
using (FileStream keyPairStream = ad(keyFileName))
{
newPublicKey = new StrongNameKeyPair(keyPairStream).PublicKey;
}
int pcbStrongNameToken;
IntPtr ppbStrongNameToken;
StrongNameTokenFromPublicKey(newPublicKey, , out ppbStrongNameToken, out
pcbStrongNameToken);
var token = new byte[pcbStrongNameToken];
(ppbStrongNameToken, token, 0, pcbStrongNameToken);
StrongNameFreeBuffer(ppbStrongNameToken);
return token;
}
public static void ReLink(string KEYFILENAME, string file ,string refAssemblyfile)
{
//获取加密文件的公钥标记
byte[] publicKeyToken = tryGetPublicKeyToken(KEYFILENAME);
//初始化一个要重新签名的程序信息列表
AssemblyInfo[] assemblyInfoList = new[]
{
new AssemblyInfo {FileName =file},
new AssemblyInfo {FileName =refAssemblyfile}
};
//获得每个程序集的名称
foreach (AssemblyInfo assemblyInfo in assemblyInfoList)
{
me = embly(me).me;
}
//检查是否被引用,是的话,就替换PublicKeyToken
foreach (AssemblyInfo assemblyInfo in assemblyInfoList)
{
AssemblyDefinition assembly =embly(me);
foreach (ModuleDefinition module in s)
foreach (AssemblyNameReference reference in lyReferences)
foreach (AssemblyInfo assInfo in assemblyInfoList)
{
if (me == me)
{
KeyToken = publicKeyToken;
sembly(assembly, me);
}
}
}
}
}
其中,tryGetPublicKeyToken方法是获取私钥文件的公钥标记,ReLink方法实现了修改
word格式-可编辑-感谢下载支持
调用的外部程序所记录的公钥标记。AssemblyDefinition, ModuleDefinition,
AssemblyNameReference,都是组件中的主要外部类, AssemblyDefinition用户获取程序集的基本信息,ModuleDefinition用于描述程序集内部的模块,AssemblyNameReference存储程序集对其他程序集的引用信息。若要了解更详细的信息,可查阅的官方帮助手册,这里不再详细介绍。经过上述代码处理,应用程序记录的公钥标记被修改为37a1891d587db1fe,这与我们仿冒的的公钥标记一致,也即该程序集可以被上层的应用程序调用了。
至此,利用组件,我们就实现了对程序集进行重签名以及修改应用程序记录的公钥标记等工作。也即实现了通过修改.NET Data Provider来满足企业更换数据库但不变更上层应用程序的需求。
四、可视化工具展示
为了在实际项目中可以反复使用组件来对.NET程序集进行重签名以及修改公钥标记,我们编写了可视化的图形界面工具——引用程序集重定位工具,如图二所示:
图二:引用程序集重定位工具
使用“重签名”可以对.NET程序集进行重签名,使用“重连接”可以将重定位程序所引用的程序集。使用该工具,我们可以很方便地完成.NET程序集的动态替换工作。
五、总结
本文基于一个假想的应用场景,利用组件,为在.NET环境下实现强命名程序集的动态替换提供了一种解决方案。基于该方案,编写的引用程序集重定位工具,已投入实际的项目中。该方案对解决类似应用问题具有很好的借鉴意义。


发布评论