第13章 脱壳技术
任何事物都有两面性,有加壳,就必有脱壳。加壳与脱壳有着紧密的联系,一些脱壳技术是针对加壳而产生的,脱壳的进步,又迫使加壳软件不断创新发展。现在越来越多的软件都加壳保护,脱壳有时是分析一个软件不可缺少的步骤。
……略……
13.5 DLL文件脱壳
DLL是Dynamic Link Library(动态链接库)的缩写形式,是一个共享函数库的可执行文件。DLL文件的脱壳与EXE文件步骤差不多,DLL文件多了个基址重定位表等要考虑。
13.5.1 寻找OEP
当DLL被初次映射到进程的地址空间中时,系统将调用DllMain函数,当卸载DLL时也会再次调用DllMain函数。也就是说,DLL文件相比EXE文件运行有一些特殊性,EXE的入口点只在开始时执行一次,而DLL的入口点在整个执行过程中至少要执行两次。一次是在开始时,用来对DLL做一些初始化。至少还有一次是在退出时,用来清理DLL再退出。
工作,如IAT初始化等。退出时再次进入入口点时,外壳将跳过相关的初始化代码,这时程序代码流程会短些。所以找OEP也有两条路可以走,一是载入时找,二是在退出时找,退出时流程短些,相对来说更容易找到OEP。
用第16章编写的加壳工具对实例EdrLib.dll进行加壳处理。用LordPE查看其PE信息,获得EntryPoint为D000h,ImageBase为400000h,区块的信息如图13.41所示。
|
| 图13.41 查看区块信息 |
DLL本身不能直接执行,但可以调用LoadLibrary将DLL的文件映像映射到调用进程的地址空间中,退出时调用FreeLibrary卸载DLL。为了调试DLL,OllyDbg提供了一个类似原理的辅助程序loaddll.exe,这个程序被压缩存放在资源段里,如果OllyDbg所在文件夹内没有loaddll.exe,则会释放这个文件。用OllyDbg打开DLL,将会询问启动loaddll.exe,如图13.42所示。然后链接库被加载并停在程序的入口(ModuleEntryPoint),现在就可以正常调试DLL程序了。
|
| (点击查看大图)图13.42 提示是否启动loaddll.exe |
OllyDbg加载EdrLib.dll将停在外壳代码第一行。细心的读者会发现,此时EdrLib.dll并没有被映射到默认的400000h内存地址中,而是选择了另一个地址。按“Alt+M”键打开内存映像窗口,将会发现EdrLib.dll被映射到地址E20000h,如图13.43所示。
|
| (点击查看大图)图13.43 查看DLL被映射的地址 |
注意:由于DLL被映射的地址是由系统动态分配的,因此在读者系统中显示的地址可能与本书不同,操作时以当前系统基址为准。下面将忽略所有基地址注释,读者操作时以实际的映像地址来操作。
外壳的入口代码如下:
00E2D000 pushad 00E2D001 call 00E2D0C8 …… 00E2D0C9 sub ebp, 6 00E2D0CF mov eax, dword ptr [ebp+C0] ;[ebp+C0]是一个计数器变量,此时为0 00E2D0D5 or eax, eax ;如果是0,表示首次加载,将执行初始化代码 00E2D0D7 je short 00E2D0E0 00E2D0D9 push ebp ;dll文件退出时走这里 00E2D0DA jmp dword ptr [ebp+C4] 00E2D0E0 inc dword ptr [ebp+C0] ;[ebp+C0]计数器变量加1
|
此时可以按正常的思路跟踪代码寻找OEP,由于DLL退出时也会来到入口一次,本例演示一下DLL退出时寻找OEP的过程。先在外壳的入口00E2D000处按F2键设置一个断点,按F9键数次让DLL跑起来,DLL装载成功后,loaddll.exe将出现如图13.44所示的界面。
|
|
图13.44 加载DLL成功后的界面 |
然后将如图13.44所示的窗口关闭,DLL将被卸载,将再次中断在外壳的入口点处。
00E2D000 pushad ;退出时,会再次来到这里 00E2D001 call 00E2D0C8 …… 00E2D0C9 sub ebp, 6 00E2D0CF mov eax, dword ptr [ebp+C0] ;[ebp+C0]的值此时是1 00E2D0D5 or eax, eax 00E2D0D7 je short 00E2D0E0 00E2D0D9 push ebp 00E2D0DA jmp dword ptr [ebp+C4] 00E2D0E0 inc dword ptr [ebp+C0] ;dll文件退出时将走这条线路
|
来到外壳代码的第二段,这段是解压还原、初始化原程序,为避免重复初始化,第二次进入入口点后将会跳过这些初始化代码。
003E0000 call 003E0005 003E0005 pop edx 003E0006 sub edx, 5 003E0009 pop ebp 003E000A mov eax, dword ptr [edx+359] ;计数器变量 003E0010 or eax, eax 003E0012 je short 003E001A 003E0014 popad 003E0015 jmp 0E21240 ;dll退出时从这里进入OEP
|
跳过外壳初始化代码后,将直接到OEP处。
003E0283 push 0E21240 003E0288 retn ;返回到OEP,即1240h(RVA值) |
此时,OEP的RVA值 = E21240 h-映像地址=E21240 h-E20000h = 1240h。
技巧:一般加壳软件用同一套外壳代码处理EXE和DLL,因此在处理EXE文件时,外壳里也有判断是不是多次加载的代码,找到这些跳转,强行改变,这样程序虽不能运行,但能很快定位到OEP相关的代码。
OllyDbg加载某些外壳的DLL文件,不能正常地暂停在外壳的入口点,会直接运行起来。碰到这种情况,可以将外壳的入口点改成死循环,机器码是EBFEh。一个示例如下:
00401000 EB FE jmp short 00401000 |
OllyDbg加载修改后的DLL,由于入口死循环,OllyDbg的CPU窗口显示白屏,按F12键让OllyDbg暂停即可看到代码,再将入口代码恢复成原指令,又可单步调试了。详细操作见光盘映像文件中动画演示。