一个很老的保护壳,对比看看才几年时间壳的技术已经日新月异了。。。
本文适合脱壳爱好者初级向中级进军时参考一下,见笑了~
这里的测试样本是经典的Win98记事本,用PEiD看下:PESHiELD 0.25 -> ANAKiN
由于一般的加密壳都会用系统的异常处理更改程序的运行流程,我们取消OD的忽略异常选项,可以很快到达程序的关键点。当然,也可以单步跟踪,遇到回跳就F4带过,很快就能到达异常中断。
先F9直接运行,遇到异常中断时用SHIFT+F9步过,发现经过2次“非法使用寄存器”异常后,OD会崩溃,看来是反调试检测到OD了。
重新载入程序,在第二次异常中断时停下来,观察堆栈:
0013FF9C 0013FFE0 指向下一个 SEH 记录的指针
0013FFA0 0040D4AC SE处理程序
程序中断后会转到0040D4AC处继续执行,我们在这一句下断,用SHIFT+F9运行后会中断在这一句:
0040D4AC mov eax, dword ptr [esp+C]
0040D4B0 add dword ptr [eax+B8], 4 ; 注意这一句,该处保存程序继续执行的入口点
0040D4B7 push ebx
0040D4B8 xor ebx, ebx
0040D4BA mov dword ptr [eax+4], ebx
0040D4BD mov dword ptr [eax+8], ebx
0040D4C0 mov dword ptr [eax+18], 155
0040D4C7 mov dword ptr [eax+C], ebx
0040D4CA mov dword ptr [eax+10], ebx
0040D4CD pop ebx
0040D4CE xor eax, eax
0040D4D0 retn ; 这里程序会返回到系统领空
这一段代码也是更改程序流程的一种方式,程序执行到0040D4D0处的retn后会迷失在系统领空,而在前面0040D4B0处会把程序流程的入口保存到[eax+B8]中。我们直接在[eax+B8]=0040D4DB处下断,F9后中断下来。去掉花指令后就剩下下面的了:
0040D4DB pop dword ptr fs:[ebx]
0040D4DE mov esp, eax
0040D4E0 mov eax, dword ptr [ebp+12A7] ; EAX=00400000,Base Image
0040D4EB add eax, dword ptr [eax+3C] ; [eax+3C]=PE Header
0040D4F1 mov ecx, eax ; 保存PE Header
0040D4F7 mov esi, dword ptr [eax+8] ; TimeDateStamp
0040D4FD xor edi, edi
0040D502 dec edi
0040D506 mov edx, dword ptr [eax+28] ; OEP
0040D50C xor ebx, ebx
0040D512 mov eax, dword ptr fs:[30]
0040D51B test eax, eax
0040D520 js short 0040D53A
0040D525 mov eax, dword ptr [eax+C]
0040D52B mov eax, dword ptr [eax+C]
0040D531 mov dword ptr [eax+20], ebx
0040D561 mov ebx, dword ptr [ebp+124F]
0040D56A add ebx, ecx ; NOTEPAD.00400080
0040D56F movzx eax, byte ptr [ebx]
0040D575 mov ah, al
0040D57A add dword ptr [ebp+1243], eax
0040D580 lea eax, dword ptr [ebp+10F3] ; EAX="VirtualAlloc"
0040D589 lea ebx, dword ptr [ebp+127B]
0040D594 call 0040E1B6 ; GetProcAddress VirtualAlloc
0040D599 mov eax, dword ptr [ebp+12A7]
0040D5A3 add eax, dword ptr [ebp+1273] ; eax=00406000
0040D5AD mov dword ptr [ebp+1227], eax
0040D5B3 mov ebx, dword ptr [ebp+12AB]
0040D5BC lea esi, dword ptr [ebp+1100] ; esi="NuxHmzvcgd\ywkauz",加密字符
0040D5C2 call 0040DF44
0040D5CA call 0040DF44
0040D5D2 call 0040DF44 ; 字符串解密
0040D5DC lea eax, dword ptr [ebp+1100] ; eax="GetCurrentProcess"
0040D5E5 lea ebx, dword ptr [ebp+125B]
0040D5EB call 0040E1B6 ; GetProcAddress GetCurrentProcess
0040D5F3 lea eax, dword ptr [ebp+1112] ; eax="WriteProcessMemory"
0040D5FE lea ebx, dword ptr [ebp+125F]
0040D604 call 0040E1B6 ; GetProcAddress WriteProcessMemory
0040D60D lea eax, dword ptr [ebp+1125] ; eax="CreateFileA"
0040D617 lea ebx, dword ptr [ebp+126B]
0040D61D call 0040E1B6 ; GetProcAddress CreateFileA
0040D622 push 0
0040D627 push 80
0040D62F push 3
0040D634 push 0
0040D639 push 3
0040D63E push 800000
0040D646 lea eax, dword ptr [ebp+113A] ; eax="\\.\TRW.VXD"
0040D650 push eax ; NOTEPAD.0040E13A
0040D654 mov eax, dword ptr [ebp+126B] ; kernel32.CreateFileA
0040D65D call 0040DDBB ; call CreateFileA
0040D665 cmp eax, -1 ; 检测TRW
0040D66B jnz 0040D75B
0040D671 lea eax, dword ptr [ebp+115F] ; eax="CreateThread"
0040D67A lea ebx, dword ptr [ebp+116C]
0040D684 call 0040E1B6 ; GetProcAddress CreateThread
0040D68C lea eax, dword ptr [ebp+1174] ; eax="ExitThread"
0040D695 lea ebx, dword ptr [ebp+117F]
0040D6A0 call 0040E1B6 ; GetProcAddress ExitThread
0040D6A8 mov dword ptr [ebp+81F], ebp ; NOTEPAD.<模块入口点>
0040D6B1 lea edx, dword ptr [ebp+1187]
0040D6BA lea ecx, dword ptr [ebp+81E]
0040D6C3 push edx ; NOTEPAD.0040E187
0040D6C7 push 0
0040D6CC add edx, 4
0040D6D2 push edx ; NOTEPAD.0040E18B
0040D6D6 push ecx ; NOTEPAD.0040D81E
0040D6DA push 0
0040D6DF push 0
0040D6E4 mov eax, dword ptr [ebp+116C] ; kernel32.CreateThread
0040D6ED call 0040DDBB ; call CreateThread
0040D6F5 push dword ptr [ebp+117F] ; kernel32.ExitThread
0040D6FB inc dword ptr [ebp+1183]
0040D701 retn ; 返回系统领空,不知道怎么返回
执行到这里就需要注意下了,程序用CreateThread建立了一个新线程并跳到新线程执行,而当前进程就迷失在系统领空了。而创建的新线程的入口点即为0040D6D6这一句保存的0040D81E。直接在0040D81E处下断,F9中断下来:
0040D81E mov ebp, offset <模块入口点>
0040D823 lea esi, dword ptr [ebp+10C5]
0040D829 sub esp, 20
0040D82C mov edi, esp
0040D82E mov ecx, 8
0040D833 rep movs dword ptr es:[edi], dword ptr [esi]
0040D835 mov eax, dword ptr [ebp+1183]
0040D83B or eax, eax
0040D83D je short 0040D835
0040D83F push 0
0040D844 push 80
0040D84C push 3
0040D851 push 0
0040D856 push 3
0040D85B push 800000
0040D863 lea eax, dword ptr [ebp+1131] ; 地址=0040E131, (ASCII "\\.\SICE")
0040D86D push eax ; NOTEPAD.0040E131
0040D871 mov eax, dword ptr [ebp+126B] ; kernel32.CreateFileA
0040D87A call 0040DDBB ; call CreateFileA
0040D882 cmp eax, -1 ; 检测SICE
0040D888 jnz 0040D772
0040D891 cmp byte ptr [ebp+121B], 1
0040D89B je short 0040D907
0040D8A2 mov ecx, 0C8
0040D8AA push ecx
0040D8B0 loopd short 0040D8A7
0040D8B6 mov eax, 64
0040D8BB call 0040DC88
0040D8C0 add eax, 2710
0040D8C5 neg eax
0040D8C7 push eax
0040D8C8 mov ecx, 0E
0040D8CD push ecx
0040D8CE loopd short 0040D8CD
0040D8D0 push 5A4D
0040D8D5 mov ebx, esp
0040D8D7 lea eax, dword ptr [ebp+12AB]
0040D8DD push eax
0040D8DE push 360
0040D8E3 push ebx
0040D8E4 push dword ptr [ebp+12A7]
0040D8EA mov eax, dword ptr [ebp+125B] ; kernel32.GetCurrentProcess
0040D8F0 call 0040DDBB
0040D8F5 push eax
0040D8F6 mov eax, dword ptr [ebp+125F] ; kernel32.WriteProcessMemory
0040D8FC call 0040DDBB
0040D901 add esp, 360
0040D907 lea esi, dword ptr [ebp+15C8] ; "NON-COMMERCIAL!!"
0040D90D mov edx, 14356241
0040D912 lods dword ptr [esi]
0040D913 xor edx, eax
0040D915 rol edx, 1
0040D917 lods dword ptr [esi]
0040D918 add edx, eax
0040D91A ror edx, 1
0040D91C lods dword ptr [esi]
0040D91D add edx, eax
0040D91F lods dword ptr [esi]
0040D920 xor edx, eax
0040D922 cmp edx, 8446AB4
0040D928 jnz <模块入口点>
0040D92E cmp byte ptr [ebp+1219], 1
0040D935 jnz short 0040D953
0040D953 mov esi, dword ptr [ebp+1253]
0040D959 add esi, ebp
0040D95B mov ebx, dword ptr [ebp+1243]
0040D961 lods dword ptr [esi] ; --开始还原401000处的代码--
0040D965 or eax, eax
0040D96A je short 0040D9BE ; 是否继续循环?
0040D96F mov edi, eax
0040D974 xor edi, ebx
0040D979 rol ebx, 1
0040D97B add ebx, edi
0040D980 add edi, dword ptr [ebp+12A7] ; NOTEPAD.00400000
0040D986 lods dword ptr [esi]
0040D98A mov ecx, eax
0040D98C xor ecx, ebx
0040D991 lods dword ptr [esi]
0040D992 add eax, ebx
0040D998 rol ebx, 1
0040D99A xor dword ptr [edi], eax
0040D9A0 xor dword ptr [edi], ecx ; 还原代码
0040D9A2 rol eax, 3
0040D9AA add eax, ecx
0040D9AC add edi, 4
0040D9B2 dec ecx
0040D9B7 jnz short 0040D99A ; 循环长度为0FF8
0040D9BC jmp short 0040D961 ; 回跳,继续循环
0040D9BE cmp byte ptr [ebp+1219], 1 ; --代码还原完毕--
0040D9C5 jnz short 0040D9D5
0040D9C7 mov ebx, dword ptr [ebp+1263]
0040D9CD mov eax, dword ptr [ebp+1277]
0040D9D3 mov dword ptr [ebx], eax
0040D9D5 mov eax, dword ptr [ebp+1227] ; ss:[0040E227]=00406000 (NOTEPAD.00406000)
0040D9DB mov dword ptr [ebp+121F], eax
0040D9E1 cmp byte ptr [ebp+121E], 1
0040D9E8 jnz short 0040DA54
0040DA54 mov edx, dword ptr [ebp+1227] ; NOTEPAD.00406000
0040DA5A sub edx, dword ptr [ebp+121F]
0040DA60 add edx, dword ptr [ebp+12A7]
0040DA66 mov dword ptr [ebp+122B], edx
0040DA6C mov esi, dword ptr [ebp+1267]
0040DA72 or esi, esi
0040DA74 je short 0040DAA9
0040DA76 add esi, dword ptr [ebp+12A7] ; NOTEPAD.00400000
0040DA7C add esi, 10
0040DA7F mov edx, dword ptr [ebp+1227]
0040DA85 sub edx, dword ptr [ebp+12A3]
0040DA8B or edx, edx
0040DA8D je short 0040DA94
0040DA8F call 0040DEE8
0040DA94 mov edx, dword ptr [ebp+12A7]
0040DA9A sub edx, dword ptr [ebp+12AF]
0040DAA0 or edx, edx
0040DAA2 je short 0040DAA9
0040DAA4 call 0040DEE8
0040DAA9 cmp byte ptr [ebp+121A], 0
0040DAB0 je short 0040DAD6
0040DAB2 push 4
0040DAB4 push 1000
0040DAB9 push dword ptr [ebp+128B]
0040DABF push 0
0040DAC1 call 0040D702
0040DAC6 or eax, eax
0040DAC8 je 0040D806
0040DACE mov esi, eax
0040DAD0 mov dword ptr [ebp+127F], eax
0040DAD6 mov ecx, dword ptr [ebp+12A7]
0040DADC mov edx, dword ptr [ebp+1227]
0040DAE2 mov eax, dword ptr [edx+C] ; --开始修复IAT--
0040DAE5 or eax, eax
0040DAE7 je 0040DBE0 ; 是否还有DLL?
0040DAED mov dword ptr [edx+C], ecx
0040DAF0 add eax, dword ptr [ebp+122B]
0040DAF6 push edx
0040DAF7 push ecx
0040DAF8 push eax
0040DAF9 push eax
0040DAFA mov byte ptr [ebp+121D], 0
0040DB01 mov ebx, dword ptr [eax]
0040DB03 and ebx, 0DFDFDF
0040DB09 cmp ebx, 43464D ; "MFC"
0040DB0F jnz short 0040DB29
0040DB11 mov ebx, dword ptr [eax+5]
0040DB14 and ebx, DFDFDFFF
0040DB1A cmp ebx, 4C4C442E
0040DB20 jnz short 0040DB29
0040DB22 mov byte ptr [ebp+121D], 1
0040DB29 mov ebx, eax ; "SHELL32.dll"
0040DB2B call 0040D70D ; GetModuleHandleA
0040DB30 pop ebx
0040DB31 pop ecx
0040DB32 pop edx
0040DB33 or eax, eax
0040DB35 jnz short 0040DB49
0040DB37 push edx
0040DB38 push ecx
0040DB39 push ebx
0040DB3A call 0040D718 ; LoadLibraryA
0040DB3F or eax, eax
0040DB41 je 0040D789 ; Load失败?
0040DB47 pop ecx
0040DB48 pop edx
0040DB49 call 0040DC3D
0040DB4E mov dword ptr [ebp+BAE], eax
0040DB54 mov esi, dword ptr [edx]
0040DB56 mov dword ptr [edx], ecx
0040DB58 mov edi, dword ptr [edx+10]
0040DB5B mov dword ptr [edx+10], ecx
0040DB5E or esi, esi
0040DB60 jnz short 0040DB64
0040DB62 mov esi, edi
0040DB64 add esi, dword ptr [ebp+122B]
0040DB6A add edi, dword ptr [ebp+122B]
0040DB70 mov eax, dword ptr [esi]
0040DB72 or eax, eax
0040DB74 je short 0040DBD8 ; 当前DLL是否结束?
0040DB7A movzx eax, ax
0040DB7D jmp short 0040DBA5
0040DB7F add eax, dword ptr [ebp+122B]
0040DB85 mov word ptr [eax], 0
0040DB8A inc eax
0040DB8B inc eax
0040DB8C push ebx
0040DB8D push esi
0040DB8E push eax
0040DB8F mov ebx, dword ptr [ebp+1287]
0040DB95 mov esi, eax
0040DB97 call 0040DF44 ; 字符串解密
0040DB9C mov dword ptr [ebp+1287], ebx
0040DBA2 pop eax
0040DBA3 pop esi
0040DBA4 pop ebx
0040DBA5 push eax
0040DBA6 push edx
0040DBA7 push esi
0040DBA8 push edi
0040DBA9 push ecx
0040DBAA push ebx
0040DBAB push eax
0040DBAC push eax
0040DBAD push offset SHELL32.#599
0040DBB2 call 0040D723 ; GetProcAddress
0040DBB7 pop ebx
0040DBB8 or eax, eax
0040DBBA je 0040D7D2 ; GetProcAddress失败?
0040DBC0 call 0040DC3D
0040DBC5 pop ebx
0040DBC6 pop ecx
0040DBC7 pop edi
0040DBC8 call 0040DC54
0040DBCD pop esi
0040DBCE pop edx
0040DBCF pop ebx
0040DBD0 add esi, 4
0040DBD3 add edi, 4
0040DBD6 jmp short 0040DB70 ; 循环,恢复IAT
0040DBD8 add edx, 14
0040DBDB jmp 0040DAE2 ; 继续循环,获取下一个DLL
0040DBE3 mov eax, dword ptr [ebp+1247] ; --IAT修复完毕--
0040DBED xor eax, dword ptr [ebp+129F]
0040DBF7 add eax, dword ptr [ebp+12A7] ; 真正的OEP出现了!
0040DC00 mov dword ptr [esp+1C], eax ; NOTEPAD.004010CC
0040DC09 lea edi, dword ptr [ebp+C3D]
0040DC14 mov ecx, 707
0040DC1C xor al, al
0040DC22 rep stos byte ptr es:[edi] ; 清空当前段代码
0040DC27 mov edi, ebp ; NOTEPAD.<模块入口点>
0040DC2D mov ecx, 0C35
0040DC35 rep stos byte ptr es:[edi] ; 清空当前段代码
0040DC37 popad
0040DC3B jmp eax ; NOTEPAD.004010CC,光明之巅!
然后在004010CC处DUMP,脱壳后的程序可以直接运行,但菜单中的部分功能会导致程序崩溃。用ImportREC修复IAT,发现有大量无用指针指向00400000,直接CUT,试运行之,修复成功!
总结:这个壳用了二个非法指令和一个新建线程来更改程序流程,代码中插入了大量的花指令,不过去除花指令后程序代码还是比较易懂的,没有那些双进程保护、动态区段、Stolen Bytes之类的变态保护方式,适合我等小菜练手。
后记:
尝试了一下完美优化文件,很有意思!
1.在OD里跟到OEP后停下,DUMP为“dump.exe”。注意DUMP时不用选中“Rebuild Import”重建输入表,因为后面会用ImportREC重建的。
2.用FixRes导出资源文件。最好在导出前用FixRes修复一下资源。导出时还要注意,由于用ImportREC修复IAT时会添加一个新的输入表段,因此导出资源文件的RVA要再加上一个偏移,在这里要把原RVA=7000再加上1000,即NewRVA=8000。FileAlignment填当前文件的对齐大小,可以用LordPE查出:FileAlignment=1000。导出资源的文件名为rsrc.bin。(提示:这里的RVA要加上的1000是由于程序的块对齐为1000,而且输入表的大小<1000。如果另外一个程序的FileAlignment=200,IAT SIZE=34C,那么NewRVA就要加上200×2=400。)
3.删除多余的区段。在OD里按ALT+M观察文件结构:407000-40C000为资源段,40C000-40D000为重定位表,40D000-410000为SFX和输入表。资源段我们在后面会重建,重定位表用处不大(其实是我不知道有什么用处,在LordPE里查看修复完的文件的重定位表显示为error),而后面的SFX段为壳添加的解压代码,都可以直接删除。在LordPE里删除区段只是删除了区段的头信息,物理数据并没有删除。查看到ROffset=7000,用WinHex打开dump.exe,把7000之后的所有数据直接删除。
4.修复IAT。在ImportREC中填OEP为10CC,用自动搜索,再把无效指针CUT掉即可。也可以用手工搜索得到更精确的位置和大小:在OD中随意选中一个API调用,比如004010D3处的call dword ptr [4063E4] ; GetCommandLineA,输入dd 4063E4可以看到程序用到的所有系统API。向上翻找到第一个API的位置:004062E4 77DA7883 ADVAPI32.RegQueryValueExA。向下翻找到最后一个API的位置:00406520 76322533 comdlg32.GetFileTitleA。因此在ImportREC里填IAT的RVA为004062E4-400000-4=62E0,IAT的大小为00406520-004062E4+8=248,点“获取输入表”可以发现已经没有无效指针了。修复IAT后的文件为“dump_.exe”。
5.添加资源段。在LordPE里载入dump_.exe,打开区段表,载入前面保存出来的rsrc.bin即可。注意看一下新添加的区段的Vffsett和VSize是否分别为8000和5000。
6.修复文件目录结构。导入表已经由ImportREC修复了,不用管了;资源表我们已经把它往后移了,需要修改RVA为8000;重定位表已经被删除了,需要把RVA和大小都改为0。
至此已经优化完成,在浏览器中已经识别出图标,测试运行完全通过!
总结:文件优化需要多学习PE文件的结构,在调整区块时涉及很多PE结构的知识。在优化文件时我遇到二个问题:一是为什么一定要导出资源并把资源段放到文件的最后面?我的理解是有些资源修改工具(比如eXeCope)只认标准资源,像这种脱壳后修复的资源还是不被接受。二是为什么要把重定位表设为空,并删除重定位表段?重定位表的作用是如果文件不是载入到默认的基址,而是载入到虚拟内存的另一个地址,链接器所登记的那个地址就是错误的,这时就需要用重定位表来调整。一般来说EXE文件不需要用到重定位表,而DLL会经常重定位。在这个程序里重定位表本身就是错误的,因此可以直接删除。但是当删除重定位表段后,程序的重定位表指向一个非法地址,这样会导致文件在载入到OD时有一个“非法EXE文件”的提示,这个问题刚开始困扰了我好长时间,最后终于发现是这个基本用不到的重定位表地址导致的!把重定位表地址和大小都改为0后就是一个完全合法的PE文件了。
by lelfei on 2007.10.1~
[招生]系统0day安全班,企业级设备固件漏洞挖掘,Linux平台漏洞挖掘!