※
转贴 ※
[转帖]PELOCK手记(翻译)
翻译/发帖:小狐狸
说明:
前一篇由于篇幅的关系不完整,作废。
这篇是翻译,欢迎大家提出宝贵意见。
LOCK手册集摘
\\\***PELOCK(LOCK手册集摘)***///
***** ***** 作者:crUsAdEr ***** ****
这篇文章是为了介绍PELOCK1.06的保护。许多软件(作者)用此保护以免遭到破解
的厄运,我会介绍此保护的特点并最终脱壳。PELOCK是高度依赖操作系统的,由
于这篇文章是基于WIN2K系统介绍其特点,所以会与别的系统有些出入。
所需工具:
IDA4.15
SOFT ICE(基于WIN2K SP3操作系统)
LordPE Deluxe
Superbpm (基于NT/2k操作系统)
Imprec 1.4.2
目标: PELock 1.06
包括: PElock.idc
序言:
PELOCK是与SVKPROTECTOR具有许多相似之处的一种壳,尤其是在IAT REDIRECTION和
OEP脱壳方面。同时PELOCK还可以通过ICEDUMP对KERNEL32.DLL的漏洞侦测出WIN98操
作系统中的ICEDUMP(引用SPLAJ在FRAVIA公告版上的留言)。
尽管我用比较浅显的语言说明,但我想,大家都是拥有一定基础并能熟练运用工具的
高手(应该能听的懂)。PELOCK还具有通过检验OPCODES “CC”来侦测BPX的功能,所
以请不要下BPX 函数名 不然的话PELOCK会飘然离你而去。同时它具有反跟踪能力,因
此你就得用上SUPERBPM。
(我建议大家看看我的两篇介绍ASPROTECT1.2和ARMADILLO2.6的文章以获得一些基础
知识使介绍更加清楚明白)
1.玩转LOCK
OK,首先你保证已经下载了PELOCK,这时你发现SOFTICE不能载入。怎么办!PELOCK
的核心,许多的壳都是通过载入调试来脱壳,因此我们需要一个可以载入的工具。这
下用到了前面介绍的SUPERBPM工具,用它与SOFTICE合作,下“BPM VERTUALALLOC X”
载入。运行PELOCK,断下来后,你会停在这:
00431682 push 4
00431684 push 3000h
00431689 push 0F000h; loader 大小
0043168E push ecx
0043168F mov edx, [ebp+VirtualAlloc]
00431692 cmp byte ptr [edx], 0CCh ; 检测 opcodes for bpx
00431695 setz cl; 如果发现bpx 那么
00431698 add edx, ecx ; 改变edx的值并且发生错误
0043169A call edx
0043169C lea edx, [ebp+756h] <=== 你应该停在这
004316A2 push eax; 这里的eax是VirtualAlloc的地址
004316A3 push edx; 这儿main PELock loader将会decrypted to
004316A4 push eax
004316A5 pusha
<-----------省略------------->
<--loader 解密(decryption)程序----------->
00431768 mov [esp+1Ch], edi
0043176C popa
0043176D retn; 从这跳向PELock loader code
正如上面所看到的,这儿就是PELOCK解密的地方。尽管这段程序省略了,但是你可以
很容易的通过SOFTICE滚动条找到RET命令。我们要在43176D处下断点,并且DUMP这个
ELOCK LOADER,你可以在堆栈中找到新解密(DECRYPTED)的LOADER的基址。DUMP后
我们将进入IDA去分解学习。我相信,你一定会重建PE文件头,剪切粘贴DUMP下来的
到PELOCK.EXE中去并再次用IDA重新载入。
如果你照做了,你将会发现:
00401000 call near ptr PE_Lock_start+24h
00401005
00401005 GetAPIAddress proc near ; CODE XREF: ...
00401005
00401005 var_4= byte ptr -4
00401005 arg_0= dword ptr 8
00401005 arg_4= dword ptr 0Ch
00401005 arg_8= dword ptr 10h
00401005 arg_C= byte ptr 14h
00401005 arg_104= dword ptr 10Ch
00401005
00401005 push ebp
00401006 mov ebp, esp
00401008 pusha
<---------继续---------->
摘录的部分是一些PELOCK LOADER的命令,开始处是一些GETPROCADDRESS程序,用来
读取系统DLL,PE文件头和从输出表找到API地址。API名称是杂乱的并与表相比较(
我们将讨论更多关于API REDIRECTION SECTION)。如果一个API地址被找到,那么
将核对首字节“CC”,假使发现BPX,那么执行FLOW的些微改变将会导致PELOCK的最
终结束。由于整个LOADER不完全解密(DECRYPTED),许多部分仍然是个“谜”,只有
在PELOCK执行他的主程序时才能完全解密,如果你发现IDA DISASSEMBLY与SOFTICE屏
幕有差别,那么你不得不重新DUMP这个LOADER并且把DUMP下来的解密部分覆盖在原来
的部分以使IDA分析时更准确,方便。
当你开始追踪PELOCK LOADER时,你会发现有许多无用但会阻碍分析的代码包含在LOA
DER中,使分析变的困难。下面就是这样的例子:
0040159F C1 F7 00 sal edi, 0
004015A2 C1 F2 00 sal edx, 0; 毫无用处
004015A5 EB 02 jmp short loc_4015A9; 跳过
004015A5 ; -----------------------------------------------------------
004015A7 CD db 0CDh ; -
004015A8 20 db 20h ;
004015A9 ; -----------------------------------------------------------
004015A9
004015A9 loc_4015A9: ; CODE XREF: ...
004015A9 C1 F0 00 sal eax, 0
004015AC EB 02 jmp short loc_4015B0
004015AC ; -----------------------------------------------------------
004015AE C9 db 0C9h ; +
004015AF 22 db 22h ; "
004015B0 ; -----------------------------------------------------------
004015B0
004015B0 loc_4015B0: ; CODE XREF: ...
004015B0 E8 01 00 00 00 call loc_4015B6; 顺序读出 head
004015B0 ; -----------------------------------------------------------
004015B5 42 db 42h ; B
004015B6 ; -----------------------------------------------------------
004015B6
004015B6 loc_4015B6: ; CODE XREF: ...
004015B6 8D 64 24 04 lea esp, [esp+4];纠正堆栈
OR THIS
004015F1 E8 01 00 00 00 call loc_4015F7
004015F1 ;
-----------------------------------------------------------
004015F6 3B db 3Bh ; ;
004015F7 ; -----------------------------------------------------------
004015F7
004015F7 loc_4015F7: ; CODE XREF: ...
004015F7 8F 44 24 FC pop dword ptr [esp-4];纠正堆栈
OR THIS
004015D6 7A 03 jp short loc_4015DB; 不要在意Eflags
004015D8 7B 01 jnp short loc_4015DB; 和将会跳到哪里
004015D8 ;
-----------------------------------------------------------
004015DA 7E db 7Eh ; ~
004015DB ; -----------------------------------------------------------
004015DB
004015DB loc_4015DB: ; CODE XREF: ...
004015DB EB 02 jmp short loc_4015DF; 无用代码
004015DB ; -----------------------------------------------------------
004015DD 8B db 8Bh ; ?
004015DE 8D db 8Dh ; ?
004015DF ; -----------------------------------------------------------
004015DF
004015DF loc_4015DF: ; CODE XREF: ...
004015DF C1 F6 00 sal esi, 0
我们有必要把这些无用的,对于分析没有任何价值的代码清除,以便于我们看清
整个代码,同时也方便了IDA的分析。你可以用如MOW,IMHOTEP或者自创的工具来
修整这些代码。我自己就做了一个简单的IDC SCRIPT,他可以检查并把他们NOP掉
并单行排列显示。文章中FURTHER CODE的摘录就正象无用代码的清除一样。我已把
IDC SCRIPT(作者写的工具)帖了上来作为参考,你可以自己做一个比较顺手的。
2.SOFTICE 侦测
当你清除了无用代码后,你就可以放心的研究PELOCK了,但首先我们必须使PELOCK
运行在SOFTICE下。在WIN2K系统下,PELOCK对SOFTICE有很强的侦测性,如INT1侦测,
UNHANDLEDEXCEPTIONFILTER BYTE侦测。
即由于SOFTICE载入时,他会把KERNEL32!UNHANDLEDEXCEPTIONFILTER API的第一字节
“55”用“CC”来代替。
PELOCK就是这样侦测UNHANDLEDEXCEPTIONFILTER BYTE的,如下:
004010D4 80 38 CC cmp byte ptr [eax], 0CCh ; '?
004010D7 75 0F jnz short 跳则成功
004010D9 74 01 jz short near ptr 跳则完蛋
因此,我们使用READ(R命令)修改ZERO FLAG的值来避开对UNHANDLEDEXCEPTIONFILTER
的校验。
BPM UNHANDLEDEXCEPTIONFILTER R FL Z
当WIN2000系统运行在无SOFTICE状态时,登陆IDT的INT01描述符已经是DPL=0。因此,如
果RING-3级试图访问INT01 WITH DPL=0时,GP=INT0D的例外就会出现,同时WINDOWS INT0
D HANDLER 将会报告COOOOOO5(例外_过程_违反)作为“例外”代码,而EIP就作为“例外”
产生的地址。当两者同时运行时,登陆IDT的INT01描述符被设定为DPL=3,于是这时INT01便
可顺利的进入RING3级。如果INT01冲突,那么下一个命令地址便代替它保存在堆栈中,同时W
INDOWS将检验所有的断点,如果不存在,那么WINDOWS将报告80000004(例外_独立_进程)错
误。简言之,如果SOFTICE运行,当INT01冲突时,则“例外”代码是80000004,而出现“例外
”的地址便是EIP+2;反言之,如果SOFTICE不运行,“例外“代码和地址就变成了C0000005和
EIP。(更多细节,请参见INTEL MANUAL VOLUME 3)
在此感谢[YATES]的精彩解说!
当SOFTICE运行时,PELOCK根据校验的结果产生不同的错误地址。一个SEH HANDLER便
形成了!
004044A3 CD 01 int 1 ; 如果SOFTICE不运行的例外地址
004044A5 C3 retn; 如果SOFTICE运行的例外地址
004044A6 ; -----------------------------------------------------------
004044A6 EB 36 jmp short near sice_不_存在
004044A6 ; -----------------------------------------------------------
004044A8 90 90 90 90 90 90+ Softice_侦测到 db 35h dup(90h)
004044DD ; -----------------------------------------------------------
004044DD C3 retn
004044DD ; -----------------------------------------------------------
004044DE 90 90 90 90 90 90+sice_不_存在 db 40h dup(90h)
<--- Inside SEH handler --->
004040E4 83 A8 B8 00 00 00+ sub [eax+CONTEXT.Eip], 0FFFFFFFDh ; Eip+3
004040E4 FD ; 如果SICE存在, int1 例外
004040E4 ; 将向前移2字节
因此,如果侦测到SOFTICE,例外FLOW将会改变,同时PELOCK也会结束。为了避免这
种情况的发生,我们需要修改登陆IDT的INT01描述符,使DPL=0。在WIN2K下运行IDT
;找到INTERRUPT DESCRIPTORS TABLE的地址
D <ADDRESS OF IDT>
用IDT修改从EE到8E的总共0DH字节 感谢SINTAX 和NIKOLATESLA 的帮助
具体细节,请参考ART OF ASSEMBLY ABOUT IDT FORMAT
3.输入表重建
当你完成了IDT的修改(重新运行SOFTICE时),(在这期间)UNHANDEDEXCEPTIONFILTER
将会中断两次,(你会发现)PELOCK已经可以正常运行了。我们下一步就是获取输入表。
运行PELOCK,中断在第一个UNHANDLEDEXCEPTIONFILTER,我们停在前文所说的GETPROCADD
RESS中,下“bpm EIP X”断点,再次中断时,按一次F12,我们就会进入IAT LOADING和RE
DIRECTING PROCESS的中心。
00405350 EB 2A jmp short near ptr ImportLoadingStart
00405350 ; ------------------------------------------------------
00405352 90 90 90 90 90 90+next_dll_entry db 27h dup(90h) ; CODE XREF: seg000:004059C3_j
00405379 ; ------------------------------------------------------
00405379 8D 7E 01 lea edi, [esi+1]
00405379 ; ------------------------------------------------------
0040537C 90 90 90 90 90 90+ImportLoadingStart db 24h dup(90h) ; CODE XREF: seg000:00405350_j
004053A0 ; ------------------------------------------------------
004053A0 57 push edi ; edi -> DLL 名称
004053A1 FF 55 00 call [ebp+API.LoadLibrary]
004053A1 ; ------------------------------------------------------
004053A4 90 90 90 90 90 90+ db 2Ch dup(90h)
004053D0 ; ------------------------------------------------------
004053D0 85 C0 test eax, eax
004053D2 0F 84 13 1A 00 00 jz Library_loading_fail
004053D2 ; ------------------------------------------------------
004053D8 90 90 90 90 90 90+ db 26h dup(90h)
004053FE ; ------------------------------------------------------
004053FE 8B D0 mov edx, eax ; edx = DLL 句柄
004053FE ; ------------------------------------------------------
00405400 90 90 90 90 90 90+ db 2Dh dup(90h)
0040542D ; ------------------------------------------------------
0040542D 8B F7 mov esi, edi ; import data block pointer(指针)
0040542D ; ------------------------------------------------------
0040542F 90 90 90 90 90 90+ db 2Dh dup(90h)
0040545C ; ------------------------------------------------------
0040545C string_scan_loop: ; CODE XREF: seg000:00405461_j
0040545C 8A 06 mov al, [esi] ;通过校验空字节
0040545E 46 inc esi ;对Import Data block循环扫描
0040545F 84 C0 test al, al ;并把指针放到DLL名称的末尾
00405461 75 F9 jnz short string_scan_loop
00405461 ; ------------------------------------------------------
00405463 90 90 90 90 90 90+not_end_of_thunk db 4Eh dup(90h) ; CODE XREF: seg000:00405925_j
004054B1 ; ------------------------------------------------------
004054B1 8B 46 01 mov eax, [esi+1] ; 从Import Data Block获得API name hash
004054B1 ; ------------------------------------------------------
004054B4 90 90 90 90 90 90+ db 2Ah dup(90h)
004054DE ; ------------------------------------------------------
004054DE B9 B2 06 00 00 mov ecx, 6B2h ; 为下面的CRC循环初始化数值
004054DE ; ------------------------------------------------------
004054E3 90 90 90 90 90 90+ db 23h dup(90h)
00405506 ; ------------------------------------------------------
00405506 CRC_import_decrypt_loop: ; CODE XREF: seg000:00405510_j
00405506 2B 84 8D 0A 3D 00+ sub eax, [ebp+ecx*4+3D0Ah] ; 通过对代码部分CRC校验
0040550D D3 C0 rol eax, cl ; 循环解密API name hash
0040550F 49 dec ecx ; 阻止对数值的修改
00405510 75 F4 jnz short CRC_import_decrypt_loop
00405510 ; ------------------------------------------------------
00405512 90 90 90 90 90 90+ db 28h dup(90h)
0040553A ; ------------------------------------------------------
0040553A 8A 0E mov cl, [esi] ; 获得API名称的第一个字符
0040553C 83 C6 05 add esi, 5
0040553C ; ------------------------------------------------------
0040553F 90 90 90 90 90 90+ db 23h dup(90h)
00405562 ; -------------------------------------------------
00405562 51 push ecx ; 第一个字符入栈
00405562 ; ------------------------------------------------------
00405563 90 90 90 90 90 90+ db 23h dup(90h)
00405586 ; ------------------------------------------------------
00405586 50 push eax ; API name hash入栈
00405586 ; ------------------------------------------------------
00405587 90 90 90 90 90 90+ db 32h dup(90h)
004055B9 ; ------------------------------------------------------
004055B9 52 push edx ; DLL句柄入栈
004055B9 ; ------------------------------------------------------
004055BA 90 90 90 90 90 90+ db 29h dup(90h)
004055E3 ; ------------------------------------------------------
004055E3 FF 75 00 push dword ptr [ebp+0]
004055E3 ; ------------------------------------------------------
004055E6 90 90 90 90 90 90+ db 33h dup(90h)
00405619 ; ------------------------------------------------------
00405619 E8 E7 B9 FF FF call GetAPIAddress ; The home-made GetProcAddress
00405619 ; ------------------------------------------------------
0040561E 90 90 90 90 90 90+ db 30h dup(90h) <=== 你应该到达这里
这句无用代码已用IDC SCRIPT清除
0040564E ; ------------------------------------------------------
0040564E 85 C0 test eax, eax ; 校验找到的API地址
0040564E ; ------------------------------------------------------
00405650 90 90 90 90 90 90+ db 1Dh dup(90h)
0040566D ; ------------------------------------------------------
0040566D 0F 84 92 17 00 00 jz API_add_not_found
0040566D ; ------------------------------------------------------
00405673 90 90 90 90 90 90+ db 5Bh dup(90h)
004056CE ; ------------------------------------------------------
004056CE F6 C1 80 test cl, 80h ; 校验 redirection flag(标志位)
004056CE ; 如果cl=80,API就改变(redirected)
004056CE ; ------------------------------------------------------
004056D1 90 90 90 90 90 90+ db 2Bh dup(90h)
004056FC ; ------------------------------------------------------
004056FC 8B 0E mov ecx, [esi]
004056FC ; ------------------------------------------------------
004056FE 90 90 90 90 90 90+ db 47h dup(90h)
00405745 ; ------------------------------------------------------
00405745 0F 84 87 00 00 00 jz near ptr redirect_import+2Bh ;关键跳转
00405745 ; ------------------------------------------------------
0040574B 90 90 90 90 90 90+ db 55h dup(90h)
004057A0 ; ------------------------------------------------------
004057A0 89 01 mov [ecx], eax ; 用原始的API地址
004057A2 E9 1F 01 00 00 jmp next_entry_in_thunk ; 更新 First Thunk of Import Directory
004057A2 ; ------------------------------------------------------
004057A7 90 90 90 90 90 90+redirect_import db 8Bh dup(90h) ; CODE XREF: seg000:00405745_j
00405832 ; ------------------------------------------------------
00405832 89 19 mov [ecx], ebx ; 用更改的API 地址更新First Thunk
00405832 ; ------------------------------------------------------
00405834 90 90 90 90 90 90+ db 5Dh dup(90h)
00405891 ; ------------------------------------------------------
00405891 E8 51 01 00 00 call near ptr redirect_API+1Fh
00405891 ; ------------------------------------------------------
00405896 90 90 90 90 90 90+ db 30h dup(90h)
004058C6 ; ------------------------------------------------------
004058C6 next_entry_in_thunk: ; CODE XREF: seg000:004057A2_j
004058C6 83 C6 04 add esi, 4
004058C6 ; ------------------------------------------------------
004058C9 90 90 90 90 90 90+ db 30h dup(90h)
004058F9 ; ------------------------------------------------------
004058F9 B1 FE mov cl, 0FEh ; '? ; 校验stopping byte
004058F9 ; ------------------------------------------------------
004058FB 90 90 90 90 90 90+ db 28h dup(90h)
00405923 ; ------------------------------------------------------
00405923 38 0E cmp [esi], cl ; 如果存在则作为thunk的结束
00405925 0F 85 60 FB FF FF jnz near ptr not_end_of_thunk+28h
00405925 ; ------------------------------------------------------
0040592B 90 90 90 90 90 90+ db 32h dup(90h)
0040595D ; ------------------------------------------------------
0040595D 38 4E 01 cmp [esi+1], cl ; 再次校验stopping byte
0040595D ; ------------------------------------------------------
00405960 90 90 90 90 90 90+ db 28h dup(90h)
00405988 ; ------------------------------------------------------
00405988 0F 84 FC 17 00 00 jz import_done ; 如果2个stopping byte
都符合
00405988 ; 则作为Import Data的结束
00405988 ; ------------------------------------------------------
0040598E 90 90 90 90 90 90+ db 35h dup(90h)
004059C3 ; ------------------------------------------------------
004059C3 E9 8A F9 FF FF jmp near ptr 下一个_dll_入口
希望我的注解大家都明白。基本上,IMPORT DATA被储存在一个块中,以“FE“作为STOPPING BYTE或MARKER。每个DLL IMPORT入口以连续的ASCII值组成的DLL名称开始,IMPORT ENTRIES跟随,以“FE“结束。TABLE每一个IMPORT入口由5个字节组成,首字节是API名称的第一个ASCII字符,下一个双字节是API NAME HASH。下面就是一个IMPORT DATA BLOCK的例子:
00407E21 6B 65 72 6E 65 6C+aKernel32_dll_0 db 'kernel32.dll',0
00407E2E 56 db 56h ; V
00407E2F B5 F2 31 F6 dd 0F631F2B5h
00407E33 56 db 56h ; V
00407E34 22 0A AC 32 dd 32AC0A22h
<-------------省略------------->
00407E60 47 db 47h ; G
00407E61 E4 0D 06 DE dd 0DE060DE4h
00407E65 FF db 0FEh ; stopping byte
00407E66 75 73 65 72 33 32+aUser32_dll db 'user32.dll',0
00407E71 4D db 4Dh ; M
00407E72 71 A2 D2 A8 dd 0A8D2A271h
00407E76 FF db 0FEh ;stopping byte
00407E77 FF db 0FEh ;
上面的每一个IMPORT ENTRY都作为GETPROCADDRESS的参数(假想系统DLL EXPORT DIRECTORIES
是按字母分类的)分列于EXPORT DIRECTORY中并进行校验直到找出符合首字母的字符为止。接
下来将对每个EXPORT DIRECTORY的API名称执行HASH,直到找到符合条件的HASH为止,这个找到
的API地址会校验BPX并且返回EAX中去。明显的,在4056CE处的比较和在405745处的跳转是决定
API是否应该REDIRECTED(转向/改变)的关键。因为CRC对IMPORT HASH值的检验,使我们应该想
办法让PELOCK不改变(REDIRECT)任何API,而不是简单的修改跳转。解决方法有两个:修改GET
PROCADDRESS或者在405745处修改ZERO标志位的值,使其为0。我提倡第二种在SOFTICE中简单易行
的方法。
下BPM 405745 X 如果ZFL==1 下“R FL Z:X“
SOFTICE的屏幕由于无休止的中断而不停闪烁,大量的IMPORTS出现在
屏幕里,让你的眼睛暂时离开一会儿,几分钟之后,当你再看屏幕时,P
ELOCK已经安静的运行中了。再看看IMPREC,键入OEP=1000,很快的,
28000就是你的IAT。单击GET IMPORT,除了1个之外,IMPORT将会修整
所有的IMPORT。剩下的作为练习留给读者,目标是找到例外的API并找出
原因。
4.破解目标程序
当你修复了IAT REDIRECTION并获得了有效的IMPORT TREE(目录)后,
保存并暂时搁置。下面我们将破解主程序并完全脱壳。再次运行PELOCK,
打开LORDPE,选择PARTIAL DUMP(部分脱壳),键入DUMP SIZE作为P
ELOCK.EXE的IMAGE SIZE。通过IDA分析后,你会发现仍然有大量所需
的代码未能解密,下面就是这样一段未解密代码的例子:
00401ECA E8 33 5A EF FF call near ptr 2F7902h ; CALL Decryptor(解密调用)
00401ECA ; ----------------------------------------------------------
00401ECF E8 02 00 00 dd 2E8h; code block property(代码段属性)
00401ED3 66 7B 6B 3C 47 89+ db 66h, 7Bh, 6Bh, 3Ch, 47h, 89h, 0EDh, 39h, 53h, 0EFh, 83h,
00401ED3 ED 39 53 EF 83 00+ db 72h, 6Fh, 5Dh, 3Ch, 0EEh, 26h, 0B0h, 78h, 0AEh, 29h, 98h
00401ED3 B7 A1 AD 9D E8 B2+ db 0FBh, 0EBh, 0FAh, 0EBh, 0FCh, 0EBh, 6, 0CDh, 20h, 0EBh,
00401F40 ; -------------------------------------------------------------
00401F40 E8 F1 61 EF FF call near ptr 2F8136h ; CALL Re-encryptor(再加密)
00401F40 ; -------------------------------------------------------------
00401F45 E8 02 00 00 dd 2E8h ; code block property(代码段属性)
00401F49 ; -------------------------------------------------------------
从上面可以看出,DUMP.EXE中有许多代码段,加解密地址并不在EXE程序的IMAGE中,而是在PE
LOADER MEMORY中。基本上,为了防止被脱壳和DUMPING,PELOCK把大量的加密段代码放入原始
程序中,当程序被执行时,解密段将首先被执行,它将代码块属性读出从而解密块,接着
从解密调用返回,执行代码块,在进入一般执行程序之前将调用再加密重新加密代码块。
通过对DUMP.EXE主程序的研究,发现它包含有三对不同地址,不同运算过程的加解密调用。
通过研究这些程序,我们发现注以“代码块属性“并且位于CALL下的双字(DWORD)其实是
加密代码块的长度值。尽管理解程序的解密算法对于我们正确DUMP目标程序意义不大,但是
却可以帮助我们了解它的工作原理。为了精简文章的长度,这儿就不赘述了。
为了正确DUMP程序,我们需要在到达OEP之前使PELOCK LOADER解密出所有的代码块。虽然可
以通过修改DECRYPTOR来解密,接着通过跳转避开加密的调用,但是我们怎样知道在到达OEP
之前是否已解开了所有的加密代码块呢?我是这样考虑的:由于LOADER OFFSET在不同的PC上
是不同的,所以PELOCK LOADER不仅需要知道哪里是用于DECRYPTOR和RE-ENCRYPTOR的调用,并
且需要计算出调用的距离以便进入目标程序中,同时猜想应该有一张存储相关信息的TABLE(表)
。事实正是如此。重新运行PELOCK,在原始的EXE IMAGE中下BPM <其中一个解密的调用>,SOFT
ICE将会恰好中断在主程序中。
0040DBA8 insert_crypt_engine_3: ; CODE XREF: seg000:00407578_p
0040DBA8 60 pusha
0040DBA9 E8 A3 FF FF FF call mov_ebp_40DB56; 给EBP设置(必要的)基址
0040DBAE 8D 75 F7 lea esi, [ebp-9]
0040DBAE ; -----------------------------------------------------------
0040DBB1 90 90 90 90 90 90+ db 30h dup(90h)
0040DBE1 ; -----------------------------------------------------------
0040DBE1 8D 95 6C 03 00 00 lea edx, [ebp+36Ch]; 获得Decryptor的地址
0040DBE1 ; -----------------------------------------------------------
0040DBE7 90 90 90 90 90 90+ db 21h dup(90h)
0040DC08 ; -----------------------------------------------------------
0040DC08 B9 03 00 00 00 mov ecx, 3 ; table(表)大小, 仅仅=3
-----------------------------------------------------------
0040DC0D 90 90 90 90 90 90+loop_inserting_all_engine_loc db 24h dup(90h) ;
0040DC31
-----------------------------------------------------------
0040DC31 8B 06 mov eax, [esi]; 从table(表)中读取一个双字
0040DC31 ; -------------------; 每个双字表明调用的地址
0040DC33 90 90 90 90 90 90+ db 25h dup(90h) ;
0040DC58 ; -----------------------------------------------------------
0040DC58 83 EE 04 sub esi, 4; 把表中的指针向后移
0040DC58 ; -----------------------------------------------------------
0040DC5B 90 90 90 90 90 90+ db 2Dh dup(90h)
0040DC88 ; -----------------------------------------------------------
0040DC88 C6 00 E8 mov byte ptr [eax], 0E8h ; 设置调用字节
0040DC88 ; -----------------------------------------------------------
0040DC8B 90 90 90 90 90 90+ db 2Dh dup(90h)
0040DCB8 ; -----------------------------------------------------------
0040DCB8 40 inc eax
0040DCB9 8B FA mov edi, edx; edi -> Decryptor的地址
0040DCBB 2B F8 sub edi, eax; 计算调用的距离
0040DCBB ; -----------------------------------------------------------
0040DCBD 90 90 90 90 90 90+ db 2Bh dup(90h)
0040DCE8 ; -----------------------------------------------------------
0040DCE8 83 EF 04 sub edi, 4; 减去指令长度
0040DCEB 89 38 mov [eax], edi ; 在E8后插入调用距离
0040DCEB ; -----------------------------------------------------------
PATCHES made to force decrypt code(这里的修改为了代码的解密)
0040DCED 90 90 90 90 90 90+ db 28h dup(90h) ; push dword ptr [eax+4]
0040DCED 90 90 90 90 90 90+ ; pushad
0040DCED 90 90 90 90 90 90+ ; dec eax
0040DCED 90 90 90 90 90 90+ ; call eax=>解密已修改的程序
0040DCED 90 90 90 90 90 90+ ; popad
0040DCED 90 90 90 90 90 90+ ; pop dword ptr [eax+4]
0040DCED 90 90 90 90 ; mov word [eax-1], 7EB
0040DD15 ; -----------------------------------------------------------
0040DD15 83 C0 08 add eax, 8;
0040DD18 03 40 FC add eax, [eax-4]; 加上代码块长度
0040DD1B 83 C0 10 add eax, 10h; eax -> 调用Re-encryptor的地址
should be placed
0040DD1B ; -----------------------------------------------------------
0040DD1E 90 90 90 90 90 90+ db 2Eh dup(90h); 这儿设置了另一个调用
0040DD4C ; -----------------------------------------------------------
; ;
0040DD4C C6 00 E8 mov byte ptr [eax], 0E8h ; 'F' ; mov byte ptr [eax], E9
0040DD4C ; -----------------------------------------------------------
0040DD4F 90 90 90 90 90 90+ db 2Fh dup(90h);调用Re-encryptor
0040DD7E ; -----------------------------------------------------------
0040DD7E 40 inc eax
0040DD7E ; -----------------------------------------------------------
0040DD7F 90 90 90 90 90 90+ db 2Ch dup(90h); edi -> Re-encryptor的地址
0040DDAB ; -----------------------------------------------------------
0040DDAB 8D BD 4E 07 00 00 lea edi, [ebp+74Eh] ; lea edi, [eax+8]
0040DDAB ; -----------------------------------------------------------
0040DDB1 90 90 90 90 90 90+ db 28h dup(90h)
0040DDD9 ; ------------0040DDD9 2B F8 sub edi, eax; 计算调用的距离
0040DDD9 ; -----------------------------------------------------------
0040DDDB 90 90 90 90 90 90+ db 23h dup(90h)
0040DDFE ; -----------------------------------------------------------
0040DDFE 83 EF 04 sub edi, 4; 减去指令长度
0040DDFE ; -----------------------------------------------------------
0040DE01 90 90 90 90 90 90+ db 25h dup(90h)
0040DE26 ; -----------------------------------------------------------
0040DE26 89 38 mov [eax], edi; 插入调用距离
0040DE26 ; -----------------------------------------------------------
0040DE28 90 90 90 90 90 90+ db 2Eh dup(90h); 结束调用
0040DE56 ; -----------------------------------------------
0040DE56 49 dec ecx; 计算表中数值减1
0040DE56 ; -----------------------------------------------------------
0040DE57 90 90 90 90 90 90+ db 36h dup(90h); 如果不是表(TABLE)尾, 循环
0040DE8D ; -----------------------------------------------------------
0040DE8D 0F 85 7A FD FF FF jnz near ptr loop_inserting_all_engine_loc
0040DE8D ; -----------------------------------------------------------
0040DE93 90 90 90 90 90 90+ db 23h dup(90h)
0040DEB6 ; -----------------------------------------------------------
0040DEB6 8D 7D 52 lea edi, [ebp+52h]
0040DEB9 B9 16 03 00 00 mov ecx, 316h; 写入无用代码
0040DEBE F3 AA repe stosb; 覆盖上述代码段
0040DEC0 61 popa
0040DEC1 C3 retn
注释说明的很清楚。每一部分加密代码的地址被读出并储存在TABLE(表)中,
通过解密代码块的长度计算RE-ENCRYPTOR的调用的区域,而E8XXXXXXXX的作用
是为了调用DECRYPTOR,。为了避免调用DECRYPTOR/ENCRYPTOR而使程序直接从
表(TABLE)上列出的地址解密出所有的的代码块,我们可以修改(PATCH)程
序.PATCH(修改)的必要过程已通过上述橘黄色注释标明。由于“CALL EAX”虚
拟了原始EXE的执行,因此它会触发用于解密某个地址下代码块的CALL DECRYP
TOR(的执行)。但是,我们的目的不是使程序返回执行到原始EXE中,而是通
过修改程序,使它返回执行到我们修改(PATCH)的循环中去。这是一件很容易
的事,只要纠正程序的堆栈即可。
我们要找到DECRYPTOR程序的结束语句,如:
RET ;只返回执行到解密代码块
修改堆栈后是这样:
POP EAX ;EAX-解密代码块的地址
RET ;从“CALL EAX”返回我们修改的循环中
4.CRC 校验
上文已提到,程序中有3对不同的DECRYPTOR/ENCRYPTOR,而每一对又有一段
各自独立的程序,这段程序用于把(DECRYPTOR/ENCRYPTOR)调用程序插入目
标程序EXE中,通过EXE中的一张表(TABLE)放置调用的程序的地址。为了获得
正确的DUMP,我们可以通过修改这3对程序来解密目标程序EXE的整个代码部分。
然而,问题随之而来:其中一个调用插入程序(CALLS INSERTION ROUNTINE)
包含加密表(TABLE)地址,并通过CRC校验来解密TABLE ENTRIES。如果修改
将导致对TABLE ENTRIES解密错误情况的发生。下面就是这段程序:
00408368 Insert_Crypt_Engine_1: ;
00408368 B9 4A 00 00 00 mov ecx, 4Ah ; 表(TABLE)的大小
00408368 ; -----------------------------------------------------------
0040836D 90 90 90 90 90 90+ db 27h dup(90h)
00408394 ; -----------------------------------------------------------
00408394 8D 75 E8 lea esi, [ebp-18h] ; 表的开端
00408394 ; -----------------------------------------------------------
00408397 90 90 90 90 90 90+ db 27h dup(90h)
004083BE ;
-----------------------------------------------------------
004083BE 8D 95 68 0B 00 00 lea edx, [ebp+0B68h]; edx -> DECRYPTOR地址
004083BE ; -----------------------------------------------------------
004083C4 90 90 90 90 90 90+get_crypted_offset(得到_加/解密_偏移量) db 55h dup(90h) ; CODE XREF: ...
00408419 ; -----------------------------------------------------------
00408419 8B 06 mov eax, [esi]; 从表中读取解密的ENTRY
0040841B EB 31 jmp short near ptr prepare_CRC_data_pointer(准备_CRC_数据_指针)
0040841B ; -----------------------------------------------------------
0040841D 90 90 90 90 90 90+set_no_of_loop(设置_no_of_循环) db 27h dup(90h) ; CODE XREF: ...
00408444 ; -----------------------------------------------------------
00408444 BB 1F 01 00 00 mov ebx, 11Fh
00408449 E9 25 01 00 00 jmp near ptr calculate_insertion_address(计算_插入_地址)
00408449 ; -----------------------------------------------------------
0040844E 90 90 90 90 90 90+prepare_CRC_data_pointer(同上) db 2Eh dup(90h) ; CODE XREF: ...
0040847C ; -----------------------------------------------------------
0040847C 83 EE 04 sub esi, 4
0040847F EB 9C jmp short near ptr set_no_of_loop(同上)
0040847F ; -----------------------------------------------------------
00408481 90 90 90 90 90 90+get_call_distance(得到_调用_距离) db 2Fh dup(90h) ; CODE XREF: ...
004084B0 ; -----------------------------------------------------------
004084B0 2B F8 sub edi, eax
004084B2 E9 84 00 00 00 jmp near ptr minus_length_4_bytes(减去_长度_4_字节)
004084B2 ; -----------------------------------------------------------
004084B7 90 90 90 90 90 90+set_up_CALL_decrypt(设置_调用_解密) db 2Eh dup(90h) ; CODE XREF: ...
004084E5 ; -----------------------------------------------------------
004084E5 C6 00 E8 mov byte ptr [eax], 0E8h ; 'F' ; 设置调用
004084E8 E9 09 01 00 00 jmp near ptr minus_1_byte(减去_1_字节)
004084E8 ; -----------------------------------------------------------
004084ED 90 90 90 90 90 90+decrease_counter db 1Bh dup(90h) ; CODE XREF: ...
00408508 ; -----------------------------------------------------------
00408508 49 dec ecx; 通过计算减1来计算表尾
00408509 E9 C1 02 00 00 jmp 检验_结束_表?
00408509 ; -----------------------------------------------------------
0040850E 90 90 90 90 90 90+get_CALL_to_address(得到_调用_地址) db 26h dup(90h) ; CODE XREF: ...
00408534 ; -----------------------------------------------------------
00408534 8B FA mov edi, edx
00408536 E9 46 FF FF FF jmp near ptr get_call_distance(同上)
00408536 ;
-----------------------------------------------------------
0040853B 90 90 90 90 90 90+minus_length_4_bytes(同上) db 30h dup(90h) ; CODE XREF: ...
0040856B ; -----------------------------------------------------------
0040856B 83 EF 04 sub edi, 4; TABLE ENTRIES指针减4
0040856E E9 F3 01 00 00 jmp near ptr store_jump_distance(保存_跳转_距离)
0040856E ; -----------------------------------------------------------
00408573 90 90 90 90 90 90+calculate_insertion_address (同上)db 27h dup(90h) ; CODE XREF: ...
0040859A ;
-----------------------------------------------------------
0040859A CRC_loop: ; CODE XREF: ...
0040859A 03 84 9D F6 05 00+ add eax, [ebp+ebx*4+5F6h]
004085A1 D1 C8 ror eax, 1 ; 循环计算
004085A3 4B dec ebx ; 解密代码的调用
004085A4 75 F4 jnz short CRC_loop ;插入的地址
004085A6 E9 0C FF FF FF jmp near ptr set_up_CALL_decrypt(同上) ; <=== HERE do bpm
004085A6 ; -----------------------------------------------------------
004085AB 90 90 90 90 90 90+get_distance_call db 44h dup(90h) ; CODE XREF: ...
004085EF ; -----------------------------------------------------------
004085EF 2B F8 sub edi, eax
004085F1 E9 E1 01 00 00 jmp near ptr byte_4087D7
004085F1 ; -----------------------------------------------------------
004085F6 90 90 90 90 90 90+minus_1_byte(同上) db 29h dup(90h) ; CODE XREF: ...
0040861F ; -----------------------------------------------------------
0040861F 40 inc eax
00408620 E9 E9 FE FF FF jmp near ptr get_CALL_to_address(同上)
00408620 ; -----------------------------------------------------------
00408625 90 90 90 90 90 90+byte_408625 db 24h dup(90h) ; CODE XREF: ...
00408649 ; -----------------------------------------------------------
00408649 89 38 mov [eax], edi
0040864B E9 9D FE FF FF jmp near ptr decrease_counter
0040864B ; -----------------------------------------------------------
00408650 90 90 90 90 90 90+calculate_block_length(计算_块_长度) db 1Eh dup(90h) ; CODE XREF: ...
0040866E ; -----------------------------------------------------------
0040866E 83 C0 08 add eax, 8 ; 解密代码的关键
00408671 EB 00 jmp short $+2
00408673 8B 58 FC mov ebx, [eax-4] ; 得到加解密长度
00408673 ; -----------------------------------------------------------
00408676 90 90 90 90 90 90+ db 2Eh dup(90h)
004086A4 ; -----------------------------------------------------------
004086A4 C1 CB 03 ror ebx, 3; ror ebx, 3
004086A4 ; -----------------------------------------------------------
004086A7 90 90 90 90 90 90+ db 28h dup(90h)
004086CF ; -----------------------------------------------------------
004086CF 03 C3 add eax, ebx; eax -> 插入调用Encryptor(的地址)
004086D1 E9 C1 00 00 00 jmp near ptr get_end_pointer(得到_结束_指针)
004086D1 ; -----------------------------------------------------------
004086D6 90 90 90 90 90 90+get_reencrypt_call_address db 26h dup(90h) ; CODE XREF: ...
004086FC ; -----------------------------------------------------------
004086FC 8D BD 9C 13 00 00 lea edi, [ebp+139Ch]
00408702 E9 A4 FE FF FF jmp near ptr get_distance_call
00408702 ; -----------------------------------------------------------
00408707 90 90 90 90 90 90+insert_encrypt_call(插入_解密_调用)db 2Dh dup(90h) ; CODE
XREF: ...
00408734 ; -----------------------------------------------------------
00408734 C6 00 E8 mov byte ptr [eax], 0E8h ; 'F'
00408737 EB 00 jmp short $+2
00408737 ; -----------------------------------------------------------
00408739 90 90 90 90 90 90+ db 27h dup(90h)
00408760 ; -----------------------------------------------------------
00408760 40 inc eax
00408761 E9 70 FF FF FF jmp near ptr get_reencrypt_call_address
00408761 ; -----------------------------------------------------------
00408766 90 90 90 90 90 90+store_jump_distance(同上) db 2Ah dup(90h) ; CODE XREF: ...
00408790 ; -----------------------------------------------------------
00408790 89 38 mov [eax], edi
00408792 E9 B9 FE FF FF jmp near ptr calculate_block_length(无注释即同上)
00408792 ; -----------------------------------------------------------
00408797 90 90 90 90 90 90+get_end_pointer db 30h dup(90h) ; CODE XREF: ...
004087C7 ; -----------------------------------------------------------
004087C7 83 C0 10 add eax, 10h
004087CA E9 38 FF FF FF jmp near ptr insert_encrypt_call
004087CF ; -----------------------------------------------------------
004087CF check_end_of_table?: ; CODE XREF: ...
004087CF 0F 85 1C FC FF FF jnz near ptr get_crypted_offset+2Dh ;如果未结束则循环
004087D5 EB 3C jmp short _done_ ;
004087D5 ; -----------------------------------------------------------
004087D7 90 90 90 90 90 90+byte_4087D7 db 34h dup(90h) ; CODE XREF: ...
0040880B ; -----------------------------------------------------------
0040880B 83 EF 04 sub edi, 4
0040880E E9 12 FE FF FF jmp near ptr byte_408625
00408813 ; -----------------------------------------------------------
00408813 _done_: ; bpm here to wait till it finishes
下BPM等待直到完成
这一段很难懂,因为代码FLOW(流程)不是一条直线,我希望通过色彩的标记使大家
能了解清楚一些。算法与先前摘录的代码算法类似,只不过代码块长度计算不同,而且
TABLE ENTRIES在解密方面未于利用。基本上TABLE ENTRIES对于加密代码段的解密
工作是通过40859A段的CRC循环完成的。如果你在4085A6上下断点,就会发现EAX就是
解密的地址。我有两个解决CRC校验的办法,一种是把整个原始代码块复制到另一区域,
然后修改CRC程序,在新的区域完成校验;还有一种就是再次利用SOFTICE断点。我赞成
后一种,因为它简单易行。
一般的,我会让PELOCK LOADER 继续对每个TABLE ENTRIES执行解密,然后在加密代
码块的前后插入适当的调用。但是,要想在4085A6处设断并把解密的地址存入表(TABLE)
中,我们至少得在结束时拥有一张解密表。
下BPM 408813 X ;整个INSERTION循环结束时的地址断点
BPM 4085A6 X DO “DD ESI+4 EAX:X” ;把EAX中解密值存回表中
ESI指针还原(-4->+4)
当SOFTICE到达最终的断点时,你会在数据窗口中发现新的包含加密代码块地址的解密表。
剩下的工作就是通过插入一些代码解密出最后的表中所有的代码块。
A EIP
mov ecx, 4A; 表大小
load_lodsd; 从表中读取1ENTRY
push dword ptr [eax+5];保存
pushad
call eax; 虚拟原始EXE执行程序
popad
pop dword ptr [eax+5]; 复原代码块属性值
mov word ptr [eax], 7EB;修改(PATCH)Call Decryptor JUMP OVER(跳过)
mov ebx, word ptr [eax+5] ; 读取
ror ebx, 3; 得到代码块长度
add eax, ebx
add eax, 19; 计算Call Encryptor地址
mov word ptr [eax], 7EB; 替换(REPLACE)Call Encryptor JUMP OVER(跳过)
dec ecx; 表大小减1
test ecx, ecx
jne load_; 如果未结束则获取下一个TABLE ENTRY
jmp eip; 大循环, 在此DUMP
接下来,在修改完成另两处INSERTION程序后,工作做完。因此当PELOCK重新运行并
进入大循环后, EXE中所有的加密代码块便完全被解密,并且DECRYPTORS和RE-ENCRY
PTORS调用也通过修改跳过。
5.找到OEP
既然我们已经正确DUMP程序并且解密、修正了所有的代码,接下来的工作就是运行IMPREC
并对IMPORT DIRECTORY进行纠正了。我们的目的当然是找到OEP。看一下DISASSEMBLY
和HEX显示,我们发现第一节(FIRST SECTION)以大量字符串开始,代码紧随其后,所以
猜想OEP一定在字符串的尾部。
另一类一般做法是PELOCK从哪里跳转到OEP。在前述的三对INSERTION程序完成执行后,
答案很快就揭晓了。
00407578 E8 2B 66 00 00 call insert_crypt_engine_3 ; insertion routine call
00407578 ; -----------------------------------------------------------
0040757D 90 90 90 90 90 90+ db 28h dup(90h); that is responsible for inserting
004075A5 ; -----------------------------------------------------------
004075A5 E8 0C 73 00 00 call insert_crypt_engine_2 ; those Call Decryptor/Encryptor
004075A5 ; -----------------------------------------------------------
004075AA 90 90 90 90 90 90+ db 28h dup(90h) ; 进入EXE主代码段004075D2 ; -----------------------------------------------------------
004075D2 E8 C5 07 00 00 call unknown_import_stuff_and_insert_crypt_engine_1
004075D2 ;
-----------------------------------------------------------
004075D7 90 90 90 90 90 90+ db 28h dup(90h); 代码仍然被加密
<-------------省略-------------->; 在到达前最后解密
00407AA1 5D pop ebp; OEP.
00407AA1 ;
-----------------------------------------------------------
00407AA2 90 90 90 90 90 90+ db 124h dup(90h)
00407BC6 ; -----------------------------------------------------------
00407BC6 68 1E 1D 40 00 push 401D1Eh ; OEP!!!
00407BC6 ; -----------------------------------------------------------
00407BCB 90 90 90 90 90 90+ db 23h dup(90h)
00407BEE ; -----------------------------------------------------------
00407BEE C3 retn
因此,当找到任何INSERTION ROUTINE后,你可以检查堆栈从而发现从哪里调用,然后
下BPM。你应该DUMP LOADER并且看看IDA。尽管在到达OEP之前,会有一些循环和SHE等
给你的进程带来麻烦,但是IDA会帮你解决这一切并最终找到OEP。
6.总结
(省略了一些废话:-))
总的来说,PELOCK是一个很好的壳,破解的过程也充满刺激。假使PELOCK在加壳(PACK
ING)时就已经插入(INSERTING)了DECRYPTOR和ENCRYPTOR的调用,并且删去了包含解
密代码块地址的表的话,我想我们一定很难或者不能确定解密出所有的代码块。现在有
一个想法,那就?
找脱壳机的提供一线索
上传的附件: