|
|
|
用Ollydbg手脱Armadillo V3.60加壳的DLL
好文章,赶紧收藏,呵呵!! |
|
老大能帮我看看这个是什么用什么壳的吗?
这个壳有anti-debug,我调试了几次,olydbg 都无法调试而中断,用ollydbg隐身插件也没用,还请FLY大大教教方法! 不过用peid是可以查到入口点,但就是有anti,无法脱壳! |
|
|
|
PECompact 2.06加壳的记事本和主程序的脱壳和伪破解
David 大大又来贡献了啊,不错喔,很喜欢看你的文章,写得很仔细,对我们菜鸟来说,实在是没有负担!!! 希望DFCG的论坛赶快恢复,你的很多脱壳的好文章,我都还来不及看呢!!! |
|
PELock 1.06 如何脱壳,请教一下!
大家可以先试试脱 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 |
|
PELock 1.06 如何脱壳,请教一下!
我找到一篇经翻译过的国外脱壳教程,但是用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 文水平,译文难免有错,一切要以原文为准。 ③ 本人不承担本译文导致的一切责任和后果。 |
操作理由
RANk
{{ user_info.golds == '' ? 0 : user_info.golds }}
雪币
{{ experience }}
课程经验
{{ score }}
学习收益
{{study_duration_fmt}}
学习时长
基本信息
荣誉称号:
{{ honorary_title }}
能力排名:
No.{{ rank_num }}
等 级:
LV{{ rank_lv-100 }}
活跃值:
在线值:
浏览人数:{{ visits }}
最近活跃:{{ last_active_time }}
注册时间:{{ user_info.create_date_jsonfmt }}
勋章
兑换勋章
证书
证书查询 >
能力值