获取入口点
调试程序是首先要找到程序的入口点,方便查看。可执行文件(.exe)的入口点通常位于 <Module>
类或具体的启动类中,如 Program
类的 main
方法。Program
类也称为程序的主类。寻找入口点有以下几种方法:
- 在解决方案资源管理器中寻找类似
Program.cs
或包含static void Main(string[] args)
方法的文件。 - 在 dnSpy 的搜索框中输入
Main
,查找可能的入口方法。 - 在程序集名称
apri_cli (1.0.0.0)
或 物理文件名apri_cli.exe
上,点击右键选择“转到入口点(E)”即可。
知识点:
解决方案资源管理器中:
第一层标题是 程序集 名称,这里是
apri_cli (1.0.0.0)
第二层标题是 物理文件名 名称,这里是
apri_cli.exe
。这些数据都存储在.NET程序编译时生成的程序集元数据中,不会改变。
附加进程
使用 dnSpy 附件进程时,通过 调试==>附加到进程(P) 弹出进程列表,在列表中选择要附加的进程即可。
这里新手要关注的点是,没有附加进程前,dnSpy 的页面没有任何内容,如下所示:
进程列表:
通过进程列表附加进程后,其实窗口中也不显示任何内容,但这时候其实已经附加成功。
这时可以通过 调试==>窗口==>模块 打开“模块窗口”,在其中搜索要调试的模块。
搜索目标模块:
找到目标模块后双击,即可在“程序集资源管理器”出现要调试的目标模块。定位到目标代码所在行下断点,之后运行程序即可调试。
返回当前执行指令行
使用 dnSpy 逆向调试程序,查看反编译出的代码时,经常会跳转到调用代码或上层代码中,查看具体的代码实现。这时要想快速返回当前执行的指令行(比如在 main()
函数中中断的位置),可通过堆栈的内容快速返回。
-
打开“调用堆栈”窗口:通过点击菜单栏的“调试”>“窗口”>“调用堆栈”。
-
选择当前执行的方法:在“调用堆栈”窗口中,找到位于最顶部的条目,这通常表示当前执行的方法。
-
双击条目:双击第一行,dnSpy 将自动跳转到反编译窗口中的当前执行的指令行。
修改 IL 字节码
调试过程中,有时需要修改代码的执行流程,可以采用修改 IL 字节码的方法。选中要修改的代码行,右键选择“编辑IL指令(S)”。
弹出的窗口中,比其它行代码颜色稍微深一点的 IL 指令,就对应之前选中的反编译代码。
观察具体的操作码,看到三行中的第二行是 call 指令,第三行是 brtrue.s
,说明就是要修改的目标 IL指令。点击该指令,在弹出的下拉列表中选择类似的 brfalse.s
指令,之后点“确定”。
可以看到反编译的代码已经改变,之前的非运算符 !
已经没有了。
显示私有类
这里要感谢同事的帮忙,提供了一个好的解决方法。
调试某样本时,发现 setServerIp()
方法中对 Program.<setServerIp>d__9
实例化了一个对象,但没有具体的实现代码,点击 new Program.<setServerIp>d__9()
中的类名称时,又跳转回 setServerIp()
函数自身。
通过GPT得知,这行代码是编译器生成的,用于实现异步方法或迭代器方法的部分。在C#中,当你写一个使用了await
关键字的异步方法或者一个yield return
语句的迭代器方法时,编译器会将这个方法转换成一个状态机。这个过程是自动的,旨在处理异步操作的执行流程或迭代器的状态管理。
Program.<setServerIp>d__9 <setServerIp>d__ = new Program.<setServerIp>d__9();
这里的Program.<setServerIp>d__9
是编译器生成的一个类,用于实现名为setServerIp
的方法的状态机。这个类名中的<setServerIp>d__9
是编译器生成的,其中setServerIp
是原始方法的名称,d__9
是编译器用来标识这个特定状态机的内部名称。这种命名约定是编译器内部使用的,以确保名称的唯一性。
Program
是包含setServerIp
方法的类。<setServerIp>d__9
是编译器为setServerIp
方法生成的状态机类。- 最后,
<setServerIp>d__ = new Program.<setServerIp>d__9();
这行代码创建了这个状态机的一个实例。
这种转换使得编译器能够自动处理异步方法的挂起和恢复,或者在迭代器方法中处理集合的迭代过程。这是C#语言支持异步编程和迭代器的一种底层机制,并且这一转换对于开发者来说是完全透明的。在使用像dnSpy这样的工具进行反编译时,你会看到这种由编译器生成的代码,但在原始的C#源代码中,这部分是看不到的。
但要调试恶意样本分析其功能时,我们必须知道该函数具体执行了哪些功能,这时可以通过修改 dnSpy 的设置解决这个问题。
打开“显示编译器生成的隐藏类型和方法”选项后,setServerIp()
方法变成了 <setServerIp>d__9
状态机类,可以看到具体实现代码如下: