第一次发表文章,如有失敬之处,请大家多多指教,谢谢。
前几天碰到一个文件,peid查壳提示:不是有效的PE文件。但运行的时候居然可以正常运行,od载入也没有问题,于是想知道peid是怎么辨认一个文件不是有效PE的。我的peid版本是V0.94,壳信息为eXPressor 1.3.0 -> CGSoftLabs [Overlay].
好,我们先来脱壳 od载入,忽略所有异常,隐藏od。下断点bp VirtualFree,断下来五次后,堆栈信息如下:
0012FF40 004DCDDD /CALL to VirtualFree from PEiD.004DCDD7
0012FF44 003D0000 |Address = 003D0000
0012FF48 00000000 |Size = 0
0012FF4C 00008000 \FreeType = MEM_RELEASE
0012FF50 7C930738 ntdll.7C930738
0012FF54 FFFFFFFF
此时取消VirtualFree处的断点,Alt+F9返回到程序领空
004DCDDD |. 8B45 C8 mov eax, dword ptr [ebp-38]
004DCDE0 |. 5F pop edi
004DCDE1 |. 5E pop esi
004DCDE2 |. 5B pop ebx
004DCDE3 |. 83C4 64 add esp, 64
004DCDE6 |. 5D pop ebp
004DCDE7 |. FFE0 jmp eax -->此处跟进
上面的jmp eax跟进后代码如下:
004AA9E9 9C pushfd
004AA9EA 60 pushad
004AA9EB E8 00000000 call 004AA9F0
004AA9F0 5D pop ebp
004AA9F1 B8 07000000 mov eax, 7
利用esp定律,hr esp,然后执行,程序断在:
004AAC69 C2 0C00 retn 0C
004AAC6C 61 popad
004AAC6D 9D popfd -->程序停在这里
004AAC6E ^ E9 8DD3FFFF jmp 004A8000 -->跟过去
004AAC73 8BB5 33FDFFFF mov esi, dword ptr [ebp-2CD]
004AAC79 0BF6 or esi, esi
jmp 004A8000处的代码为:
004A8000 60 pushad
004A8001 E8 00000000 call 004A8006
004A8006 8B2C24 mov ebp, dword ptr [esp]
004A8009 83C4 04 add esp, 4
004A800C 8DB5 4A020000 lea esi, dword ptr [ebp+24A]
004A8012 8D9D 11010000 lea ebx, dword ptr [ebp+111]
004A8018 33FF xor edi, edi
004A801A EB 0F jmp short 004A802B
004A801C FF7437 04 push dword ptr [edi+esi+4]
004A8020 FF3437 push dword ptr [edi+esi]
004A8023 FFD3 call ebx
004A8025 83C4 08 add esp, 8
004A8028 83C7 08 add edi, 8
004A802B 833C37 00 cmp dword ptr [edi+esi], 0
004A802F ^ 75 EB jnz short 004A801C
004A8031 61 popad
004A8032 68 CCA84500 push 0045A8CC
004A8037 C3 retn
在最后的retn处下断点,并删除我们下的硬件断点,F9执行,程序断下来后F8单步走,即到oep
0045A8CC 6A 60 push 60 -->oep
0045A8CE 68 10CF4200 push 0042CF10
0045A8D3 E8 B4180000 call 0045C18C
0045A8D8 BF 94000000 mov edi, 94
0045A8DD 8BC7 mov eax, edi
利用LordPE把文件dump出来,再利用ImportRec修复IAT,程序可以运行。
编写语言为:Microsoft Visual C++ 7.0
下面来进行我们的主要部分。OD载入我们脱壳后的peid程序。连续Shift+F9几次后程序运行,中间可能会跳出几个错误提示,不用理它,点确定继续。
程序运行后,下断点bp CreateFileA,然后把刚才提示不是有效PE的文件拽进peid的窗口。
此时程序断在kernel32.dll的CreateFileA处,取消这个断点,按Alt+F9返回到程序领空,代码如下:
004384B7 50 push eax
004384B8 8B4424 28 mov eax, dword ptr [esp+28]
004384BC 50 push eax
004384BD FF15 FCD14700 call dword ptr [<&kernel32.CreateFile>; kernel32.CreateFileA
004384C3 83F8 FF cmp eax, -1 -->返回到此处
004384C6 8946 04 mov dword ptr [esi+4], eax
004384C9 74 42 je short 0043850D
004384CB 6A 00 push 0
004384CD 50 push eax
004384CE FF15 ECD14700 call dword ptr [<&kernel32.GetFileSiz>; kernel32.GetFileSize -->得到文件大小
004384D4 8B4E 04 mov ecx, dword ptr [esi+4]
004384D7 6A 00 push 0
004384D9 6A 00 push 0
004384DB 6A 00 push 0
004384DD 57 push edi
004384DE 6A 00 push 0
004384E0 51 push ecx
004384E1 8946 10 mov dword ptr [esi+10], eax
004384E4 FF15 F0D14700 call dword ptr [<&kernel32.CreateFile>; kernel32.CreateFileMappingA -->打开我们要查壳的文件
004384EA 85C0 test eax, eax
004384EC 8946 08 mov dword ptr [esi+8], eax
004384EF 74 15 je short 00438506
004384F1 6A 00 push 0
004384F3 6A 00 push 0
004384F5 6A 00 push 0
004384F7 53 push ebx
004384F8 50 push eax
004384F9 FF15 F4D14700 call dword ptr [<&kernel32.MapViewOfF>; kernel32.MapViewOfFile -->把文件内容读进内存
004384FF 85C0 test eax, eax
00438501 8946 0C mov dword ptr [esi+C], eax
00438504 75 0F jnz short 00438515
00438506 8BCE mov ecx, esi
00438508 E8 23FFFFFF call 00438430
0043850D 5F pop edi
0043850E 5E pop esi
0043850F 32C0 xor al, al
00438511 5B pop ebx
00438512 C2 0800 retn 8
00438515 5F pop edi
00438516 5E pop esi
00438517 B0 01 mov al, 1 -->al置1,表示文件读取成功,否则al为0
00438519 5B pop ebx
0043851A C2 0800 retn 8 -->返回到上一层,我们记为A处
00444F35 8B56 04 mov edx, dword ptr [esi+4]
00444F38 6A 01 push 1
00444F3A 52 push edx
00444F3B 8D4C24 18 lea ecx, dword ptr [esp+18]
00444F3F E8 3C35FFFF call 00438480 -->这个call就是我们上面分析的,把文件从磁盘取到内存中
00444F44 84C0 test al, al -->A,即从上面的代码处返回到这里
00444F46 75 16 jnz short 00444F5E -->若al为0则跳走,提示无法打开文件
00444F48 6A 13 push 13
00444F4A 68 5C7A4000 push 00407A5C
00444F4F B9 E48C4600 mov ecx, 00468CE4
00444F54 E8 77BDFEFF call 00430CD0
00444F59 E9 C6000000 jmp 00445024
00444F5E 8B06 mov eax, dword ptr [esi]
00444F60 50 push eax
00444F61 E8 AA4EFFFF call 00439E10 -->消息处理函数
00444F66 8B4C24 24 mov ecx, dword ptr [esp+24]
00444F6A 8B5424 20 mov edx, dword ptr [esp+20]
00444F6E 83C4 04 add esp, 4
00444F71 51 push ecx
00444F72 52 push edx
00444F73 8D4C24 2C lea ecx, dword ptr [esp+2C]
00444F77 E8 84630000 call 0044B300 -->这里主要是检测文件是否是PE,此处我们记为B处,见下面对B处的分析
00444F7C 84C0 test al, al -->若al为0,则表示文件不是有效PE,否则al为1
00444F7E 75 1F jnz short 00444F9F -->根据上一个call返回的al值,al为1则跳走表示是一个PE
00444F80 6A 13 push 13
00444F82 68 487A4000 push 00407A48 -->提示不是有效的pe文件
00444F87 B9 E48C4600 mov ecx, 00468CE4 -->若不是PE文件的话
00444F8C E8 3FBDFEFF call 00430CD0 -->得到函数getTickCount的函数地址
00444F91 8D4C24 10 lea ecx, dword ptr [esp+10]
00444F95 E8 9634FFFF call 00438430 -->释放内存空间
00444F9A E9 85000000 jmp 00445024 -->跳走
B处分析:00444F77 E8 84630000 call 0044B300:
0044B314 8956 08 mov dword ptr [esi+8], edx
0044B317 66:813A 4D5A cmp word ptr [edx], 5A4D -->检测文件开头是不是MZ标志 ----->第1处:检测MZ标志
0044B31C 75 1F jnz short 0044B33D -->不是则跳走,提示非PE
0044B31E 8B4A 3C mov ecx, dword ptr [edx+3C] -->找到PE存在地址,并放入ecx
0044B321 8D0411 lea eax, dword ptr [ecx+edx] -->找到标志位PE的地址,并放入eax
0044B324 3BC2 cmp eax, edx -->比较标志位PE的地址是否有效 ---->第2处:检测PE地址是否有效
0044B326 72 15 jb short 0044B33D -->若无效则跳走
0044B328 81C1 20010000 add ecx, 120 -->PE+120然后定位到data段
0044B32E 3BE9 cmp ebp, ecx -->ebp存放的是文件大小 ---->第3处:若文件大小 小于data段的大小表
0044B330 72 0B jb short 0044B33D -->跳走表示不是pe
0044B332 8946 0C mov dword ptr [esi+C], eax -->把PE标志地址放进esi+c
0044B335 8138 50450000 cmp dword ptr [eax], 4550 -->检测标志位是不是PE ---->第4处:检测"PE"标志
0044B33B 74 07 je short 0044B344 -->是则跳走,不跳表示非PE
0044B33D 5E pop esi
0044B33E 32C0 xor al, al -->al清零,若被清零,则表示非PE
0044B340 5D pop ebp
0044B341 C2 0800 retn 8
0044B344 83C0 04 add eax, 4 -->eax加上4,指向PE FILE HEADER第一个字段machine,在intel机器上此值恒为0x014c
0044B347 8946 10 mov dword ptr [esi+10], eax -->保存machine的地址在esi+10
0044B34A 8B46 0C mov eax, dword ptr [esi+C] -->把PE标志位的地址移回eax
0044B34D 8B56 10 mov edx, dword ptr [esi+10] -->把machine地址移向edx
0044B350 83C0 18 add eax, 18 -->eax+18字节指向PEOptionHeader的第一个字段Magic number,0x010B 表示是一个可执行文件.
0044B353 8946 14 mov dword ptr [esi+14], eax -->把Magic number的地址放到esi+14里
0044B356 0FB74A 10 movzx ecx, word ptr [edx+10] -->把edx+10处为:SizeOfOptionalHeader,两字节0X00E0,放入ecx
0044B35A 03C8 add ecx, eax -->使ecx指向SECTION_HEADER中第一个块表信息的Name段
0044B35C 53 push ebx
0044B35D 8BD8 mov ebx, eax -->把eax移向ebx,使ebx指向PEOptionHeader的头部
0044B35F 894E 18 mov dword ptr [esi+18], ecx -->保存ecx到位置esi+18
0044B362 66:813B 0B01 cmp word ptr [ebx], 10B -->检测ebx处的内容是不是magic number 10B, ---->第5处:检测Magic Number是否为0x010B
0044B367 74 08 je short 0044B371 -->若是10B则跳走,不是则不跳,表示不是PE
0044B369 5B pop ebx
0044B36A 5E pop esi
0044B36B 32C0 xor al, al -->al清零表示PE
0044B36D 5D pop ebp
0044B36E C2 0800 retn 8
0044B371 57 push edi -->保存edi
0044B372 0FB77A 02 movzx edi, word ptr [edx+2] -->把NumberofSections保存到edi
0044B376 8B5424 14 mov edx, dword ptr [esp+14] -->把MZ的地址放入edx
0044B37A 2BD1 sub edx, ecx -->开始处的地址减去第一个块表的地址
0044B37C 03D5 add edx, ebp -->上面得到的值加上文件大小得到除去文件头部的文件大小
0044B37E B8 CDCCCCCC mov eax, CCCCCCCD -->CCCCCCCD移入eax
0044B383 F7E2 mul edx -->edx*eax,高位放入edx,低位放入eax
0044B385 C1EA 05 shr edx, 5 -->edx右移5位.逻辑右移指令
0044B388 3BFA cmp edi, edx -->比较NumberOfSections与得到的值
0044B38A 77 38 ja short 0044B3C4 -->若大于则跳走,表示非PE ---->第6处:对NumberOfSections的上界校验
0044B38C 33D2 xor edx, edx -->edx清零
0044B38E 85FF test edi, edi -->检测NumberOfSections是否为0 ---->第7处:对NunmberOfSections的下界校验
0044B390 7E 1D jle short 0044B3AF -->跳走表示非PE
0044B392 8D41 14 lea eax, dword ptr [ecx+14] -->ecx+14指向PointerToRawData,eax保存PointerToRawData在内存中的地址
0044B395 8B48 FC mov ecx, dword ptr [eax-4] -->ecx保存当前段的SizeOfRawData
0044B398 85C9 test ecx, ecx -->检测取到的数据是否为零
0044B39A 74 04 je short 0044B3A0 -->若为零则跳走,找下一个块表中的信息
0044B39C 3928 cmp dword ptr [eax], ebp -->检测PointerToRawData是否超出文件大小 ---->第8处:对块表中PointerToRawData的校验
0044B39E 73 24 jnb short 0044B3C4 -->若超出则跳走,非PE
0044B3A0 8B4E 10 mov ecx, dword ptr [esi+10] -->把machine的内存地址放入ecx
0044B3A3 0FB749 02 movzx ecx, word ptr [ecx+2] -->取得SectionNumber
0044B3A7 42 inc edx -->edx+1,edx起一个计数器的作用
0044B3A8 83C0 28 add eax, 28 -->指向下一个Section段的PointerToRawData
0044B3AB 3BD1 cmp edx, ecx -->比较找了几个节表
0044B3AD ^ 7C E6 jl short 0044B395 -->若小于的话则继续找
0044B3AF 8B43 10 mov eax, dword ptr [ebx+10] -->ebx指向magic number,ebx+10指向entrypoint,即把entrypoint移到eax
0044B3B2 3B43 3C cmp eax, dword ptr [ebx+3C] -->ebx+3c指向sizeofHeaders,检测oep是否在Header内
0044B3B5 72 09 jb short 0044B3C0 -->若oep小于SizeOfHeaders,那么就跳走 ---->第9处:对oep的校验
0044B3B7 50 push eax -->保存oep
0044B3B8 8BCE mov ecx, esi
0044B3BA E8 11FEFFFF call 0044B1D0 -->找到oep在哪个节里,此处记为C处,见下面对C处的分析
0044B3BF 48 dec eax -->eax得到的值应该为oep所在块的offset-oep所在块的RVAg再加上OEP,若OEP信息有误,则置0,eax减1; 0-1=0XFFFFFFFF
0044B3C0 3BC5 cmp eax, ebp -->比较eax与文件大小
0044B3C2 72 09 jb short 0044B3CD -->如果小于的话则跳走,跳走就对了,表示找到了值,若不跳走的话表示非PE
0044B3C4 5F pop edi
0044B3C5 5B pop ebx
0044B3C6 5E pop esi
0044B3C7 32C0 xor al, al -->al清零
0044B3C9 5D pop ebp
0044B3CA C2 0800 retn 8 -->返回上一层
0044B3CD 8B5424 14 mov edx, dword ptr [esp+14]
0044B3D1 5F pop edi
0044B3D2 5B pop ebx
0044B3D3 896E 04 mov dword ptr [esi+4], ebp
0044B3D6 8916 mov dword ptr [esi], edx
0044B3D8 5E pop esi
0044B3D9 B0 01 mov al, 1 -->如果是pe,则此处置1
0044B3DB 5D pop ebp
0044B3DC C2 0800 retn 8
C处:0044B3BA E8 11FEFFFF call 0044B1D0:
0044B1D0 8B41 14 mov eax, dword ptr [ecx+14] -->ecx+14处的地址指向magic number,地址移向eax
0044B1D3 8B50 3C mov edx, dword ptr [eax+3C] -->magic number +3c指向SizeofHeaders
0044B1D6 57 push edi -->保存edi
0044B1D7 8B7C24 08 mov edi, dword ptr [esp+8] -->把entrypoint放入edi
0044B1DB 3BFA cmp edi, edx -->比较oep与SizeofHeaders ---->第10处:又一次对oep的校验
0044B1DD 73 06 jnb short 0044B1E5 -->如果oep不小于SizeofHeaders就跳走
0044B1DF 8BC7 mov eax, edi -->若oep小于SizeofHeaders那么执行这一步操作
0044B1E1 5F pop edi
0044B1E2 C2 0400 retn 4
0044B1E5 8B41 18 mov eax, dword ptr [ecx+18] -->块表中第一个节的的地址进入eax
0044B1E8 8B49 10 mov ecx, dword ptr [ecx+10] -->machine字段进ecx
0044B1EB 53 push ebx -->保存ebx,即magic number地址
0044B1EC 0FB759 02 movzx ebx, word ptr [ecx+2] -->取NumberofSections放入ebx
0044B1F0 55 push ebp -->保存ebp
0044B1F1 33D2 xor edx, edx -->edx清零
0044B1F3 85DB test ebx, ebx -->检测NumberofSections是否等于0 ---->第11处:又一次检验NumberOfSections
0044B1F5 56 push esi -->保存各个入口点在内存中的存放地址
0044B1F6 894424 14 mov dword ptr [esp+14], eax -->把块表第一个节的节名称放入esp+14
0044B1FA 7E 24 jle short 0044B220 -->如果小于或等于就跳走
0044B1FC 83C0 10 add eax, 10 -->eax+10,使eax指向SizeOfRawData,即当前块的大小
0044B1FF 90 nop
0044B200 8B70 FC mov esi, dword ptr [eax-4] -->把当前块装载时的内存地址VirtualAddress放入esi,
0044B203 3BFE cmp edi, esi -->看oep的地址是否小于当前地址
0044B205 72 11 jb short 0044B218 -->如果小于的话就跳走,挨个比较oep与各区段的rva
0044B207 8B08 mov ecx, dword ptr [eax] -->当前块的大小进ecx
0044B209 85C9 test ecx, ecx -->看看大小是否为0
0044B20B 75 03 jnz short 0044B210 -->不为零则跳走
0044B20D 8B48 F8 mov ecx, dword ptr [eax-8] -->为零的话就将VirtualSize取进ecx
0044B210 8BEF mov ebp, edi -->oep进入ebp
0044B212 2BEE sub ebp, esi -->oep-当前块的RVA放入ebp,这个值就是说从当前RVA开始,到达oep有多少字节,可以用它来与块的大小进行比较,从而知道oep是不是在这个块内
0044B214 3BE9 cmp ebp, ecx -->比较oep-当前块的RVA与当前块大小 ---->第12处:对oep-rva与块值的校验
0044B216 72 11 jb short 0044B229 -->如果小于当前块的大小则跳走,跳走表示oep在当前块内,如果都不跳走表示找不到oep在哪个块里面
0044B218 42 inc edx -->块的计数器
0044B219 83C0 28 add eax, 28 -->指向下一块的大小
0044B21C 3BD3 cmp edx, ebx -->检测是否超过了文件总的块数
0044B21E ^ 7C E0 jl short 0044B200 -->如果还没超过则循环
0044B220 5E pop esi
0044B221 5D pop ebp
0044B222 5B pop ebx
0044B223 33C0 xor eax, eax -->eax清零,表示非PE
0044B225 5F pop edi
0044B226 C2 0400 retn 4
0044B229 8B4424 14 mov eax, dword ptr [esp+14] -->把块表第一个节的节名称装入eax
0044B22D 8D1492 lea edx, dword ptr [edx+edx*4] -->edx中装的是到找到oep所在节的时候所经过的块表数
0044B230 8D0CD0 lea ecx, dword ptr [eax+edx*8] -->上一句加上这一句是找到oep所在节的节名称并把地址放入ecx
0044B233 8B41 14 mov eax, dword ptr [ecx+14] -->ecx+14表示PointerToRawData,把地址放入eax
0044B236 8B51 0C mov edx, dword ptr [ecx+C] -->ecx+c表示VirtualAddress,把地址放入edx
0044B239 5E pop esi
0044B23A 5D pop ebp
0044B23B 2BC2 sub eax, edx
0044B23D 5B pop ebx
0044B23E 03C7 add eax, edi
0044B240 5F pop edi
0044B241 C2 0400 retn 4
至此,peid检测一个文件是否是有效PE的代码分析完成,总的来说有以下几种情况可判定为一个文件是否为PE:
第1处:检测MZ标志
第2处:检测PE地址是否有效
第3处:若文件大小 小于data段的大小表
第4处:检测"PE"标志
第5处:检测Magic Number是否为0x010B
第6处:对NumberOfSections的上界校验
第7处:对NunmberOfSections的下界校验
第8处:对块表中PointerToRawData的校验
第9处:对oep的校验
第10处:又一次对oep的校验
第11处:又一次检验NumberOfSections
第12处:对oep-rva与块值的校验
返回值为al,若al = 0则表示非PE,接下来提示不是有效的PE;若al = 1则表示为PE文件,开始匹配userdb.txt里面的特征码, 如若匹配成功则提示相应的壳信息,如果不成功则提示:Nothing Found.
peid提示我非有效PE的文件,是在第12处校验时出错,那个文件的OEP-第一块的RVA 正好等于第一块的PhysicalSize,而它接下来的命令为jb short 0044B229,所以这个jump if below 跳转没有实现.
在找下一个块表的时候,又发现oep小于当前块表的RVA,所以一直到退出都找不到OEP在哪个块表里面,故al置0,所以才提示非有效PE信息。
明白了原理我们可以来利用相关的方法,修改PE相关信息,达到让PEID误报文件为非有效PE的目的。
好了,文章到这儿就结束了,如果大家发现分析的有不对的地方,欢迎及时指正。谢谢。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课