作者:Leepy
Mono.Cecil是一個強大的MSIL的注入工具,利用它可以實現動態創建程序集,也可以實現攔截器橫向切入動態方法,甚至還可以修改已有的程序集,並且它支持多個運行時框架上例如:.net2.0/3.5/4.0,以及silverlight程序
官方地址:http://www.mono-project.com/Cecil
首先,我先假想有一個這樣的商業組件,該組件滿足了以下條件:
1. 該程序集的代碼被混淆過了
2. 該程序集被強命名簽名過了
3. 該程序集的UI界面被加上了版權信息,例如水印等等
這里我引用的是之前寫的一篇文章中的用戶登錄的組件(http://www.cnblogs.com/liping13599168/archive/2010/05/07/1729357.html),並將其進行改造成一個Silverlight組件,項目結構如圖:
其中BusinessComponent類庫作為選定的”商業組件",這里編寫一個CopyRight類,作為水印的打印信息:
{
internal void SetRightInfo(TextBlock textBlock)
{
textBlock.Text = "This is a watermark.";
}
}
運行效果為:
可以看到組件上面附帶一個版權信息效果
接着,要對BusinessComponent進行強命名簽名,選擇項目右鍵,如下設置:
新建一個snk文件,這個文件利用RSA加密包含着公鑰和私鑰的信息,對該組件編譯會把它編譯進去了。
這個snk到底做什么用的呢?一會兒大家看着就知道了。
最后,我要利用Dotfuscator混淆工具,對於BusinessComponent.dll進行代碼混淆
由於我這里的Dotfuscator使用的老版本的4.2,所以不支持對於Sliverlight程序集的混淆,有興趣的朋友可以自己下載試試,官方地址是:http://www.preemptive.com
好了,到這里,一個BusinessComponent商業組件的雛形設計好了。現在拿起手上的工具——Mono.Cecil去改造它了。
首先官方下載了一個mono.cecil的項目,打開工程主動去編譯一下,在目錄Silverlight_Release就能夠獲取Mono.Cecil.dll
那么,把它引入到項目中來,在頁面中加入一個按鈕,點擊觸發事件程序:
var entryPointResourceInfo = Application.GetResourceStream( new Uri(entryPointPart.Source, UriKind.Relative));
var module = ModuleDefinition.ReadModule(entryPointResourceInfo.Stream);
var type = module.Types.FirstOrDefault(o => o.Name == "Copyright");
var method = type.Methods.FirstOrDefault(o => o.Name == "SetRightInfo");
Instruction instruction = method.Body.Instructions[ 2];
instruction.Operand = "";
module.Write( "C:\\BusinessComponent.dll");
其中module獲取程序集中的Module模塊,module.Types可以得到程序集中的所有類型:
選擇Type的類型名稱為Copyright,通過type.Methods可以得到該類型得到的所有方法:
選擇Method的名稱為SetRightInfo,method.Body.Instructions可以得到該方法里面的IL堆棧調用信息:
我們看到索引2這行的信息,ldstr是定義一個字符串常量的方式,而索引3通過索引2行的值,賦值給TextBlock中,請查看Copyright的代碼就可以知道。於是:
instruction.Operand = "";
這樣就可以把該水印信息去除了,是不是很簡單呢:)
最后一行module.Write("C:\\BusinessComponent.dll");就是將修改過后的程序集重新寫入到一個新的程序集中;
執行程序,在C盤就得到一個BusinessComponent的新程序集了。
現在,我重新引入該新的程序集,看是否水印信息會被去掉,重新編譯,發現編譯出錯:
這是為什么?這就是我上面提到的利用snk的強命名簽名了,它的目的是為了防止你的程序集被非法篡改而設置的,而需要破解它就需要它的私鑰了,可是作為一個真正的商業組件,我們是沒有辦法得到它的私鑰,那是不是意味着沒辦法破解了呢。
那么我重新通過sn.exe進行重新簽名:
顯示公鑰不配對。這個是顯然的,因為之前通過VS的Sign標簽人工設置business.snk,它采用的是這個文件的公鑰,而mykey.snk就要采用它自己的公鑰了。
既然無法破解,那我就去改掉它的簽名,利用我自定義的公鑰和私鑰去做簽名,而恰恰好,Mono.Cecil就可以滿足你的需要。
打開VS2010命令提示窗口,在C盤上輸入:
這樣就隨機產生了一條包含公鑰和私鑰的RSA文件,接着要抽取出它的公鑰,輸入:
緊接着輸入:
就可以得到公鑰的數據,例如publicKey以及publicKeyToken
得到了兩串字符串,把它們都復制出來,繼續在剛才的按鈕代碼中補充:
var entryPointResourceInfo = Application.GetResourceStream( new Uri(entryPointPart.Source, UriKind.Relative));
var module = ModuleDefinition.ReadModule(entryPointResourceInfo.Stream);
module.Assembly.Name.PublicKey = this.GetPublicKey();
module.Assembly.Name.PublicKeyToken = this.GetPubliKeyToken();
{
string s = "0024000004800000940000000602000000240000525341310004000001000100c5f2c7d8057257ea3640b93b7a98b1a5501718196589973b09b94cf47fb246c8ad86ae688caa36959d9793702c27ce198a447d69ba0ddac70075fab16748999f066795de472dfb5cd9347a10f967e750ca388aaa9a66619291003345b0176dec0008d3d88986c4605c0d60e3b6563f96984c6d28aeb4bf2d672e8d2d2123c394";
return this.StrToHexBytes(s);
}
private byte[] GetPubliKeyToken()
{
string s = "d2850434514ae5ca";
return this.StrToHexBytes(s);
}
private byte[] StrToHexBytes( string hexString)
{
hexString = hexString.Replace( " ", "");
if ((hexString.Length % 2) != 0)
hexString += " ";
byte[] returnBytes = new byte[hexString.Length / 2];
for ( int i = 0; i < returnBytes.Length; i++)
returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
return returnBytes;
}
重新編譯執行,它的公鑰信息就已經編譯進去了,同樣在C盤生成BusinessComponent.dll新程序集
這個時候,先不着急引入程序集,先對新程序集通過私密對其進行重新簽名,輸入命令:
OK,現在就可以重新引入了,這個時候再重新編譯代碼:
編譯通過了。
現在運行下程序(這里要注意,你必須將SilverlightApp程序切換到OOB模式,才可以修改程序集):
水印信息已經被去除了:)
Mono.Cecil還可以用來做很多東西,比如說可以在一個方法中增加其他代碼,能夠細到IL中的每一個執行步驟,所以通過它可以做很多有意思的東西,比如說單元測試中的Mock對象以及性能上更佳的反射功能。
附上本文源代碼:SilverlightMonoCecilDemo.rar