您的位置:首页 > 其它

使用DNSpy 调试.net 服务

2020-07-29 09:36 711 查看

背景:

某个.net 应用以windows服务的形式对外提供服务,然后通过IIs的web应用和该服务进行交互,通过web的方式向用户提供业务。该项目有点年头,目前相当于自己维护,没厂家支持,而且自定义了很多开发在上面,但是有一个棘手的问题是该项目没有提供所有源码的,类似服务程序,使用的相关DLL还进行了混淆加密,所以其内部逻辑通过反编译看源码会非常绕,而且就算反混淆后,代码的组织和命名也会差很多,看起来会非常的累。


症状:

1. 某次为了和生产保持一致,就地升级了测试环境的操作系统(2012 r2 到2016),这个服务死活其不来,还好做了VM的快照。这个程序错误的时候就在日志里记了一条“输入字符串格式不对”,没头没脑,也不包含stack trace ,不知道哪里报的错。后来没有办法,拿快照回退,初步判定为操作系统兼容性问题(注意是.net 4.x 开发的,虽然有点怀疑这个结论)

2. 另外的同事为了熟悉这个系统,在新的测试环境,重新部署了一套这个系统(操作系统使用的2019),系统可以运行,没有问题。(所以我很怀疑1 中的兼容性问题,但目前我找不到原因)

3. 恢复快照的测试环境,后又经历过两次服务起不来的问题。有次打了补丁,有次是重启就不行了。但是因为没有太多思路去排错(因为配置什么的都没有变过,关键是也没有什么有用的日志协助排错,而且没技术支持,没源码),后面偷懒直接恢复了快照解决。

4.  测试环境因为需要更新补丁,重启,结果服务又挂了。还是一样的报错

尝试解决:

实在忍不了,我必须得把这个原因找出来,不然在生产上出现,真的没法处理,而且到时候肯定手忙脚乱。初步的想法是通过调试器直接调试这个服务,然后找到报错的位置,大致按逻辑找到可能出错的位置。从而找出到底是配置问题、还是兼容性问题,还是其他的什么。


由于是个.net 应用,带调试功能,且能反编译的DNSpy 就属于我的首选工具了。支持直接查看.net 语言编译的exe、dll 文件,而且支持直接调试,而且可以就地对dll、exe的代码进行修改并保存。


结果调试一个windows 服务没有你想的简单。当使用DNSpy调试运行一个windows 服务时,由于其不是通过net start 或者windows 服务管理器执行的,所以报这个错误。

这可如何是好,这个服务起来就挂掉,我没有通过attach 到进程的方式来调试啊。还好微软的文章提供了一些思路。https://docs.microsoft.com/en-us/dotnet/framework/windows-services/how-to-debug-windows-service-applications#debugging-tips-for-windows-services

两个选项:

1. 在服务的onstart或者main 里增加sleep ,延长服务执行实际代码的时间,在sleep 没报错的时候,使用调试器attch上去(加sleep 也就一两行代码的改动,应该可以直接使用dnspy搞定,即使混淆过了,但是程序入口这些还是很容易找到的,因此选择了该方法)

2. 把服务的程序逻辑改写,重新编译成一个console 程序。(没源码,修改较多,看来不太适合我的情况)


Dnspy 打开服务程序的exe文件,定位到onstart 方法,然后点击右键,选择”编辑方法(C#)“

增加一个sleep,我这里大概5秒钟,可以手快的执行完attach,另外在onstart处下断点。

保存修改。然后再DNspy的文件菜单中选择保存模块。这样会把更改写入到新的exe中(注意保留备份)


在服务管理器中启动服务。


使用dnspy 调试--附加到进程

注意点: 这里附加到进程能列出的是dnspy执行账号下的进程,加入你的服务以其他用户身份执行,可能列举不出来,所以可能要更换dnspy的运行账号。


后续由于涉及到应用内部的内容,就不写出来了,大概定位到是程序加载某些自生成的配置文件(二进制),然后某些目录的配置文件不知被谁拷贝了一份,名称为xx.yy-复制,导致程序加载报错(可能是文件名问题),但是报错内容只有一条“输入格式不正确”,因为程序本身对错误进行了catch ,然后没有记录下面stack信息,调试器里其实可以看到。虽然代码进行了混淆,但是还可以定位到大概的位置。

在 System.Version.VersionResult.SetFailure(ParseFailureKind failure, String argument)  
在 System.Version.TryParseComponent(String component, String componentName, VersionResult& result, Int32& parsedComponent)  
在 System.Version.TryParseVersion(String version, VersionResult& result)  
在 System.Version.Parse(String input)  
在 System.Version..ctor(String version)  
在 OO.Server.OOProcess.a(String A_0, Version& A_1)  
在 OO.Server.OOProcess.b(String A_0)  
在 l.c(String A_0)  
在 l.j()  
在 l.b(Boolean A_0)  
在 l.b(Boolean A_0)  
在 s4.a(Boolean A_0, Boolean A_1)  
在 aje.g()  
在 OO.Server.Server.Start()


总结分析:

综合以上多个历史症状,可以得出问题原因是复制的自动生成的配置文件导致了应用crash,只是服务不重启的时候,这个配置文件不会重新加载,所以无论是升级操作系统、打补丁、重启都是由于间接重启了服务导致配置重新加载,然后触发错误。

更多思考和问题:

这个程序是一个.net 反编译和更改代码相对简单,但是如果服务是个C程序或者C++编译的,如何进行调试呢?如何增加sleep 呢,或者有其他更好的方法进行调试。而且程序本身catch了异常,退出也是相对优雅的退出,不能通过在程序无处理异常时打开调试器的方法。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: