您的位置:首页 > 理论基础 > 计算机网络

给没有源代码的.NET程序“打补丁“(转http://www.cnblogs.com/lerit/archive/2011/08/22/2148858.html#2182758)

2011-11-18 15:51 267 查看
公司为一个web应用程序写了一个注册机,基本原理是用户运行这个软件后,得到一个申请码,然后公司根据这个申请码给出相应注册码,匹配后方可正常使用web软件。在别人机子上没有问题,但是我机子上运行软件后死活就是没有申请码产生,也没报错。开发此程序的人员早不知道是谁了,也没有源码,只好自己分析是什么问题导致的,如果是程序的问题,希望能给程序打个“补丁”,准确的说是采用比较初级的.NET逆向工程来注入需要的补丁代码。以下是思路和主要操作(代码中略去了不需要的代码部分)。

1.

用reflector打开后,发现是.NET程序,且没混淆,这就好办了。因为程序的代码比较少,在reflector中看就那么几个按钮的事件函数,读一下就差不多了。基本思路是,既然申请码那个文本框没有产生申请码,那么就要找到为那个申请框赋值的语句,看看那个语句有没有问题,那么要想找到这个语句,就要先找到这个申请框在程序中的ID号。

2.

从程序中填写完注册码后点击的那个注册按钮入手,那个按钮点击后提示了“注册码错误”,根据这几个字,找到了:




codeprivatevoidregisterBut_Click(objectsender,EventArgse)
{
if(this.sAnswerCode(this.requestCode.Text)==this.answerCode.Text)
{
this.setRegStr(this.answerCode.Text);
MessageBox.Show("注册成功。");
}
else
{
MessageBox.Show("注册码错误!");
}
}


显然,requestCode就是申请码那个TextBox,answerCode就是注册码那个TextBox,完成找ID号的工作。

3.

然后去找为requestCode赋值的语句:




codeprivatestaticvoidMain()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(newSystemSet());
}
publicSystemSet()
{
………
this.requestCode.Text=this.CpuID();
………
}

privatestringCpuID()
{
stringtext2="";
try
{
ManagementObjectSearchermanagementObjectSearcher=newManagementObjectSearcher("SELECT*FROMWin32_Processor");
ManagementObjectCollectionmanagementObjectCollection=managementObjectSearcher.Get();
using(ManagementObjectCollection.ManagementObjectEnumeratorenumerator=managementObjectCollection.GetEnumerator())
{
.....
}
.....
}
catch
{

}
returntext2;
}


可以看出来,是CpuID方法用于产生申请码的,应该是根据cpu等硬件信息产生的,这就可以为每台计算机产生不同的申请码。最终经过处理返回text2就是申请码,用try-catch代码块,把所有可能错误都忽略了,难怪没申请码但是也没报错~~~

4.

将CpuID这个方法在我本机的VS中执行了一下(需要引用System.Management),果然报错了,查看catch抛出的异常,是在调用searcher.Get()时抛出的异常,信息如下:


{System.Runtime.InteropServices.COMException(0x80070422):无法启动服务,原因可能是已被禁用或与其相关联的设备没有启动。(异常来自HRESULT:0x80070422)
在System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32errorCode,IntPtrerrorInfo)
在System.Management.ManagementScope.InitializeGuts(Objecto)
在System.Management.ManagementScope.Initialize()
在System.Management.ManagementObjectSearcher.Initialize()
在System.Management.ManagementObjectSearcher.Get()

可能我机子的某个服务没有启动造成的,而Get方法却使用了此服务。Get的智能提示是:

“异步调用WMI查询,并绑定到一个观察程序以传递结果。”

这就明白了,去服务里看了一下,我的WindowsManagementInstrumentation确实没有启动,这是我之前优化系统时关上的,没想到在这里用到了。启动此服务后,程序运行正常了,可以产生申请码。


5.

虽然我知道了是这个问题,不过以后要是再使用这个软件的机子发生同样问题呢?总不能还配一个专门的说明书,所以如果能给打个小小的补丁,那是最好。不过因为没有源码,原则上是修改越少越好,而不是一味追求完美的修补。

为了方便起见,我这里只想在CPUID方法的catch中加一个提示语句:


viewsourceprint?

1
MessageBox.Show(
"无法生成申请码,请确认系统WMI服务启动正常!"
);
这肯定是不够严谨的,不会所有的异常都是因为WMI未启动导致的,因为修改IL比较麻烦,这里就为了最小化修改的原则,以后发现别的问题再说。

操作思路如下:


得到程序的IL代码;
将需要的提示信息的IL代码注入;
反编译程序;
测试程序运行是否正常。

(1)

首先,利用ILDASM(.NET的IL反汇编程序,Framework自带的),打开这个程序,然后转储它的IL及其相关文件(这里我转储为programIL,一共生成4个文件,一个IL后缀,两个resources后缀,还一个res后缀)。




(2)

将其中以IL后缀名的文件用文本编辑器打开,并找到CpuID的IL代码段,可以看到如下:






code.methodprivatehidebysiginstancestring
CpuID()cilmanaged
{
......省略
catch[mscorlib]System.Object
{
IL_00b2:pop
IL_00b3:leave.sIL_00b5

}//endhandler
IL_00b5:ldloc.1
IL_00b6:ret
}//endofmethodSystemSet::CpuID



可以看到IL的catch中,确实什么都没干,就直接跳转出去了。我这里需要在里面加上上面说的提示信息,修改完毕的代码是:




codemethodprivatehidebysiginstancestring
CpuID()cilmanaged
{
......省略
catch[mscorlib]System.Object
{
IL_00b2:pop
IL_00b3:nop

IL_00b4:ldstr“无法生成申请码,请确认系统WMI服务启动正常!"
IL_00b9:callvaluetype[System.Windows.Forms]System.Windows.Forms.DialogResult[System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string)
IL_00be:pop
IL_00c0:nop
IL_00c1:nop
IL_00c2:nop

IL_00c3:leave.sIL_00c5

}//endhandler
IL_00c5:ldloc.1
IL_00c6:ret
}//endofmethodSystemSet::CpuID

对加入的IL稍做说明:


IL_00b4位置的ldstr,意思就是将后面的字符串的对象引用推送到堆栈上;
然后IL_00b9的call就是调用了MessageBox.Show方法,当然,上面的字符串就会被当参数传入;

最后IL_00be处执行pop,将位于顶部的刚才压入的字符串引用从堆栈中弹出;

经过上面压栈,调用,出栈,我们的自定义代码注入进去了,并且又恢复了修改前栈的状态,因此不影响栈的平衡。这里想强调一下,上面的IL写法不是唯一的,比如我们完全可以将那个字符串“无法生成申请码,请确认系统WMI服务启动正常!”用如下代码来代替:

bytearray(E065D56C1F7510623375F78B01780CFFF78B6E78A48BFB7CDF7E57004D0049000D67A1522F54A852636B385E01FF)

其中bytearray里面的代码为汉字的unicode码。

还需要注意的是:

为了计算代码方便,插入了一些nop命令,它用于在修补操作码的情况下填充空间;
由于插入了一些代码,因此IL的地址需要变化,插入代码后的源IL代码的地址应该后推,也就是之前是到IL_00b3就跳出catch,现在变成到了IL_00c3才退出,相差了16(16进制);

因为从IL_00b3开始到最后所有的代码的地址都变化了,如果在之前有代码跳转到这些代码中的话,需要也相应修改!这个程序就有一个地方,就是在try没有报错情况下,他会跳过catch段而直接到原来的IL_00c5(leave.sIL_00b5),因此我将其改为了IL_00c5即可。

(3)

利用Ilasm((.NET的IL汇编程序,Framework自带的),将修改好的IL汇编成为应用程序。这个只提供了命令行版本的。

以下是命令,将这个il编译到newProgram.exe,还有许多参数,可以参照ilasm的连接;




执行过程略去,以下是部分统计信息:




可以看出,没有出现任何错误,提示编译成功。


(4)

运行一下新程序,当WMI服务停止时,确实提示了信息:




经过其他测试,没有影响之前程序的功能,修改就算成功了。


总结:


注册机作为保护别的软件的一种程序,如果自己都保护不好,还谈何保护其他软件呢,起码的反编译措施还是需要做一下的;
try-catch应该是解决软件异常而建立的机制,而不是忽略问题和省事儿的做法,最起码为了用户不至于一头雾水,为了软件发现bug后能够有效反馈和更新,给点儿提示或做个日志也是好的;

编码和测试都有很大欠缺,尤其是和系统环境相关的代码,一方面编码应该给出一些建设性的建议(比如WMI服务没有启动,请启动服务等),另一方面,测试的环境也不应该仅仅限于开发机器;

微软的IL中间语言还是很强大的,入门起来比汇编容易,当然深入了,也比较难;
系统服务不要乱优化~;
事后诸葛亮,也许我也会犯同样的问题,用别人的错误提高自己吧:)

参考:

MSIL汇编程序

MSIL反汇编程序

MSILOpCodes字段

作者:lerit
出处:http://www.cnblogs.com/lerit/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

反馈文章质量,你可以通过快速通道评论:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐