.net sdk中有不少很強大的工具,可以輕易完成對.net程序的破解,只要你懂得一點IL語言就行。現在以一個 M 軟件為例,介紹整個破解過程。
第零步:用反編譯工具分析軟件的可執行文件,制訂破解邏輯。
我以"M"來稱呼這個軟件。首先,要搞明白M的注冊原理。M是通過輸入注冊碼來完成注冊的,為了破解它,要先搞明白它注冊的原理,這就必須用反譯工具來分析它。
我用的反編譯工具是.net reflactor,可以用它打開.net的可執行文件,看到C#源代碼。打開之后,經過一番尋找,發現的注冊窗口的事件處理過程所在,然后一直找到了它的注冊校驗程序。如下:
MarkdownPad2.Licensing.LicenseEngine.VerifyLicense(String, String) : Boolean
public bool VerifyLicense(string licenseKey, string email)
{
if (string.IsNullOrEmpty(licenseKey) || string.IsNullOrEmpty(email))
{
return false;
}
try
{
this.License = this.Decrypt(licenseKey);
this.LicenseProcessed = true;
}
catch (Exception exception3)
{
...
return false;
}
if (((this.License == null) || (this.License.Email == null)) || (this.License.Product == null))
{
return false;
}
bool flag = this.License.Email.Equals(email, StringComparison.OrdinalIgnoreCase);
bool flag2 = this.License.Product == "MarkdownPad2";
return (flag && flag2);
}
在窗口中輸入的email和key都被送到這里,其中Key通過Decrypt方法進行解密,直接生成了一個License對象,這個對象的定義如下:
namespace MarkdownPad2.Licensing
{
using System;
using System.Runtime.CompilerServices;
public class License
{
public DateTime CreationDate { get; set; }
public string Email { get; set; }
public int LicenseTypeId { get; set; }
public string Name { get; set; }
public string Product { get; set; }
}
}
看來就是注冊信息的內存對象了。那么,再看看生成這個對象的那個Decrypt方法:
private License Decrypt(string payload)
{
RSA rSA = CryptoKey.FromPublicKey(
"-----BEGIN PUBLIC KEY-----\nMIIB...pQIDAQAB\n-----END PUBLIC KEY-----",
"2QJmLPD5ktxIrFkr")
.GetRSA();
byte[] msg = Convert.FromBase64String(payload);
byte[] bytes = rSA.PublicDecrypt(msg, RSA.Padding.PKCS1);
string str = Encoding.Default.GetString(bytes);
IRestResponse response = new RestResponse {
Content = str
};
License license = new JsonDeserializer().Deserialize<License>(response);
rSA.Dispose();
return license;
}
研究一下就能明白,這個程序就是通過一個指定公鑰的rsa解密器,對傳入的Base64字符進行解密,解密之后的內容是一個Json格式的文本符串,這個文本串直接反序列化就是License對象了。
破解的辦法有下列幾個:
- 把 VerifyLicense 改掉,直接返回一個true。
- 這個方法我其實用過了,但是發現了一個問題,就是軟件一啟動總是會出現一個nullException,原因應該是在啟動時軟件檢查是否注冊,然后改過的方法給它返回了個true,但它興致勃勃地去檢查注冊的信息時,沒料到是個空的。
- 解決辦法就是去修改c:\users<用戶名>\AppData\Local<產品名><文件名><版本號>\user.config文件,把注冊信息寫進去,但是注冊信息還是要符合解密要求才行。所以我不采用這個方式。
- 寫一個注冊碼生成器,用對應的私鑰生成符合程序要求的注冊碼,這樣不用改程序。
- 問題在於,我們沒有這個公鑰對應的私鑰,沒辦法做注冊信息之后再加密。
- 解決辦法自己生成一個新的RSA密碼對,然后把程序的公鑰替換了,用對應的私鑰寫注冊機。那么還是要改程序了?意義不大,算了不用這個辦法。
- 改掉Decrypt方法,把解密的過程跳過,讓它支持明文注冊信息,然后就隨我的意輸入了。就用這個辦法了!
方案確定,現在開始修改程序。
第一步:用ildasm來反編譯EXE或是DLL文件到IL源代碼。
ildasm是.net自帶的反編譯IL的工具。我用ildasm打開M.exe文件,看到里面的命名空間、類名什么的了,然后選擇菜單里的“轉儲”,選擇一個空的目錄——注意,會生成一堆文件的,所以要用空的目錄——,確認,IL文件與資源文件就保存到那里了。
由於我們只要動一點點代碼,其他的都不要動的,所以,只需要注意m.il文件就行了。
第二步:修改il文件。
用你喜歡的文本編輯器打開m.il文件,找到我們打算要修改的Decrypt方法,哇,這個在C#里只有幾行方法,在IL里好長呀!
再回來看看這個方法的c#版:
private License Decrypt(string payload)
{
RSA rSA = CryptoKey.FromPublicKey(
"-----BEGIN PUBLIC KEY-----\nMIIB...pQIDAQAB\n-----END PUBLIC KEY-----",
"2QJmLPD5ktxIrFkr")
.GetRSA();
byte[] msg = Convert.FromBase64String(payload);
byte[] bytes = rSA.PublicDecrypt(msg, RSA.Padding.PKCS1);
string str = Encoding.Default.GetString(bytes);
IRestResponse response = new RestResponse {
Content = str
};
License license = new JsonDeserializer().Deserialize<License>(response);
rSA.Dispose();
return license;
}
我們要把它改為這樣:
private License Decrypt(string payload)
{
IRestResponse response = new RestResponse {
Content = payload
};
JsonDeserializer deserializer = new JsonDeserializer();
return deserializer.Deserialize<License>(response);
}
現在,硬起頭皮,看看IL代碼,發現並不太難,比如說起頭這段:
.locals init (
class [ManagedOpenSsl]OpenSSL.Crypto.CryptoKey V_0,
class [ManagedOpenSsl]OpenSSL.Crypto.RSA V_1,
uint8[] V_2,
uint8[] V_3,
string V_4,
class [RestSharp]RestSharp.IRestResponse V_5,
class [RestSharp]RestSharp.Deserializers.JsonDeserializer V_6,
class MarkdownPad2.Licensing.License V_7)
這就是局部變量的定義啦,它們按位置編號,從0開始。
然后對着c#看,就是幾個ldXXX指令(ldstr:壓棧字串常量;ldloc:壓棧局部變量,ldarg:壓棧本方法參數)准備參數,一個call調用方法,如有返回值,就用stloc保存到第N號變量里。
IL_0000: ldstr "-----BEGIN PUBLIC KEY-----\nMI...QAB\n-----END PUBLIC KEY-----"
IL_0005: ldstr "2QJmLPD5ktxIrFkr"
IL_000a: call class [ManagedOpenSsl]OpenSSL.Crypto.CryptoKey [ManagedOpenSsl]OpenSSL.Crypto.CryptoKey::FromPublicKey(string, string)
IL_000f: stloc.0
看懂了吧?后面的也都一樣。我們一直找到new RestResponse的地方,
IL_0034: newobj instance void [RestSharp]RestSharp.RestResponse::.ctor()
IL_0039: stloc.s V_5 //new 的返回值賦值給V_5局部變量。
IL_003b: ldloc.s V_5 //第0個參數是this指針。
IL_003d: ldloc.s V_4 //set方法的參數,現在取的是V_4,也就是str局部變量
IL_003f: callvirt instance void [RestSharp]RestSharp.IRestResponse::set_Content(string)
我們只需要把 IL_003d: ldloc.s V_4
改為 IL_003d: ldarg.1
,就把payload參數直接給了response.Content了。
IL_0034: newobj instance void [RestSharp]RestSharp.RestResponse::.ctor()
IL_0039: stloc.s V_5
IL_003b: ldloc.s V_5
IL_003d: ldarg.1
IL_003f: callvirt instance void [RestSharp]RestSharp.IRestResponse::set_Content(string)
前面的RSA解密代碼如果不刪除,它們會對將放進來的明文進行解密,會引起異常,我們就把它們都注釋了吧!也就是初始化局部變量之后到IL_0034之前的代碼都全部刪除。而最后還有一個RSA.dispose(),它對應的IL代碼如下,也一並刪除:
IL_0056: ldloc.1
IL_0057: callvirt instance void [ManagedOpenSsl]OpenSSL.Core.Base::Dispose()
還有,由於有4個局部變量沒有被使用,所以也要在init里把它們刪除,不然會造成程序運行時的堆棧出錯。
最后檢查程序里的變量引用序號是不是需要調整。最后的程序是這樣:
.method private hidebysig instance class MarkdownPad2.Licensing.License
Decrypt(string payload) cil managed
{
// 代碼大小 95 (0x5f)
.maxstack 3
.locals init (
class [RestSharp]RestSharp.IRestResponse V_5,
class [RestSharp]RestSharp.Deserializers.JsonDeserializer V_6,
class MarkdownPad2.Licensing.License V_7)
IL_0034: newobj instance void [RestSharp]RestSharp.RestResponse::.ctor()
IL_0039: stloc.s V_5
IL_003b: ldloc.s V_5
IL_003d: ldarg.1
IL_003f: callvirt instance void [RestSharp]RestSharp.IRestResponse::set_Content(string)
IL_0044: newobj instance void [RestSharp]RestSharp.Deserializers.JsonDeserializer::.ctor()
IL_0049: stloc.s V_6
IL_004b: ldloc.s V_6
IL_004d: ldloc.s V_5
IL_004f: callvirt instance !!0 [RestSharp]RestSharp.Deserializers.JsonDeserializer::Deserialize<class MarkdownPad2.Licensing.License>(class [RestSharp]RestSharp.IRestResponse)
IL_0054: stloc.s V_7
IL_005c: ldloc.s V_7
IL_005e: ret
} // end of method LicenseEngine::Decrypt
第三步:用ilasm來重新編譯exe文件。
ilasm是一個.net sdk里的命令行工具,用來編譯il文件。在命令行里執行下面的命令,M.exe就被重新生成了:
ilasm /exe /resource=M.res /output=M.exe M.il
注意,M.exe里的程序還有很多圖片之類的資源,在反編譯時,這些資源文件也會被抽出來放到這里,所有的資源都會在m.res文件里描述。在重新編譯時,只需要如上面例子里的那樣/resource=M.res就行了。
好,把破解過的M.exe放回到原來的目錄里,替換原版的程序,然后執行之。
選擇注冊,在注冊碼框里輸入json格式的明文:
{"CreationDate":"2014-12-11T06:06:54.0068849Z",
"Email":"***@qq.com",
"LicenseTypeId":1,"Name":"myname",
"Product":"Msoftware"}
這個JSON數據是根據License對象的成員而編寫的。現在我們知道這個字串會被直接反序列化為一個妥妥的License對象。
一按OK……
成功!您現在的所有使用限制已被解除。