给没有源代码的.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.
从程序中填写完注册码后点击的那个注册按钮入手,那个按钮点击后提示了“注册码错误”,根据这几个字,找到了:
显然,requestCode就是申请码那个TextBox,answerCode就是注册码那个TextBox,完成找ID号的工作。
3.
然后去找为requestCode赋值的语句:
可以看出来,是CpuID方法用于产生申请码的,应该是根据cpu等硬件信息产生的,这就可以为每台计算机产生不同的申请码。最终经过处理返回text2就是申请码,用try-catch代码块,把所有可能错误都忽略了,难怪没申请码但是也没报错~~~
4.
可能我机子的某个服务没有启动造成的,而Get方法却使用了此服务。Get的智能提示是:
5.
viewsourceprint?
得到程序的IL代码;
将需要的提示信息的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即可。
注册机作为保护别的软件的一种程序,如果自己都保护不好,还谈何保护其他软件呢,起码的反编译措施还是需要做一下的;
try-catch应该是解决软件异常而建立的机制,而不是忽略问题和省事儿的做法,最起码为了用户不至于一头雾水,为了软件发现bug后能够有效反馈和更新,给点儿提示或做个日志也是好的;
编码和测试都有很大欠缺,尤其是和系统环境相关的代码,一方面编码应该给出一些建设性的建议(比如WMI服务没有启动,请启动服务等),另一方面,测试的环境也不应该仅仅限于开发机器;
微软的IL中间语言还是很强大的,入门起来比汇编容易,当然深入了,也比较难;
系统服务不要乱优化~;
事后诸葛亮,也许我也会犯同样的问题,用别人的错误提高自己吧:)
参考:
作者:lerit
出处:http://www.cnblogs.com/lerit/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
反馈文章质量,你可以通过快速通道评论:
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("注册码错误!"); } }
显然,
3.
然后去找为
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中加一个提示语句:
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字段
作者:出处:
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
反馈文章质量,你可以通过快速通道评论:
相关文章推荐
- 解决:无法连接到 WMI 提供程序。您没有权限或者该服务器无法访问。 (转帖:http://www.cnblogs.com/furenjun/archive/2011/09/01/2161873.html)
- 转:自动更新程序源码下载(C#.Net) http://www.cnblogs.com/jenry/archive/2006/08/15/477302.html
- .NET 异步处理(转载自 http://www.cnblogs.com/czy/archive/2010/01/14/1648139.html)
- .net remoting http://www.cnblogs.com/wayfarer/archive/2004/07/30/28723.html
- C# 启动外部程序的几种方法(转自:http://www.cnblogs.com/Slxj/archive/2012/01/12/2320531.html)
- asp.net(C#)调用C++程序并交互操作【转】http://www.cnblogs.com/greatverve/archive/2010/06/07/Csharp-transfer-Cpp.html
- .Net中的设计模式——从实例谈OOP、工厂模式和重构 http://www.cnblogs.com/wayne-ivan/archive/2006/09/07/496920.html
- 如果你的数据库文件只剩下数据文件没有日志文件时,如何附加 (摘自:http://www.cnblogs.com/yukaizhao/archive/2008/07/23/sp_attach_single_file_db.html)
- .net使用DotNetCharting控件生成报表统计图总结 (http://www.cnblogs.com/dreamof/archive/2008/07/18/1245887.html)
- Kerberos(转:http://www.cnblogs.com/jankie/archive/2011/08/22/2149285.html)
- C# .NET锁屏程序(顺带屏蔽任务管理器)(转)http://www.cnblogs.com/salty/archive/2011/03/29/1999142.html#2391595
- .net文档生成工具2.0 支持自定义文档生成器【转:http://www.cnblogs.com/lucc/archive/2008/09/05/1284762.html】
- JS日历控件集合----附效果图、源代码【转:http://www.cnblogs.com/yank/archive/2008/08/14/1267746.html】
- 《丁丁历险记系列之委托》-- 摘自http://www.cnblogs.com/xfxxx/archive/2010/04/03/1703839.html
- c/c++编译过程http://www.cnblogs.com/hktk/archive/2012/09/11/2680495.html
- [一个简单的.NET逆向工程]给没有源代码的.NET程序打补丁
- http://www.cnblogs.com/ITtangtang/archive/2012/05/21/2511749.html
- C# winform 登录窗体 记住密码的实现 --来源http://www.cnblogs.com/ou444/archive/2011/09/13/2174911.html
- CImage类 from http://www.cnblogs.com/afarmer/archive/2012/03/31/2427273.html
- http://www.cnblogs.com/chio/archive/2007/09/10/888260.html