对某软件的逆向分析与注册机编写
前言
这个软件会获取计算机的硬件信息(包括:CPU序号、MAC地址、硬盘序列号),然后根据这些硬件信息生成一个序列号。使用者把序列号发给软件提供者,然后提供者将注册码(.lic格式文件)发给使用者,使用者利用注册码即可完成注册。
本文将解决以下问题:
软件如何利用硬件信息生成序列号?(硬件信息与序列号的转换关系)软件如何根据序列号来判断注册码是否有效?如何根据序列号(或者硬件信息)得到注册码?
注意:以下内容将屏蔽敏感信息!本文仅做技术研究!
没有注册的情况
首先运行软件:
软件会直接弹出对话框。
点击“确定”后,点击上方菜单栏中的“注册”,点击“获取硬件信息”,此时会自动打开软件安装目录下的“硬件信息采集工具.exe”。
在界面中可以直接看到这个软件读取到的硬件信息:CPU序号、MAC地址、硬盘序列号。
点击“硬件序号”,就得到了序列号。
正常流程下,我需要把这个序列号发给软件提供者,然后从提供者那里得到注册码,再点击图2中的“注册”。
破解过程 查看软件信息
同样地,使用ExeinfoPe查看这个软件的编写语言,查看是否加壳。
显然,这个软件使用C#语言语言编写,基于.NET框架。
C#这种把源代码编译为中间代码的语言,可以很轻松地从中间代码上反编译出原始代码!
因此,接下来就可以用C#反编译软件ILSpy为所欲为~
反编译出源代码
用ILSpy打开这个软件的主exe程序。
在左边视图中可以看到,这个软件有3个命名空间:Quality.Startup、Quality.Startup.Config、Quality.Startup.Properties。
根据这些命名空间下面的类名,很容易猜到注册功能是在UCRegister这个类中实现的!
接下来就是分析UCRegister类的代码。下面展示实现功能的核心代码:
//流程第1步(笔者注释)
private bool Authorize()
{
try
{
string text = uiTxtRegisterKey.Text;
string text2 = "" ;
string text3 = "";
//流程第2步(笔者注释)
if (!text.Contains(text3) || !text.Contains(text2))
{
new Form().ShowErrorDialog("硬件序号异常,请在生成硬件序号时拷贝所有字符信息");
return false;
}
//流程第2步(笔者注释):首尾替换
text = text.Replace(text2, "").Replace(text3, "").Trim();
//流程第3步(笔者注释)
text = ExtendedUtils.Decrypt(text.Substring(0, text.Count() - 4), text.Substring(text.Count() - 4));
HardMessage hardMessage = JsonHepler.Json_DeserializeObject<HardMessage>(text);
if (hardMessage == null)
{
new Form().ShowErrorDialog("硬件序号异常,请在生成硬件序号时完整拷贝,且不要改动任意字符信息");
return false;
}
EnumHelper.GetEnumDescription(hardMessage.Duration);
if (hardMessage.CPUInfo != HardWareInformation.CPUInfo || hardMessage.DiskNo != HardWareInformation.DiskNo || hardMessage.MacAddress != HardWareInformation.MacAddress)
{
new Form().ShowErrorDialog("硬件信息不匹配");
return false;
}
ServerMessage serverMessage = new ServerMessage();
serverMessage.Clients = 1;
serverMessage.Code = Guid.NewGuid().ToString();
serverMessage.Date1 = DateTime.Now;
switch (hardMessage.Duration)
{
case EDuration.Forever:
serverMessage.Date2 = DateTime.Now.AddYears(100);
break;
case EDuration.OneMonth:
serverMessage.Date2 = DateTime.Now.AddMonths(1);
break;
case EDuration.ThreeMonth:
serverMessage.Date2 = DateTime.Now.AddMonths(3);
break;
case EDuration.SixMonth:
serverMessage.Date2 = DateTime.Now.AddMonths(6);
break;
case EDuration.OneYear:
serverMessage.Date2 = DateTime.Now.AddYears(1);
break;
case EDuration.TwoYear:
serverMessage.Date2 = DateTime.Now.AddYears(2);
break;
case EDuration.FiveYear:
serverMessage.Date2 = DateTime.Now.AddYears(5);
break;
default:
serverMessage.Date2 = DateTime.Now.AddMonths(1);
break;
}
serverMessage.HDMessage = new HardMessage
{
CPUInfo = HardWareInformation.CPUInfo,
DiskNo = HardWareInformation.DiskNo,
MacAddress = HardWareInformation.MacAddress
};
File.WriteAllText(Paths.AppLicfilePath, ExtendedUtils.Encrypts(JsonHepler.Json_SerializeObject(serverMessage)));
if (serverMessage != null)
{
skinLabelLastTime.Text = LimitTime(serverMessage);
}
UIMessageTip.ShowOk("注册完成");
skinLabelLastTime.Visible = true;
uiLabel.BringToFront();
uiLabel.Visible = true;
skinLabelLastTime.BringToFront();
MainForm.Instance.Text = MainForm.RegisteredTitle;
MainForm.Instance.SetStatusMsg(string.Empty);
return true;
}
catch (Exception ex)
{
LogHelper.Error(ex);
}
return false;
}
分析源代码
经分析,读取注册码后的流程为:
用户指定注册码文件路径后,软件调用uiSymbolButton1_Click方法读取注册码文件,得到注册码text。用户点击“注册”按钮,软件调用Authorize方法(该方法就是上面展示的核心代码);如果text中不包含字符串"" 或 “”,则报错。否则,把"" 和 ““都替换成空字符串,也就是删掉text中的”” 和 “”,得到text;调用ExtendedUtils.Decrypt方法对text解密。密文是text的第0位到倒数第5位(text.Substring(0, text.Count() - 4)),解密密钥是text的最后4位(text.Substring(text.Count() - 4))。解密后的明文也存在text中;调用JsonHepler.Json_DeserializeObject方法对text反序列化,得到hardMessage;分别提取hardMessage中的CPUInfo字段、DiskNo字段和MacAddress字段(这些字段代表了注册码中的硬件信息),与本机的实际硬件信息对比,如果全部匹配,则注册码有效!
数据的大致流程图如下(注意:hardMessage的字段除了图中的三个以外,根据反编译出来的源代码可知,还有Clients字段):
以上的流程回答了问题2!
至此,再来回答问题1:
既然软件验证注册码的时候用到Json_DeserializeObject方法来反序列化(字符串→硬件信息)
那么生成注册码的时候就可以用Json_SerializeObject方法来序列化(硬件信息→字符串)!
最后回答问题3:
对于破解者来说,需要写一个程序(或者称为注册机),输入硬件信息(CPU序号、MAC地址、硬盘序列号),输出注册码。
参考上面的流程图,很容易想到,只要把这个流程反过来就行。
既然软件判断注册码是否有效的流程是:
(注册码)去掉两个字符串→解密(得到序列号)→反序列化→对比硬件信息
那么破解者的程序流程就应该是:
输入硬件信息→序列化(得到序列号)→加密→加上两个字符串(得到注册码)!!
参考源代码:
软件使用ExtendedUtils.Decrypt方法解密
那么注册机就可以用ExtendedUtils.Encrypt方法加密!
软件使用JsonHepler.Json_DeserializeObject方法反序列化
那么注册机就可以用JsonHepler.Json_SerializeObject方法序列化!
其中要注意的一个小细节是:根据源代码可知,解密用的密文在前,4位长度的解密密钥紧跟着密文后面,密文和解密密钥是不重叠的!密文在前解密密钥在后组成字符串。因此,解密用到的密钥不是固定的,完全就是字符串的最后四位!
而且在ILSpy中继续查看ExtendedUtils.Decrypt方法的实现方法(如下图),发现是使用DES算法加密,这是一种对称加密算法,加密密钥和解密密钥相等,因此加密密钥是可以任意指定4位字符串的!
编写注册机
接下来就可以编写一个注册机了,这也是最激动人心的时候!
与软件相同,注册机也用C#语言写,使用Winform做一个小窗体界面。
输入:硬件信息(包括CPU序号、MAC地址、硬盘序列号)。
输出:注册码文件。
注意:以下只展示核心代码!
首先需要定义一个HardMessage类,作为序列化的依据。
class HardMessage
{
public string CPUInfo { get; set; }
public string DiskNo { get; set; }
public string MacAddress { get; set; }
public int Clients { get; set; }
}
然后就到了注册机的核心部分:
//构造硬件信息变量hardmessage,对hardmessage序列化为SerializeMessage
HardMessage hardmessage = new HardMessage { CPUInfo = MyCPUInfo, DiskNo = MyDiskNo, MacAddress = MyMacAddress, Clients = 1 };
string SerializeMessage = JsonHepler.Json_SerializeObject(hardmessage);
//加密。明文为SerializeMessage,加密密钥为"abcd"(密钥可任意指定一个长度为4的字符串)
//加密后的密文为EncryptMessage
string EncryptMessage = ExtendedUtils.Encrypt(SerializeMessage, "abcd");
//在密文EncryptMessage前加"",后加" "
string License = "" + EncryptMessage + "";
//生成注册码文件
StreamWriter sw = new StreamWriter("注册码.lic");
sw.Write(License);
sw.Dispose();
生成注册码
代码写全后,编译运行。
把硬件信息输入后,在注册机所在目录下就生成了“注册码.lic”的文件。
最后,在图2中的界面点击“浏览”,选择这个“注册码.lic”文件,点击注册。。。。。。
Bingo!!