首页
社区
课程
招聘
PELock 1.06 如何脱壳,请教一下!
发表于: 2004-5-4 21:07 8877

PELock 1.06 如何脱壳,请教一下!

2004-5-4 21:07
8877
最近在学习手动脱壳,但遇到这个壳,却一直脱不好,有哪位大大出个教程
帮忙教一下怎麽脱吧,谢谢!

下载PELock 1.06:
http://www.softcities.com/download.asp?id=2575&p=&r=True&url=http://pelock.pac.pl/pelock.exe

该主程序就是用PElock 1.0x 来加壳的,各位高手大大试一下呗!

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 6
支持
分享
最新回复 (7)
雪    币: 93
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
刚学脱壳就来脱这么猛的壳,还是PELock主程序的壳。强!!!
2004-5-4 21:26
0
雪    币: 218
活跃值: (70)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
我找到一篇经翻译过的国外脱壳教程,但是用Soft Ice来手脱,我对这个软件不熟,给各位大大参考看看,是否可用ollydbg 脱壳!

PElock1.06手动脱壳教程

翻译:newlaos[CCG][DFCG]

这篇教程主要讨论PElock 1.06(幻影)的加壳保护。这个壳应用多种技术手段来阻止破解者对它的脱壳企图,所以在这个篇教程里,我将对PElock里大家兴趣的几个功能进行逆向工程,并且最终去除这些保护。PElock对操作的依赖性很高,而这篇教程在WIN2000操作系统下完成的,如果在其它的操作下,也许就会有些不同。

使用工具:

IDA 4.15
Soft Ice on Win2k SP3
LordPE Deluxe
Superbpm for NT/2k
Import Reconstructor 1.4.2

目标: PELock 1.06
包括: PElock.idc

前期讨论:

PElock 是一个十分有趣的加壳工具,其许多功能类似于SVKProtector,主要有IAT(输入表)的重定向、文件入口点后的代码加密等。PElock 同时还能检测到icedump等调试破解工具。虽然我尽力将这篇教程写得容易看懂,但我还是要假定读者已经具备一些脱壳、结构异常(Structured Exception Handling)方面的知识,以及能够熟练运用上面所述的工具。PElock 会在软件运行过程中始终用找"CC"的方法对bpx断点进行检测,所以在我们将不能在API上下bpx断点,否则,PElock将会产生不可预料的错误。同时,PElock 会用via SEH清除调试寄存器,所以,我们还必须始终运行SuperBPM。

(最后,虽然不必要,但我还是建议你先看一下我的前两篇教程,一篇是关于AsProtect 1.2 ,一篇是关于Armadillo 2.6,这样我就不必再重复在那两篇文章说到的相关知识)

1、开始

估计你已经下载了PElock,并且发现它不能和softice同时运行。我们待会儿再考虑这个问题!和其它加壳保护软件一样,PElock的核心也是在于其加载器如何执行加密文件和如何引入文件输入表(IAT)。我们首先要从PElock里脱得一份有效的加载器(loader)。运行SuperBPM和softice for NT/2K,下断点"bpm VirtualAlloc x"。再运行PElock保护的软件,立即被softice断下,你会在这里停下:

00431682 push 4
00431684 push 3000h
00431689 push 0F000h              ; 加载器的大小
0043168E push ecx
0043168F mov edx, [ebp+VirtualAlloc]
00431692 cmp byte ptr [edx], 0CCh ; 检测有没有下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                 ; 这里是PElock主加载器准备加密的地方
004316A4 push eax
004316A5 pusha
<-----------省略------------->
  <--加载器加密程序段-->
00431768 mov [esp+1Ch], edi
0043176C popa
0043176D retn                     ; 从这里跳到PElock加载器的代码段

  正如你上面所见到的,这里是PElock 加载器加密到内存中的位置。长的加密程序段被删去了,但你可简单地在softice的窗口中拖动转动轴找到"ret"结构。我们需要在43176D处下一个断点,同里脱得一个PElock的加载器,你能在堆栈里找到我们所需的新加密加载器的基址(base address)。脱出来后,我们就用IDA对它进行反汇编并进行研究。我相信你明白如何在IDA为这个脱出来的程序建造一个PE文件头,然后将文件头剪切粘贴入PElock.exe文件,再用在IDA重新加载。

当你用IDA载入PElock后,你会看到:

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加载器文件结构的最前面的部分,以GetProcAddress 函数开始,并通过读取系统dll PE文件头和它的输入目录来定位API的地址。API名称被hash加密并与其它表放在一起(我们要重点讨论在API重定向区块中的这方面问题)。每当一个API地址被找到后,那么第1个字节将用查找"CC",如果bpx被检测到的话,执行就会改变以致PElock完全退出。当你调试PElock时,整个加载器并没有完全解密开,很多部分依然是处于加密状态,只是在当PElock进程加载它的主进程(main exe)后,才进行解密(newlaos:这个壳也就是一部分一部分的解密开,最后才进主进程,而且每一步都在检测是否被bpx下断)。如果你发现IDA反汇编出来的代码与softice屏幕上显示的不同,那么你就要重新脱一个加载器出来,并在IDA中用解密后的加载器代码覆盖掉那些加了密的代码段。

当你开始跟踪PELock加载器的时候,你会发现有许多垃圾代码被放在加载器中,使得跟踪和反汇编变得困难。下面有一段典型的垃圾代码:

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     ; call 进入(newlaos:好象F8跟进呀)
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           ; 与标志位无关
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脚本,它可以查找垃圾代码并用NOP语句来进行替换,最后将它们在一行中显示。这篇教程后面列举的代码段都是已经将众多垃圾代码段删去了的。我已经附上我编写的idc脚本,你也可以根据需要编写的一个自己脚本。

2、Softice 的检测

在你将大部分的垃圾代码段删去后,你就能很轻松地研究PElock,但首先我们让PElock能在softice中运行。在WIN2K的环境下,PElock有着很多十分有效的检测softice的方法,例如:int1检测和UnhandledExceptionFilter字节的查找等方法。

当softice被加载后,它就会用"CC"替换掉kernel32!UNhandledExceptionFilter API的首字节"55"。PElock就是用查找"CC"的方法来检测softice是否正在运行,例如下面这段代码:

004010D4 80 38 CC    cmp byte ptr [eax], 0CCh ; '|'
004010D7 75 0F       jnz short OK          <===如果不是跳向OK
004010D9 74 01       jz short near ptr bad <===如果是就跳向BAD

当然,我们可设断点bpm UnhandledExceptionFilter,断下后,再通过修改标志位ZF的方法来跳过这个检测。
bpm UnhandledExceptionFilter r do "r fl z"

当windows2000没有softice运行的时候,在IDT里的INT01入口点描述符就会处于DPL=0的状态,这样如果一个ring-3级别的任务试图在DPL=0的情况下调用INT01中断时,就会产生GP=INT0D错误,而这时WINDOWS的INT0D处理就会报告C0000005(EXCEPTION_ACCESS_VIOLATION例外访问异常)以及Eip的异常发生地址(newlaos:感觉就想是在OD里按shift+F9才能通过一样)。当softice运行后,在IDT里的INT01的入口点描述符就会处于DPL=3,这里ring-3级别的任务就能正常的访问到INT01中断。现在,如果一个INT01中断被调用后,那么下一个指令的地址就被压入堆栈,这时WINDOWS就会先查找设置了的断点,如果没有找到任何断点,windows将报告80000004异常(EXCEPTION_SINGLE_STEP异常单步) 。简而言之,如果softice运行时,一但INT01被调用,那么异常代码是80000004,异常地址是EIP+2;当softice没有运行则异常代码就是C0000005,异常地址是EIP。(详细数据请参考《intel手册》第3卷)

PElock就是利用异常地址(exception address)的不同来检测softice是否被运行。一个正常的SEH处理被设置!

004044A3 CD 01 int 1              ; 如果softice没有运行,这里就是异常地址
004044A5 C3 retn                  ; 如果softice运行了,那么这里就是异常地址
004044A6 ; -----------------------------------------------------------
004044A6 EB 36 jmp short near sice_not_present 如果softice没有运行,就从这里跳走
004044A6 ; -----------------------------------------------------------
004044A8 90 90 90 90 90 90+ Softice_detected db 35h dup(90h) 这里是softice被检测到了
004044DD ; -----------------------------------------------------------
004044DD C3 retn
004044DD ; -----------------------------------------------------------
004044DE 90 90 90 90 90 90+sice_not_present db 40h dup(90h) 到这里就说明softice没有运行

<--- Inside SEH handler在SEH处理内 --->
004040E4 83 A8 B8 00 00 00+ sub [eax+CONTEXT.Eip], 0FFFFFFFDh ; Eip加上3个字节
004040E4 FD                                           ; 如果softice运行, int1 异常就
004040E4                                              ;会去掉上面eip的2个字节

因此,如果softice被检测到了,程序进程就会改变,随后PELock就会退出。要避过这个检测,我们就必须将IDT里的INT01入口点描述符改回DPL=0。在WIN2K里运行idt ;来获得中断描述符表的地址,然后在softice里运行:

d <IDT的地址>

再将IDT表的开始偏移ODh字节处的"EE"改为"8E",如果想得到更详细的说明,可以参考《IDT格式的反汇编技巧》一书。

3、输入表的重建

当你在内存中给IDT打好这个补丁后,UnhandedExceptionFilter会中断两次,但PElock会很顺利的运行,全然不知softice同时也在运行。我们下一步所要做的就是获取一份有效的输入表数据。运行PElock,在第一次UnhandledExceptionFilter断点上,你会处于"home-made" CetProcAddress函数里,这时下命令"bpm Eip x"来设置一个断点,当softice在新设的这个断点断下时,按一下F12,你就会处在IAT加载与重定向处理之间。

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:004059C3j
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:00405350j 输入表加载开始
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 handle(动态链接库语柄)
004053FE ; ------------------------------------------------------
00405400 90 90 90 90 90 90+ db 2Dh dup(90h)
0040542D ; ------------------------------------------------------
0040542D 8B F7             mov esi, edi                   ; 输入表数据块指针
0040542D ; ------------------------------------------------------
0040542F 90 90 90 90 90 90+ db 2Dh dup(90h)
0040545C ; ------------------------------------------------------
0040545C string_scan_loop:                                ; CODE XREF: seg000:00405461j
0040545C 8A 06             mov al, [esi]                  ; 通过简单循环扫描输入表数据块,
0040545E 46                inc esi                        ; 来查找NULL字节,并将指针移到DLL名称的尾部
0040545F 84 C0             test al, al                    ; 也就是说DLL名称是以NULL为结尾的
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:00405925j
004054B1 ; ------------------------------------------------------
004054B1 8B 46 01          mov eax, [esi+1]               ; 从输入表数据块里获取用hash加密的API名称
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_输入表解密循环 CRC_import_decrypt_loop:       ; CODE XREF: seg000:00405510j
00405506 2B 84 8D 0A 3D 00+ sub eax, [ebp+ecx*4+3D0Ah]    ; 通过这里代码区的CRC校检对
0040550D D3 C0            rol eax, cl                     ; hash加密的API 名称进行解密,这样同时
0040550F 49               dec ecx                         ; 也防止的打补丁的情况
00405510 75 F4 jnz short CRC_import_decrypt_loop            跳到CRC输入表解密循环
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                        ; 将hash加密API名称压入堆栈
00405586 ; ------------------------------------------------------
00405587 90 90 90 90 90 90+ db 32h dup(90h)
004055B9 ; ------------------------------------------------------
004055B9 52               push edx                        ; 将DLL handle(动态链接库语柄)压入堆栈
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              ; 这里就是 home-made GetProcAddress
00405619 ; ------------------------------------------------------
0040561E 90 90 90 90 90 90+ db 30h dup(90h)   <=== 你会断在这里*****
                                但这里的垃圾代码已经被我的idc脚本删除了
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           ;如果API没找到,就从这里跳走
0040566D ; ------------------------------------------------------
00405673 90 90 90 90 90 90+ db 5Bh dup(90h)
004056CE ; ------------------------------------------------------
004056CE F6 C1 80         test cl, 80h             ; 检测重定向标记
004056CE                                           ; 如果 cl=80 , API 将被重定向
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地址来更新输入表目录的First Thunk
004057A2 E9 1F 01 00 00   jmp next_entry_in_thunk  ; (newlaos:不知如何翻译Thunk)
004057A2 ; ------------------------------------------------------
004057A7 90 90 90 90 90 90+redirect_import db 8Bh dup(90h) ; CODE XREF: seg000:00405745j
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:004057A2j
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 ; '|'       ; 查找thunk结束的字节
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 这里跳转就说明还没有到thunk尾部
00405925 ; ------------------------------------------------------
0040592B 90 90 90 90 90 90+ db 32h dup(90h)
0040595D ; ------------------------------------------------------
0040595D 38 4E 01         cmp [esi+1], cl          ; 再次查找thunk结束的字节
0040595D ; ------------------------------------------------------
00405960 90 90 90 90 90 90+ db 28h dup(90h)
00405988 ; ------------------------------------------------------
00405988 0F 84 FC 17 00 00 jz import_done          ; 如果2个结束字节在一起
00405988                                           ; 也就是到了输入表数据的尾部
00405988 ; ------------------------------------------------------
0040598E 90 90 90 90 90 90+ db 35h dup(90h)
004059C3 ; ------------------------------------------------------
004059C3 E9 8A F9 FF FF jmp near ptr next_dll_entry 跳到下一个dll入口点

我希望上面的解释足够让大家明白其过程。最基本的是输入表数据存入在一个大的数据块中,这个大数据块是以"FE"为结束字节或标记的。每一个DLL输入表的入口点都是以ASCII形式的DLL名称开始的,后面紧跟着输入表的入口参数表,最后以"FE"结束。每个输入表的入口点都是由5个字节组成,第1个字节就是ASCII形式的API名称的第1个字符,接着的4个字节就是被hash加密的API的名字(newlaos:第1个字节是明码,后4个字符被加密)。下面是一个输入表数据块的例子:

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 ;  结束字节
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 ;   结束字节
00407E77 FF                    db 0FEh ;  

上面每个入口点都作为参数传递给"home-made" GetProcAddress 函数(假定系统DLL输出目录以字母表的顺序存放),然后"home-made" GetProcAddress 函数就会对整个输出目录进行查找,直到找到与第1个字节相匹配的输出函数,然后就开始对输出目录里的每个首字符相同的API函数名称进行hash加密,最终找到与所需的hash加密后完全相同的API函数,一但这个API函数地址被找到,程序就会对bpx中断进行检测并通过EAX的值返回检测结果。正如你所见到的,API函数是否要重定向关键是由4056CE行的比较与在405745行的条件跳转决定的。我们希望PElock对所有的API函数都不进行重定向,但对跳转进行打补丁的方法也是不可取的,因为这里还用了CRC校检,来计算被hash加密了的输入表的值(newlaos:一但发现比较不对也就是意味程序被修改了,其结果可想而知)。对这种情况的解决方法有两个,对"home-made" GetProcAddress 打补丁,使ECX的值为0,这样在405745的跳转就始终不会跳转了,或是修改405745的ZF标记位。我选择的是第二种方法,在softice里通过一个简单的命令就可以实现:

bpm 405745 x if ZFL==1 do "r fl z; x"

因为上面这个命令,Softice的屏幕不停的因为bpm中断而闪动,你所要做的就是从屏幕前离开一会儿:) 当你再次回到屏幕前时,PElock就象任何都没有改动一样地运行着。现在运行Import Reconstructor 1.4.2,在OEP(程序入口点)处填入1000,它会自动为你在28000处找到IAT,点击GET Import,输入表除了1个地方都能够被修正。剩下的这个就留给读者当作练习,试着找出是哪个API函数和为什么它仍然被重定向。(newlaos:晕倒,我们是菜鸟呀!!!)

4、解密exe目标程序

当你修改了程序的IAT(输入表)的重定向问题,并获得了一个有效的输入表后,将它保存好备用。现在是完全解密PElock.exe和对它进行脱壳的时候了。再次运行PElock,并运行LordPE,选择部分脱壳,在脱壳大小处填入PElock.exe的内存映象大小(PELock.exe image size)。再用IDA对脱出来的部分进行反汇编,你会发现里面还有很大一部分代码仍然处于加密状态,这些代码只是在程序运行需要时才进行解密。下面有一段当程序到达入口点时仍然处于加密状态的代码:

00401ECA E8 33 5A EF FF        call near ptr 2F7902h   ; 调用加密器CALL
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
00401F40                   ; -------------------------------------------------------------
00401F45 E8 02 00 00           dd 2E8h       ; code block property
00401F49                   ; -------------------------------------------------------------

你可以看见,在上面的dump.exe里Decryptor(解密器--程序中的一段解密代码)和 Re-encryptor(再加密器--程序中的一段加密代码)的地址并不在exe文件的内存映象中,相反,这两个地址在PE 加载器的内存中。很明显,PElock为了防止脱壳和解密,在最原始的exe文件里加了许多类似的加密代码段,而当这些代码需要执行的时候,PElock就是调用Decryptor(解密器)对它们进行解密,然后再进行执行。执行完毕后,再用加密器(Re-encryptor)将它们再次加密回去。对dump.exe地进一步研究发现有三对加-解密器在不对的地址,而且用的是完全不同的算法。

对程序进行动态跟踪的话,我们可以知道在每个加-解密的CALL后面的两个字节(注译为"code block property")是代码长度加密后的值。就算对解密算法完全了解也无法得到个一完好的exe目标文件的脱壳文件,只能是对它如何工作有个大概了解。为了保证这个篇文章在一个合理的长度内,我也不会讨论其算法。

要获得一份有用脱壳后的文件,我们就必须强行让PElock在到达入口点之前就将所有代码段进行解密。我们也对解密器(Decryptor)打个补丁让它将代码段进行解密,并且用一个简单的跳转指令将那些加密器(encryptor)覆盖掉,但问题是我们如何知道已经解密了所有加了密的代码段之后来到入口点呢?我一直在思考这个问题,并且认为由于PElock加载器在每台机器的偏移不同,所以加载器也必须知道在哪里插入对解密器和加密器的调用,因此PElock加载器就需要计算每次CALL的相对地址,那将这些相对地址放入exe目标文件中,这样肯定就有一个表,表里存放的必定是那些CALL需要插入的地址偏移。实际上正是如此。重新启行PElock,并在原始的exe文件里的加密器(Decryptor)处下bpm断点,softice一断下来就正好处理这些处理之间

0040DBA8 插入加密器的第三号引擎(第三种加密算法) ; CODE XREF: seg000:00407578p
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] ; 获得加密器的地址
0040DBE1 ; -----------------------------------------------------------
0040DBE7 90 90 90 90 90 90+ db 21h dup(90h)
0040DC08 ; -----------------------------------------------------------
0040DC08 B9 03 00 00 00     mov ecx, 3          ; 表的大小,只有3个入口点
0040DC08 ; -----------------------------------------------------------
0040DC0D 90 90 90 90 90 90+loop_inserting_all_engine_loc db 24h dup(90h) ;
0040DC31 ; -----------------------------------------------------------
0040DC31 8B 06              mov eax, [esi]      ; 从表中记取一个双字
0040DC31 ; -------------------; 而每一个双字就是CALL将插入的地址
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 ; 设置CALL的字节
0040DC88 ; -----------------------------------------------------------
0040DC8B 90 90 90 90 90 90+ db 2Dh dup(90h)
0040DCB8 ; -----------------------------------------------------------
0040DCB8 40                 inc eax
0040DCB9 8B FA              mov edi, edx         ; edi -> 加密器的地址
0040DCBB 2B F8              sub edi, eax         ; 计算CALL的偏移
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后插入CALL的偏移
0040DCEB ; -----------------------------------------------------------
                                             补丁使得强行解密代码
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 -> 为加密代码段(加密器)
should be placed
0040DD1B ; -----------------------------------------------------------
0040DD1E 90 90 90 90 90 90+ db 2Eh dup(90h) ; 这里设置另一个CALL
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)      ; 调用加密器
0040DD7E ; -----------------------------------------------------------
0040DD7E 40             inc eax
0040DD7E ; -----------------------------------------------------------
0040DD7F 90 90 90 90 90 90+ db 2Ch dup(90h)      ; edi -> 加密器的地址
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 ; 计算CALL的偏移
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           ; 插入 call 偏移
0040DE26 ; -----------------------------------------------------------
0040DE28 90 90 90 90 90 90+ db 2Eh dup(90h)      ; to finalise the Call
0040DE56 ; -----------------------------------------------------------
0040DE56 49             dec ecx                  ; 计数器减1
0040DE56 ; -----------------------------------------------------------
0040DE57 90 90 90 90 90 90+ db 36h dup(90h)      ; 如果没有到表的尾端,循环返回
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

上面的注释已经十分的清楚了。加密了的代码的地址存入在表里,通过读取表里每个入口点,然后E8xxxxxxxx 再将它用加密器将这个代码进行加密,再基于加密了的代码段的长度计算出加密器的位置。我们需要给这段代码打个补丁,以便使它将地址表里的所有加密了的代码段进行解密,而不将加-解密器的调用放入这些地址。在上面代码段的注释里我已经用橙色将需要打补丁的地方标示出来(newlaos:我用*****将其标示出来)。"Call eax"是用来模拟原始exe执行的,因些会引发对解密器的调用,然后将那个地址后代码段进行解密。然而,我们必须给解密器打个补丁使之不能象正常的那样执行返回,必须让它执行返回到我的补丁循环中。这可以通过简单修正堆栈值来到达这个目的。

我们必须找到解密器(解密程序段),就象下面:

ret      ; 对代码段进行解密后,准备执行返回

要改成

pop eax ;eax为解密后的代码段地址

ret      ;执行返回到我的补丁循环中

5、CRC陷井

正如上面所提到的,程序中有三个不同的加-解密器,因此有三个分开的不同表,里面存放那些CALL的地址,每对加-解密器就是根据这个地址将它们插入exe目标文件中的。为了获得一份有用脱壳文件,我们就必须给这3个程序段打上补丁,来使程序强行解密全部代码段。然而,随之而来的问题是插入进程的CALL地址表是加了密的,并且要通过CRC解密来获得这相表的入口。而试图打补丁的话就会造成址表入口点的解密错误。下面就是进程:

00408368                     Insert_Crypt_Engine_1: ; 加入加密引擎1
00408368 B9 4A 00 00 00      mov ecx, 4Ah           ; 表1的大小
00408368 ; -----------------------------------------------------------
0040836D 90 90 90 90 90 90+ db 27h dup(90h)
00408394 ; -----------------------------------------------------------
00408394 8D 75 E8            lea esi, [ebp-18h]     ; 表1开始
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 ->解密器的地址
004083BE ; -----------------------------------------------------------
004083C4 90 90 90 90 90 90+get_crypted_offset db 55h dup(90h) ; CODE XREF: ...
00408419 ; -----------------------------------------------------------
00408419 8B 06               mov eax, [esi]         ; 从表中读取加了密的入口点
0040841B EB 31               jmp short near ptr prepare_CRC_data_pointer
0040841B ; -----------------------------------------------------------
0040841D 90 90 90 90 90 90+set_no_of_loop 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 minuserials_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' ; 设置 CALL
004084E8 E9 09 01 00 00      jmp near ptr minuserials_1_byte
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 check_end_of_table?
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 获取CALL的偏移
00408536 ; -----------------------------------------------------------
0040853B 90 90 90 90 90 90+minuserials_length_4_bytes db 30h dup(90h) ; CODE XREF: ...
0040856B ; -----------------------------------------------------------
0040856B 83 EF 04            sub edi, 4             ; 将指针减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:(CRC校验循环)  ; CODE XREF: ...
0040859A 03 84 9D F6 05 00+  add eax, [ebp+ebx*4+5F6h]
004085A1 D1 C8               ror eax, 1              ; loop calculating the address
004085A3 4B                  dec ebx                 ; where CALL to decrypt code
004085A4 75 F4               jnz short CRC_loop      ; can be inserted to
004085A6 E9 0C FF FF FF      jmp near ptr set_up_CALL_decrypt ; <=== 在这里下bpm断(设置解密CALL)
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+minuserials_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 -> 存放加密器CALL的地址
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 跳向加密CALL的地址
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断,直到上面代码运行结束

我知道跟踪起来很困难,因为代码段并不是连续,但这也不是我的错呀,而是PELOCK作者使得我们的生活变得复杂,我希望上面代码用不同颜色标示出其不同的重要性能有点帮助。加密算法和前面代码段的类似,唯一区别只是代码段长度的计算的不同,且表入口点的解密是在fly过程中完成的(newlaos:不明白)。从表入口点的解密转到加了密代码的地址基本上都是在40859A行的CRC_loop(CRC校验循环)里完成的。如果你在4085A6行下断点的话,就能在EAX里看见解密后的地址。这里,我能想出两个方法跳过CRC校验,第一个是将整修原始代码段拷贝到内存的其它地方,再被CRC进行打上补丁,让它将CRC校验指向新的地址;第二个是我们能再次使用softice的断点功能。我选择后者,虽然我的softice的屏幕会不停闪动,但这样更容易操作一些。

基本上,我让PElock加载器象正常一样执行,解密每个表入口点,然后将适当的CALL插在加了密的代码段的前面或是后面。然而,我会在4085A6行下一个断点,并将解密后的地址存放进表,这样我们到最后至少可以得一个解密了的表。

bpm 408813 x ; 在整个插入CALL动作循环结束后设置的断点!
bpm 4085A6 x do "dd esi+4 eax; x" ; 将EAX里解密后的值存放进表
                              ; 因为esi 指针占用4字节,所以我们也必须加4

当softic屏幕不再闪动时,我们也到了最后的一个断点,你就会在softice的数据窗口看到一个新的解密后的代码段地址表(newlaos:地址表已经被解密,但代码段仍处于加密状态)。剩下来我们要做的就是在softice窗口里加一些代码,以使其强行加密地址表里的加了密的代码段

a eip
        mov ecx, 4A            ; 表的大小
load_   lodsd                  ; 从表里读取一个入口点
        push dword ptr [eax+5] ; 保存代码段的特征值
        pushad
        call eax               ; 引发原始exe的执行
        popad
        pop dword ptr [eax+5]  ; 取出代码段的特征值
        mov word ptr [eax], 7EB ; 在解密器CALL处找一个跳过(Jump over)补丁
        mov ebx, word ptr [eax+5] ; 读取代码段的特征值
        ror ebx, 3             ; 获得代码段长度
        add eax, ebx
        add eax, 19            ; 计算加密器CALL的地址
        mov word ptr [eax], 7EB ; 用一个跳过指令替换掉加密器CALL
        dec ecx                ; 地址表数目减1
        test ecx, ecx
        jne load_              ; 地址表如果没有取完的话就再次循环取下一个的入口点
        jmp eip                ; 无限循环, 在这里脱壳

当你完成对另外两个加-解密器进程的修改,并将那里的加了密的代码段解密后,最后才来进行上面这个代码的操作。这样当我们再次运行PElock,并到达无限跳转循环时,在目标exe文件里所有的加了密的代码就被完全解密了,三对加-解密器CALL都被打了跳过的补丁。

6、找OEP入口点

现在我们已经获得了一个解密并修改好了的脱壳文件,是再次运行Import Reconstructor 1.4.2并且修复输入表的时候了。最后的任务就是找到OEP入口点,相对PElock来说这个过程相对简单。仔细观查dump.exe反汇编代码和16进制编辑模式,我们会看到在第一个区块是以很多字符串开始的,后面紧跟着就是代码,由此我们可以猜测到OEP就是在这些字符串的最后。

另一个找PElock保护文件的OEP通用方法是就是找到PELOCK是从哪里跳到OEP的(newlaos:费话,我们需要的是方法),这发生在前面我们提到的三个加-解密器CALL将那些进程插入目标exe文件之后。

00407578 E8 2B 66 00 00     call insert_crypt_engine_3  ; 第三个加密引擎
00407578 ; -----------------------------------------------------------
0040757D 90 90 90 90 90 90+ db 28h dup(90h)             ; 这里负责插入动作
004075A5 ; -----------------------------------------------------------
004075A5 E8 0C 73 00 00     call insert_crypt_engine_2  ; 这里调用加-解密器CALL2
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)             ; 这些代码仍处于加密状态
<-------------省 略-------------->                      ; 直到OEP的最后一刻才解密
00407AA1 5D pop ebp ;
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

因此当你发现任何插入进程(通过加-解密将合适的CALL插入目标exe文件的主进程),你就能在堆栈里找到它们是从哪里调用过来的,并在那里适当地下bpm断点。当然你也可能在那里脱出一个加载器来,并在IDA里更好观察研究。到达OEP后,再跟进几步就会有一个简单的解密和SEH循环,用来防止动态跟踪,但如果用IDA就能轻松找到象上面代码那样PUSH OEP(压入入口点)的动作。

7、最后的思考

哇,一个地狱般的加壳保护程序。其中肯定有很多我并没有讨论到,因为那样会使这篇教程变得太长。我也相信当你读到这里时,不会再想读我的任何文字的。我也写累了,或许找个其它的时候将它写全吧:-)

总而言之,PElock是一个非常好的加壳保护程序,对它进行逆向也的确有乐趣。如果PElock将加-解密器CALL放置在打包时,并且从表中去除加密代码的地址的话,那样将会使我们解密所有代码段的工作变得更难,以致于我都不敢确信是否能够完全将所有代码段解密(newlaos:现在的PELOCK2.33是不是这样?)。这里有个问题是壳加载器总是会在不同的偏移处进行加载工作,这样PElock在打包时才能知道CALL的偏移地址,但它可以用CALL dword ptr [xxxxxxxx] 方法而不是在xxxxxxxx处有个修复的地址。啊,这只是一个建议。

希望你能喜欢这篇教程,如果你觉得这篇教程写得好或是有哪此地方需要改进的话,来信告诉我!

原 著: crUsAdEr

翻 译: newlaos[CCG][DFCG] 2003年6月17日

感 谢:DFCG所有兄弟的鼓励和支持

声 明:
① 转载请保持本文完整。
② 限于本人 E 文水平,译文难免有错,一切要以原文为准。
③ 本人不承担本译文导致的一切责任和后果。

2004-5-5 12:07
0
雪    币: 218
活跃值: (70)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
大家可以先试试脱 PELock 1.06 加壳的 notepad 档及unpackme!(下载附件)

我用Ollydbg 跟到OEP只要 10秒,但Dump下来的档案却有一大堆指针无法修?,
即使用手动修?指针,却没法Fix dump 下来的档案,真怪!!!

另外,之前Fly大大有脱过 PELock 1.06 加壳的unpackme ,这个东西就简单多了, 我用Ollydbg 脱,只要30秒就搞定(用Ollydump无需修复指针就可运行),怎会差这麽多???

我把Fly大大的文章贴在下面,也给大家参考!

模拟跟踪 之 PELock 1.06脱壳――fly杀手unpackme
  
相关页面:  http://tongtian.net/pediybbs/viewtopic.php?p=11089#
软件大小:  17.35 KB

【加壳方式】:PELock 1.0x -> Bartosz Wojcik 加壳的小东东。

【作者声明】:初学Crack,只是感兴趣,没有″他目的。失误之处敬请诸位大侠赐教!

【调试环境】:WinXP、Ollydbg1.09、PEiD、LordPE、ImportREC

―――――――――――――――――――――――――――――――――  
【脱壳过程】:
           
         
  
goodmorning 说:“fly杀手unpackme  专门对付fly,嘿嘿。 ” 晕倒   抽点时间看了一下。反跟踪、花指令、SEH、单步陷阱…… 也算是一个猛壳啦。 因为这只是一个简单的unpackme,有些PELock的效果没有发挥出来,比如这里没能用上Stolen Code、输入表也只有简单的几个函数,所以被偶凑巧搞定了   有兴趣的兄弟可以直接试试pelock.exe主程序,呵呵,偶还没搞定pelock.exe主程序。

――――――――――――――――――――――――
一、运用 模拟跟踪 走到OEP     

设置Ollydbg忽略除了“内存访问异常”之外的所有″他异常选项。用Ollydbg手动脱壳,老规矩:用IsDebug 1.4插件去掉Ollydbg的调试器标志。载入后弹出“是压缩代码――要继续进行分析吗?”,点“否”

00405150     F3:                  prefix rep:  //进入OD后停在这!

F9运行,弹出Demo版PELock保护的提示,确定后返回。

00371AD1     8900                 mov dword ptr ds:[eax],eax//第1次异常
00371C7E     8900                 mov dword ptr ds:[eax],eax//第2次异常
00371E1B     8900                 mov dword ptr ds:[eax],eax//第3次异常
00372C8F     8912                 mov dword ptr ds:[edx],edx//第4次异常
00372E27     891B                 mov dword ptr ds:[ebx],ebx//第5次异常
00372EC2     8909                 mov dword ptr ds:[ecx],ecx//第6次异常
00374986     8900                 mov dword ptr ds:[eax],eax//第7次异常

Shift+F9通过7次异常,再来一次就运行啦,停!看看堆栈:

0012FFB0   0012FFB8  指针到下一个 SEH 记录
0012FFB4   003746D4  SE 句柄  //当然是此处下断啦  

在003746D4处下断,Shift+F9断在003746D4处。OK,当然可以F7一步步单步走啦,但是很慢  

003746D4     E8 01000000          call 003746DA//此处偶开始 模拟跟踪!

☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
关于 模拟跟踪,建议先看看[FCG]的 菩提 兄翻译的《ollydbg的教学-Run trace》,在《看雪论坛精华五》里。

现在让偶们复习如下几个命令,偶这次用到的就是 TC 命令。
TI :跟踪进入直到地址
TO :跟踪步过直到地址
TC :跟踪进入直到满足条件  //脱壳一般使用这个命令较多  
TOC:跟踪步过直到满足条件
TR :运行直到返回
TU :运行直到用户代码
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆

当偶们来到003746D4处时下命令:TC EIP>00400000   
很快的,程序由于进入系统DLL,地址大于00400000而暂停!

77F833A0     64:8B25 00000000     mov esp,dword ptr fs:[0]//停在这里!
77F833A7     64:8F05 00000000     pop dword ptr fs:[0]
77F833AE     8BE5                 mov esp,ebp
77F833B0     5D                   pop ebp
77F833B1     C2 1400              retn 14

如果直接Ctrl+F9执行到返回的话,程序就运行了,所以偶只好想点办法。
点击 查看->运行跟踪,在最下面就会发现是003748DB处进入系统DLL

003748DB     C3                   retn //这里返回入系统DLL

所以偶在下面的许多JMP处下断,F9运行,断在00374988处。呵呵,运气不错啦  

003748DC     EB 02                jmp short 003748E0
003748DE     0FC8                 bswap eax
003748E0     EB 02                jmp short 003748E4
003748E2     CD 0A                int 0A
003748E4     78 03                js short 003748E9
003748E6     79 01                jns short 003748E9
003748E8     92                   xchg eax,edx
003748E9     74 03                je short 003748EE
003748EB     75 01                jnz short 003748EE
003748ED     D7                   xlat byte ptr ds:[ebx+al]
003748EE     EB 02                jmp short 003748F2
003748F0     CD20 EB034CAF        vxdcall AF4C03EB
003748F6     B7 EB                mov bh,0EB
003748F8     034D 6F              add ecx,dword ptr ss:[ebp+6F]
003748FB     68 2BC0EB03          push 3EBC02B
00374900     D5 A4                aad 0A4
00374902     12E8                 adc ch,al
00374904     0100                 add dword ptr ds:[eax],eax
00374906     0000                 add byte ptr ds:[eax],al
00374908     04 8F                add al,8F
0037490A     44                   inc esp
0037490B     24 FC                and al,0FC
0037490D     EB 02                jmp short 00374911
0037490F     0F11EB               movups xmm3,xmm5
00374912     02CD                 add cl,ch
00374914     2076 03              and byte ptr ds:[esi+3],dh
00374917     77 01                ja short 0037491A
00374919     F8                   clc
0037491A     EB 03                jmp short 0037491F
0037491C     57                   push edi
0037491D     F8                   clc
0037491E     48                   dec eax
0037491F     EB 03                jmp short 00374924
00374921     BF C41FE801          mov edi,1E81FC4
00374926     0000                 add byte ptr ds:[eax],al
00374928     00FD                 add ch,bh
0037492A     8D6424 04            lea esp,dword ptr ss:[esp+4]
0037492E     64:FF30              push dword ptr fs:[eax]
00374931     76 03                jbe short 00374936
00374933     77 01                ja short 00374936
00374935     CC                   int3
00374936     7E 03                jle short 0037493B
00374938     7F 01                jg short 0037493B
0037493A     AB                   stos dword ptr es:[edi]
0037493B     EB 02                jmp short 0037493F
0037493D     74 8E                je short 003748CD
0037493F     EB 02                jmp short 00374943
00374941     0F12E8               movhlps xmm5,xmm0
00374944     0100                 add dword ptr ds:[eax],eax
00374946     0000                 add byte ptr ds:[eax],al
00374948     05 8F4424FC          add eax,FC24448F
0037494D     C1F5 00              sal ebp,0
00374950     78 03                js short 00374955
00374952     79 01                jns short 00374955
00374954     CC                   int3
00374955     64:8920              mov dword ptr fs:[eax],esp
00374958     EB 02                jmp short 0037495C
0037495A     30DE                 xor dh,bl
0037495C     E8 01000000          call 00374962
00374961     0F8F 4424FCEB        jg EC336DAB
00374967     020F                 add cl,byte ptr ds:[edi]
00374969     3D EB027E75          cmp eax,757E02EB
0037496E     78 03                js short 00374973
00374970     79 01                jns short 00374973
00374972     2AEB                 sub ch,bl
00374974     031C03               add ebx,dword ptr ds:[ebx+eax]
00374977     6BEB 02              imul ebp,ebx,2
0037497A     0F0F                 ???   
0037497C     E8 01000000          call 00374982
00374981     3F                   aas
00374982     8F4424 FC            pop dword ptr ss:[esp-4]
00374986     8900                 mov dword ptr ds:[eax],eax
00374988     EB 03                jmp short 0037498D    //断在这里  

OK,偶继续模拟跟踪啦。取消所有断点,下命令:TC EIP>00400000  几秒钟后Ollydbg自动暂停在OEP处!

查看运行跟踪,发现是从下面的地方跳到OEP的  

00374C7A     68 00104000          push 401000 //下面返回的地址
00374C7F     EB 01                jmp short 00374C82
00374C81     65:EB 02             jmp short 00374C86
00374C84     CD20 EB027853        vxdcall 537802EB
00374C8A     EB 02                jmp short 00374C8E
00374C8C     0FCB                 bswap ebx
00374C8E     EB 02                jmp short 00374C92
00374C90     65:79 C1             jns short 00374C54
00374C93     F600 EB              test byte ptr ds:[eax],0EB
00374C96     010F                 add dword ptr ds:[edi],ecx
00374C98     EB 01                jmp short 00374C9B
00374C9A     65:C1F0 00           sal eax,0
00374C9E     C1F0 00              sal eax,0
00374CA1     C3                   retn    //飞向光明之巅!返回至00401000  

BTW:选择适当的时机和条件使用 ★模拟跟踪★ 会节省很多单步跟踪的时间!!

――――――――――――――――――――――――

00401000       E8 7D010000        call unpackme.00401182//在这儿用OllyDump插件直接DUMP  
00401005       8BF8               mov edi,eax
00401007       50                 push eax

―――――――――――――――――――――――――――――――――
二、修复输入表以及优化脱壳后的程序

运行ImportREC,选择这个进程。把OEP改为00001000,点IT AutoSearch,点“Get Import”,用“追踪层次1”修复,只有一个函数没有识别出来:

0  00002008  ?  0000  00BB0029

正好接着跟踪原程序,在402008处下内存访问断点,退出时断下!

0040117C       FF25 08204000      jmp dword ptr ds:[402008]//进入
77E55CA2       FFD6               call esi ; ntdll.ZwTerminateProcess//来到这里退出

发现来到77E55CA2处,所以这个函数应该是kernel32.ExitProcess啦,修正之,运行正常!

简单优化:用LordPE删除.pelock和.newIID区段,然后重建PE。20K->7.78K  

―――――――――――――――――――――――――――――――――
     
                                 
         ,     _/  
        /| _.-~/            \_     ,        青春都一饷
       ( /~   /              \~-._ |\
       `\\  _/                \   ~\ )          忍把浮名  
   _-~~~-.)  )__/;;,.          \_  //'
  /'_,\   --~   \ ~~~-  ,;;\___(  (.-~~~-.        换了破解轻狂
`~ _( ,_..--\ (     ,;'' /    ~--   /._`\  
  /~~//'   /' `~\         ) /--.._, )_  `~
  "  `~"  "      `"      /~'`\    `\\~~\   
                         "     "   "~'  ""

     

       Cracked By 巢水工作坊――fly [OCN][FCG][NUKE]

                  2003-12-06  21:25
2004-5-5 13:05
0
雪    币: 93
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
你不知道注册版和未注册版加的壳区别很大吗?

等你先把加壳的记事本脱了再来脱主程序吧,跟到OEP是很简单,PELock主要难点就在修复 (IAT+Replace Code)
2004-5-5 13:40
0
雪    币: 898
活跃值: (4039)
能力值: ( LV9,RANK:3410 )
在线值:
发帖
回帖
粉丝
6
jingulong 兄发过《PeLock 1.06加壳98记事本的脱壳修复》
在新兵论坛,等几天论坛镜像做好就可以查看了
2004-5-5 19:01
0
雪    币: 494
活跃值: (629)
能力值: ( LV9,RANK:1210 )
在线值:
发帖
回帖
粉丝
7
jingulong的那篇文章个人认为过分简练,大段的代码也
没有注释,偶有点吃不消。:p

我在exetools的中文区贴了一篇文章,和jingulong的
意思差不多,尽量详细了一些。有兴趣可以看看。只是
是用那个US_unpackme3做的例子,希望不要怪罪。

BTW,我觉得公开讨论一下那几个unpackme也无妨,探讨
技术嘛。版主不必担心。就算都脱了,也并不等于水平
就上去了。水平没到那个层次,混进团体自己也无趣。

我花了不少时间,搞了2个,惭愧,慢慢练。对我这个
脱壳菜鸟,玩这个真是很耗精力啊。 :)
2004-5-5 21:36
0
雪    币: 182
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
最初由 softworm 发布

我在exetools的中文区贴了一篇文章,和jingulong的
意思差不多,尽量详细了一些。有兴趣可以看看。只是
是用那个US_unpackme3做的例子,希望不要怪罪。


exetools现在不能注册了,能不能贴到这里来啊?
2004-6-6 16:17
0
游客
登录 | 注册 方可回帖
返回
//