我在.NET平台诞生了12年后才接触到它,而国内.NET平台的逆向技术兴起于10年前。
我是不幸的,不幸的是起步比前辈们晚了10年左右,十分后悔当初把最好的学习时间奉献给了游戏。最后发现,玩再多游戏也并没什么卵用。
我也是幸运的,幸运的是前人已留下了太多对.NET平台的探索脚印。我只需要跟随着他们的脚步快速前进,而不会绕很多弯路和四处碰壁。
最后感谢那些写过文章供后人学习的前辈们,虽然很多文章都是七八年前的,但受用至今。
此文既不是破解,也不讲脱壳,只是逆向角度分析.NET程序执行原理。仅希望抛砖引玉,待各位大牛们帮我完善接下来的分析过程。
---------------------------------------------------分割线----------------------------------------------------------
调试环境:Windows XP SP3 注:调试环境请使用Win7之前的系统,Win7之后的系统有所变化。
目标对象:一个基于.NET Framework 2.0框架编写的EXE文件
所需工具:IDA,OllyDBG,WinDBG,CFFExplorer,Depends
调试环境:Windows XP SP3 注:调试环境请使用Win7之前的系统,Win7之后的系统有所变化。
目标对象:一个基于.NET Framework 2.0框架编写的EXE文件
所需工具:IDA,OllyDbg,WinDBG,CFFExplorer,Depends
1.首先我们用CFF打开一个.NET程序,找到可选头,这里有个AddressOfEntryPont域,它的值是000030CE,这是一个RVA(相对虚拟地址)。这个AddressOfEntryPont标志着加载器要运行加载PE文件时执行的第一个指令的地址。
2.我们再找到ImageBase这个域,它的值是00400000,ImageBse标志着程序载入内存的理想初始地址(基地址),这里也是为什么采用XP系统来调试.NET程序的原因,有兴趣的朋友可以研究下。
3.我们可以看到AddressOfEntryPont位于.text区段上,我们来计算一下这个000030CE在文件中的物理地址是多少:
物理地址=RVA-Visual Address+Raw Address
由图可知 Visual Address=2000,Raw Address = 200
所以: 物理地址=000030CE-2000+200=12CE
4.我们切换到十六进制编辑界面,找到12CE位置,下图可以看到这几个字符 FF 25 00 20 40 00
5.FF 25 00 20 40 00是什么意思,我们打开OD来看看。在OD中,我们按Ctrl+B,然后输入FF 25可以看到,FF25代表的是汇编指令中的JMP,也就是无条件跳转。后面的00 20 40 00,其实是一个内存地址00402000,所以整句代码连起来就是 jmp 00402000 (意思就是程序加载后直接跳转到00402000这里来执行)。
6.为了验证我们刚才所分析的是否正确,我们现在需要借助WinDBG来进行分析。首先将目标程序载入WinDBG,程序会自动断下来。我们在Command窗口中可以看到如下信息,程序主模块从00400000处开始加载,也就是我们刚才在CFF中看到的ImageBase(基地址)。
7.配合我们刚才的偏移地址000030CE,我们在下面的命令栏中输入指令“u 00400000+000030CE”,这条指令的意思是从地址00400000+000030CE处反编译8条机器码。
8.回车之后看到了一个让我们心动的结果,004030CE处的前6个字节反编译对应的汇编代码为 Jmp 00402000,正好验证了我们前面的分析。
9.证实了前面的分析完全正确,接下来我们我们去探索一下00402000究竟什么。下一步我们在命令栏中输入dps WindowsFormsApplication3+0x2000 也可以输入dps 00402000
10.回车之后看到如下信息mscoree!CorExeMain,可以看到00402000这个地址代表的是mscoree.dll这个模块的_CorExeMain函数,位置在79004ddb处。
11.如果WinDBG看上去并不是那么直观,那我们继续借助OD来分析吧。我们用OD中载入程序,Ctrl+G转到我们在WinDBG中看到的地址79004ddb处,然后F2在这个地址下断点。
12.重新载入并运行当前程序,程序执行到我们刚才下断点的地方就会自动断下来。这时候留心观察寄存器里EIP的值(为什么要观察EIP,因为EIP标志着程序将执行的下一句指令),我们可以很清楚地看到,程序下一句将要执行的是mscoree.dll这个模块中的_CorExeMain函数。
13.既然程序的加载需要调用到mscoree.dll中的_CorExeMain函数,那么我们很容易猜想到,_CorExeMain函数会被保存到程序的导入表中,程序运行的时候进行调用。我们在CFF中切换到到程序IAT(导入表),可以很清晰地看到,.NET程序导入了mscoree.dll的_CorExeMain函数,这也是.NET程序唯一的导入函数。
14.当然我们也可以借助于别的分析工具来分析当前程序依赖的DLL。
我们把目标程序用Depends载入,可以很清晰地看到该程序仅仅只依赖mscoree.dll中的_CorExeMain函数。
15.综上所述,PE Loader加载了.NET镜像文件之后,首先会执行一句JMP mscoree. _CorExeMain(这里特别指明是mscoree中的_CorExeMain是有原因的,今后有时间再讲述为什么特别指明mscoree这个模块),借助微软的mscoree.dll来启动我们的.NET托管运行环境。
_CorExeMain这个函数究竟做了哪些工作,如何一步一步启动托管运行环境的,等有时间继续写吧。