小菜的一次Shellcode跟踪实例
本人对汇编调试方面是真正的菜鸟,甚至连菜鸟都谈不上。然而,一贵有自知之明,二贵有学习精神。从半个月前发现迅雷0day的shellcode的xor加密开始,我才在这感冒的期间(刚好实在不想看也看不下去物理啊),花一些时间对着OD解闷。时至今晚,终于算是了解了,一个shellcode,它运行后究竟干了什么,它是如何“远程下载病毒并运行的”。
shellcode实例:上篇blog文,4楼的网友提供的毒网,晚上上来看到了,心想顺便看一下,结果欲罢不能,居然真的把shellcode的动作都discover出来了。作为之前完全不懂这些的我来说,这实在是一个不大不小的里程碑,值得纪念一下,特别是这个shellcode还是新鲜的。现在把对shellcode代码的分析发上来。
AlphaEncode的代码,xor的代码,都在前面的blog文里提过了,这一个shellcode的相关手法也基本一样,故不再谈(这次是xor 66H),直接从解密后的真正原貌开始。
讲解之前,先说一下我所知的,毒网里的shellcode要在别人的计算机里正常运行,需要注意的关键:
1. 所有shellcode都难以保证自己的代码被加载到哪个内存地址。shellcode要运转,特别是要读取自身代码里的数据(如病毒URL地址,要调用的函数名等常量和变量),就必须要自我定位,即获取自身在内存中的虚拟地址。
一般来说,只要在执行shellcode过程中,能获取到程序当前的入口点EIP值,就能以此定位自己的代码。但是EIP值是不能直接用mov等方法获取的。为此,shellcode编写者利用了一个典型的办法,你在下面的shellcode中将会看到。
2.shellcode必须在所在进程的空间里,调用系统API函数,来完成远程下载文件并运行的目的。那么,怎么获取函数地址?这也就是远程注入代码的又一关键:函数地址重定位问题。
只要API所在的dll被进程所加载,就可以通过由kernel32.dll导出的GetProcAddress来得到函数地址,进而Call之。但是,GetProcAddress本身也是一个函数,我们首先要得到它的地址啊!看起来好像进入死循环了。
既然如此,shellcode作者就必须模拟程序加载dll的方式,为自己的shellcode代码创建一个类似的“输入表”。要得到kernel32.dll中的GetProcAddress函数的地址,也就要读取kernel32.dll的输出表。那么,问题变成了如何定位kernel32.dll的输出表。
问题转变成了一个PE文件结构读取的问题。只要得到kernel32.dll在进程虚拟空间中的基址(DOS文件头,即MZ的位置),就可以在03CH偏移处读到PE文件头("PE"字样地址)相对这个DOS文件头的偏移,并计算出PE文件头地址。PE文件头结构中的78H处,就是输出表的地址了。通过检索输出表,就可以得到GetProcAddress函数的地址的偏移量,进一步变为在虚拟空间中的入口点地址。
问题逐层深入,变成怎么得到“kernel32.dll文件在进程空间中的基址”。硬编码?那你干脆连函数地址都硬编码,不是更省?!shellcode作者们有更好的作法。
讲到dll的基址,一个结构呼之欲出了——PEB!fs:[0x30]!
OK了,shellcode终于从一片“不确定”中抓到了这根可以一条指令就得到的“救命稻草”。其他的,看代码注释吧。不知道PEB的(我也是前两天碰巧刚知道),不知道PE文件结构的(我一直看着它看得头昏脑胀),看前面的部分可能会有些吃力……
代码在记事本里很整齐,copy上来就乱了,想详细看的同样copy回记事本看吧……
调试环境:XP sp2 VC++6.0 将shellcode内容作为main函数里的局部变量,之后嵌入两行汇编,将其地址lea入eax后jmp eax进入了shellcode,所以下面的地址是在堆栈里。代码从解密后真正执行开始:
0012FE2F E9 D5 00 00 00 jmp 0012FF09 ;EIP入栈
0012FE34 5A pop edx ;得到原EIP(0012FF0E)
0012FE35 64 A1 30 00 00 00 mov eax,fs:[00000030] ;PEB头部地址
0012FE3B 8B 40 0C mov eax,dword ptr [eax+0Ch] ;PLDR_DATA
0012FE3E 8B 70 1C mov esi,dword ptr [eax+1Ch] ;InInitializationOrderModuleList
0012FE41 AD lods dword ptr [esi] ;Flink,kernel32.dll的PLDR_MODULE
0012FE42 8B 40 08 mov eax,dword ptr [eax+8] ;kernel32.dll的基址
0012FE45 8B D8 mov ebx,eax ;记住ebx=kernel32.dll的基址(MZ头)
0012FE47 8B 73 3C mov esi,dword ptr [ebx+3Ch] ;kernel32.dll的PE头相对MZ的偏移
0012FE4A 8B 74 1E 78 mov esi,dword ptr [esi+ebx+78h] ;输出表地址
0012FE4E 03 F3 add esi,ebx ;转化成VA,记住esi=输出表VA
0012FE50 8B 7E 20 mov edi,dword ptr [esi+20h] ;AddressOfNames
0012FE53 03 FB add edi,ebx ;记住edi=AddressOfNames数组地址
0012FE55 8B 4E 14 mov ecx,dword ptr [esi+14h] ;NumberOfFunctions
0012FE58 33 ED xor ebp,ebp
0012FE5A 56 push esi
0012FE5B 57 push edi
0012FE5C 51 push ecx
0012FE5D 8B 3F mov edi,dword ptr [edi] &
补充:综合编程 , 安全编程 ,