使用调试器对病毒进行分析在反病毒工作中扮演着十分重要的角色。调试器允许你查看任意内存地址的内容、寄存器的内容以及每个函数的参数。调试器也允许你在任意时刻改变关于程序执行的任何东西。比如你可以在任意时刻改变一个变量的值——前提是你需要获得关于这个变量足够的信息,包括在内存中的位置。在实际的动态调试过程中,最常用的是OllyDBG和WinDbg,前者是病毒分析人员使用最多的调试器,缺点是不支持内核调试,如果想调试内核,WinDbg基本上就是唯一的选择了。虽然IDA Pro也能够进行动态调试,但是它远远不如OD方便。因此在实际分析的过程中,往往是将二者结合使用的。因为如果用IDA Pro在静态分析中遇到了十分抽象的函数,那么用OD动态地执行一下,该函数的功能往往就能一目了然了。关于常用工具OllyDbg和Windbg的使用方法请参考工具篇。
一、常见脱壳
攻击者为了保护攻击代码,提高分析难度,避免被杀毒软件查杀,经常对可执行程序进行加壳来达到以上目的,本节对一些常见的壳的脱壳方法进行介绍(关于壳的基本信息可以参考文件封装篇)。
UPX
将UPX加壳程序使用OD打开时出现下面的对话框,选择“否”。
单步跟踪法
单步跟踪法的原则是实现向下的跳转,越过向上的跳转,如果越过了某一个向上的跳转后,程序跑飞了,那么再次调试到那个跳转语句的时候就实现其跳转,然后再按照实现向下跳转,忽略向上跳转的原则进行调试。
如上图所示,jnz为向上的跳转,而且它的下一句代码也是向上的跳转,这时就需要在jmp的下面设置断点,直接让程序越过这两个跳转。不能在下面的nop语句上f4运行到光标,否则程序会跑飞。
按照这套法则,很快就会找到关键句popad,那么程序的入口就在附近了。如下图所示,并且再看程序的地址,jmp跳转的目标地址为0x004010CC,这条指令所在的地址为0x0040EA0F,可见这是一个很大的跳转,说明0x004010CC即是OEP。(一般有很大的跳转的话,很快就会到达程序的OEP)
开始进行脱壳,这里使用OD插件进行脱壳,选择“插件”->”OllyDump”->”脱壳在当前调试进程”。
因为这里入口点地址正是我们的OEP:10CC,所以不需要修正,直接点击脱壳。如果脱壳后的程序无法运行,还需要进行修正。这里使用方式1脱壳之后,可以运行,不需要修正;使用方式2脱壳之后,无法运行,可以使用Import REConstructor进行修正。在软件的下拉框中选中要进行脱壳的程序(这里使用的是upx.exe),然后修改OEP为我们找到的值:0x10CC。
点击AutoSearch或者GetImports获取导入函数,然后点击showinvalid显示无效的函数,没有的话就点击FixDump,选取要修正的应用程序,之后,会在应用程序所在的文件夹下生成一个原文件名加下划线的应用程序。比如原始文件名为xxx.exe,那么Import REConstructor生成的文件名为xxx_.exe。至此,脱壳完毕。
ESP定律法
设置完断点之后,可以选择Debug->Hardware breakpoints查看硬件断点是否设置成功。
F9运行程序,程序停在关键句附近,接着要删除设置的硬件断点,直接店家上图所示的Delete即可。找到OEP之后再进行脱壳。
内存镜像法
首先需要对OD进行设置,忽略所有异常
在内存窗口中找到程序的.rsrc段,按f2键设置断点,shift+f9运行程序,断下之后,在程序的代码段按f2设置断点。
二、ASPack脱壳
脱ASPack壳可以使用上面所说的单步跟踪法,ESP定律法和内存镜象法,这里以单步跟踪法进行演示,其他两种方法的思路如上所述。
单步跟踪法
单步跟踪法除了上面说的跳转规则外,还有一个规则需要遵从,那就是近call f7进入,远call f8跳过。如果跳过了某个函数之后程序跑飞,那么再次调试时f7进入此函数即可。
如下所示,载入程序后即可看到一个call指令,那么这个函数就需要进入跟踪。
跟踪到如上图所示的地址后,就快到大OEP了,可见这个程序采用的是push/ret的方式进行跳转的,而且跳转的跨度很到,从0x004243BA处跳转到0x00402360。跳转之后即到OEP,接着按照上面所说的方式进行脱壳。
三、FSG脱壳
单步跟踪法
按照前面讲解的原则对fsg壳进行单步跟踪需要细心,拿此例来说,单步跟踪的时候,看到如下所示的语句:
这个跳转并没有实现,但是程序的OEP就在0x004010CC,我们可以跟随到这个地址里面查看代码,直接在这个地址右键,选择follow:
遇到这种情况,直接右键,选择Analysis->Analyse code,就可以将这些机器码转为汇编码:
ESP定律法
使用ESP定律法单步跟踪的时候,要时刻注意着ESP值的变化,当ESP的值发生变化后,就安装上面所说的方法设置硬件访问断点,然后运行程序,程序将停止在oep附近,然后在单步跟踪,很快就将找到程序OEP。
四、FSG变形壳
FSG变形壳在找OEP的方法和FSG的查找方法一样,只是在脱壳的时候有点区别,按照FSG的脱壳方法找到OEP之后,进行脱壳,使用OD的插件进行脱壳,发现程序无法运行,然后再对其导入表进行修正。使用Import REConstructor载入目标程序,修改OEP,点击show Invalid会看到一些无效的程序。
在这些无效的程序上右键,选择cut thunks,接着点击Fix Dump选择我们脱下来的程序。但是修正之后发现仍然无法运行,将修复后的程序使用OD载入,直接F9运行,看到OD中断到下图所示的位置:
第二种方法,将jle改为jmp强制跳转,越过int 0x13:
修改之后,保存文件,就可以运行了。也可以使用16进制工具打开文件,找到相应的位置,修改数据并保存。
五、FSG2.0脱壳
使用ESP定律法和单步跟踪法结合可以很快找到程序的OEP,这里重点讲解脱壳后的修复问题。
使用Import REConstructor载入程序后,点击Get Import可以看到只有37个函数,这是很不正常的,而且没有无效的指针。如果直接进行Fix Dump程序依然无法执行。
可以看到上图中IAT表的大小只有0x94,这是不对的,需要对IAT进行修复,这里介绍两种方法,第一种方法是通过OD,当我们找到OEP的时候,可以看到下图所示的情况:
函数GetCommandLineA所在的地址是0x4063E4,在dump窗口(左下窗口)中定位到这个函数的地址:
向上找,直到遇到一堆00,此时就找到了IAT表的开始地址,如下图所示,开始地址是0x004062E4。
然后在往后找,同样是找00,即IAT的结尾。可以看到它结尾的地址是0x00406E00。
所以IAT的大小为0x00406E00-0x004062E4=0xB1C,接着就把RVA和Size写入Import REConstructor中。
cut掉无效的函数指针,选取我们脱壳后的应用程序进行Dump即可。
第二种方法和第一种类似,都需要找到IAT的RVA进行修改,不同的是不需要计算Size的值,随便写一个比较大的值,比如0x1000就行,这样的话,肯定会有很多无效的函数指针,当我们查看这些无效指针的反汇编代码时,会提示ReadError。
这种情况下,直接把无效的指针cut掉即可。
六、PECompact1.84脱壳
使用单步跟踪法对PECompact壳进行调试的时候,会看到有些向上的跳转,如果我们在这些跳转的下面下断点运行程序的话,程序将跑飞,这个时候,就需要在这些跳转的前面,找一个没有实现的大跳转,跟随进去,设置断点,然后运行程序,使用这种方法,一般很快就可以找到OEP。
例如:
有一个向上的jmp跳转,如果在它下面设置断点并运行程序的话,程序就将跑飞,这时,需要重新载入程序,在这个jmp前面找到一个没有实现的大跳转。
按照这个思路进行调试,即可找到程序OEP,接下来就可以进行脱壳。
七、nspack脱壳
对于nspack的壳,最简单的方法就是使用esp定律法,当然,使用前面所说的其他方法也可以找到OEP。
八、Yoda脱壳(Yodas Protector)
内存镜像法
使用OD载入程序,忽略所有异常,找到内存的.rsrc段,设置断点后运行程序,然后再在代码段设置断点,运行程序,就可直接到达程序的OEP,脱这个壳的重点在于修复方面。按照上述方法进行脱壳,使用Import REConstructor进行修复,发现大部分的指针都是无效的。
点击Show Invalid显示无效指针,然后右键选择Trace Level1进行修复。
修复之后,所有的指针都有效了。
然后再选择目标程序进行fix dump。
我们选择的指针就是第一行那个,库文件是advapi32,函数名为RegSetValueExA:
然后双击刚才选择的指针,查找对应的动态库和函数名。
当然这种方法适合无效指针较少的情况。修复完之后,选择脱壳后的文件进行Fix即可。
其它巧方法
选择SFX->Trace real entry bytewise。
然后载入目标程序,OD将自动停在OEP处。这种方法可以尝试使用,不保证适用于每一种壳。
附加数据的处理
有些加壳的作者会将一些关键的代码放到附加数据里,这样的话,当脱壳之后,附加数据里的代码也会被脱掉,这样就达到了保护代码的作用。这小节就来讲讲如何处理带有附加数据的情况。
首先使用peid对目标程序进行查壳,从peid的显示信息上看,可以看到这是北斗壳,Overlay表示有附加数据。
这个壳比较简单,重点讲解是脱壳后的修复,这里介绍两种方法。
第一种方法是使用16进制编辑器载入原始的目标程序,从数据末尾向上找,一直找到全为0的位置。
从CA00开始复制,一直复制到数据末尾,然后打开修复后的文件,在数据的末尾将复制的数据粘贴进去即可。
第二种方法和第一种类似,只是找的速度相对较快,使用peid查看程序的节信息。
我们需要关注的是R偏移和R大小,找最后一个区段,使用0x400+0xC600=0xCA00,然后在16进制查看器中定位到这个地址,即是我们复制的数据的开始地址。