网站首页 → 文章4217内容



Armadillo v3.x for copymem-II 脱壳完全篇――上篇

 阅读:3642  2024-07-01 18:07



    ‌‍Armadillo v3.x for copymem-II 脱壳完全篇――上篇
    ――查找OEP和代码修复
    
    这是一篇脱Armadillo加壳软件的个人的经历过程,本人特别声明:这个过程是参照了Bighead[DFCG][YCG]翻译的Ricardo Narvaja的“Getright 5 脱壳和重建”文章操作的,可以说是个照葫芦画瓢做的。感谢Bighead[DFCG][YCG]和相关的翻译者,感谢Ricardo Narvaja的文章。
    这次拿来练习的软件是FCG的peterdocter老大提供的软件,下载地址:http://peter.88vip.com/armadillo3.rar
    工具:
    Ollydbg v1.09d中文版
    PuPe 2002版 感谢yesky1兄提供
    LordPE Deluxe-1.4 by yoda
    Import Reconstructor 16f
    准备好笔和纸(不能少喔!),一杯茶和好是心情^_^.让我们开始一个艰苦和漫长的脱壳过程吧(因为我菜!)。Armadillo是当今猛壳之一啦。其CopyMem-II+Debug-Blocker的加壳方式是非常强劲的,好在有牛人在前面带路,虽艰难倒也不迷失方向。
    (根据Ricardo Narvaja 的文章:必须说明一下,此教程仅在 Windows XP 调试。不要尝试在 Windows 98 或者 Windows 2000 下进行。事实上是因为只有 Windows XP 已经与必要的 API 脱钩了。我是windows2003应该符合要求了。)
    开始吧!~
    SETUP 1 BP IsDebuggerPresent
    用OllyDbg 装入需要脱壳的软件armadillo3.exe(其实是个crackme),开始我们的第一道关,反anti_debug 这是每个Armadillo加壳软件都要做的第一件工作。在OD的命令行窗口中键入 BP IsDebuggerPresent。记住,我们应保留大小写字母正常状态,因为如果我们键入任何命令有异,我们可以在命令栏右侧的显示来反映出错误信息(未知的命令)。所以要正确写入 BP IsDebuggerPresent 然后按回车键。如果在按回车键后没有信息显示,这是表明此 BP 运行良好。(最好把OD的异常选择项全部勾上)F9运行一下就会来到IsDebuggerPresent函数的入口地址:
    77E2AC39 >MOV EAX,DWORD PTR FS:[18] ; <--中断在这里
    77E2AC3F MOV EAX,DWORD PTR DS:[EAX+30]
    77E2AC42 MOVZX EAX,BYTE PTR DS:[EAX+2] ; <--运行到这里
    77E2AC46 RETN
    在77E2AC42地址处停下,看看DS:[EAX+2]指向的地址7FFDF002的指是01(这个地址可能和你的不同),修改这个值为00 因为Armadillo通过IsDebuggerPresent函数来检查是不是被调试器调试,如果有调试器就会赋EAX值为01没有就赋EAX值为00。修改这个值就是告诉他没有调试器,自然不会出错拉!记下这个地址,以后经常会用到的。
    SETUP 2 BP WaitForDebugEvent
    下面该干什么呢?查找OEP的内存出现的地方,如果你用Peid来查看软件的OEP会得到一个错误的值。因为Armadillo会修改OEP地址。来看看怎么查找呢?在命令行中输入BP WaitForDebugEvent 然后回车。运行一下就会来到函数的入口:
    77E605B6 >PUSH EBP -停在入口
    77E605B7 MOV EBP,ESP
    77E605B9 SUB ESP,68
    77E605BC PUSH ESI
    在堆栈(Stack)窗口我们可以见到在此 API 函数有关参数的全部信息
    0012E220 0043776E /CALL 到 WaitForDebugEvent
    0012E224 0012EFF4 |pDebugEvent = 0012EFF4 //注意这个地址
    0012E228 000003E8 \Timeout = 1000. ms
    这个函数的作用我不知道,但我知道0012EFF4地址就是OEP将会出现的地方。来到转存窗口 G 0012EFF4 :
    0012EFF4 01 00 00 00 10 00 00 00 ... ...
    0012EFFC 00 00 00 00 A8 F0 12 00 .... .
    0012F004 00 00 50 00 64 EF 12 00 ..P.d?.
    0012F00C 5C F0 12 00 00 00 00 00 \?.....
    0012F014 5C F1 12 00 00 00 00 01 \?....
    0012F01C 58 EF 12 00 AC F0 12 00 X?. .
    0012F024 AC F2 12 00 34 5A F3 77  .4Z體
    0012F02C 58 BB F7 77 FF FF FF FF X击w
    0012F034 E7 DF F3 77 E1 26 F4 77 邕體?魒
    这一步的作用就是通过函数找到这个内存地址,因为他会出现真正的OEP入口。为什么?我也不知道。
    SETUP 3 Bp WriteProcessMemory
    命令栏写入 Bp WriteProcessMemory 断点然后点击 RUN。可能会在某些异常停下,那你可以通过按下 Shift + F9 顺利地绕过。这个软件还会出现未注册的Armadillo加壳提示,越过他。只要我们在 WriteProcessMemory API 停下了,千万不要触动任何键,来看看能有什么收获――
    堆栈窗口可以看到第一个块的全部信息:
    0012E0C0 0043AFC2 /CALL 到 WriteProcessMemory 来自 armadill.0043AFBC
    0012E0C4 00000044 |hProcess = 00000044 (window)
    0012E0C8 00425000 |Address = 425000
    0012E0CC 003A2630 |Buffer = 003A2630
    0012E0D0 00001000 |BytesToWrite = 1000 (4096.)
    0012E0D4 0012E1DC \pBytesWritten = 0012E1DC
    先来看看Ricardo Narvaja的文章里说的:
    是父进程将要复制到它的子进程的指引。此信息是保存在一个缓存之内,父进程来自 003A2630,并且它将会从 00425000开始以每 1000 字节块方式复制到子进程(字节写入),所以如果我们找到此方案,我们就可以很容易理解到子进程第一个块是完全空的,然后当子进程尝试执行 OEP 时将它返回一个错误信息,因为在这个点并没有什么数据,因此父进程获得报告,同样我们可以在父进程看到有关报表,所以它停止运行,在下一个错误前复制必要的数据块然后继续运行。该数据副本的数值大小是 1000 字节,所以当程序试图执行任何超过此块时,各种错误将会发生,然后错误将会向父进程报告,然后父进程将会复制另外的 1000 字节的块,诸如此类。
    这个块开始在 425000 一直到 425FFF。OEP 必定在其值之内。让我们来留意刚才的那个转存窗口:
    0012EFF4 01 00 00 00 B0 0E 00 00 ...?..
    0012EFFC B8 0E 00 00 01 00 00 80 ?.. ..€
    0012F004 00 00 00 00 00 00 00 00 ........
    0012F00C D0 51 42 00 02 00 00 00 蠶B. ...
    0012F014 00 00 00 00 D0 51 42 00 ....蠶B.
    0012F01C D0 51 42 00 00 00 00 00 蠶B.....
    0012F024 00 00 00 00 00 00 00 00 ........
    0012F02C 13 00 00 00 94 10 00 C0 ...?.
    注意窗口中蓝色的字节004251D0(应该倒过来),这个值是在425000到 425FFF之间的值。那么软件的OEP应该的004251D0
    呵呵,这段我先怎么也不明白。现在让我来解释一下:(我编程不行,所以说的不一定正确)
    WriteProcessMemory 函数是内存复制函数,大家知道Armadillo的copymem-II方式会在内存中生成同名的2个进程,其中一个是父进程,一个是子进程。那么子进程是怎么生成的呢,就是从父进程中用WriteProcessMemory函数复制来的。(这二个进程的窗口句柄不同,并且互相掌握着对方的OEP)SETUP2就是得到了内存复制信息的内存地址,因为复制是按1000的块进行的,所以第一个复制的块中必定包含了子进程的OEP地址。这个地址的值在第一块的值之间。所以上面的转存窗口中的蓝色那个地址就是真正的OEP了。Ricardo Narvaja真牛呀!
    OEP找到了,是不是就能dump出来呢?试试dump出来的代码又是错误的。可以肯定的是有一个函数在解密后又破坏了复制的数据,这就引出了下面来查找破坏数据的函数的方法。接着来――
    SETUP 4 NOP 填充 Cripter?Call (不让程序破坏解密的代码)
    父进程会为他的子进程解密一个块。但是仍有 Cripter Call 那些加密或破坏旧块来避免被转储。现在就以实际操作来查找这样的 Call 然后用 NOP 填充替换它。这是个艰苦和复杂的工作.
    首先在cpu窗口中右击鼠标出现功能菜单,选择中断的〔条件记录〕窗口。在条件表达式中填 〔ESP+U〕然后选择〔永远记录条件表达式〕。这个用来检查复制的数据块。
    怎么查找Cripter Call呢?
    在WriteProcessMemory函数的入口处停下。Alt+K打开调用堆栈窗口:
    呼叫堆栈
    地址 堆栈 例程 / 参数 调用来自 Frame
    0012E0C0 0043AFC2 ? kernel32.WriteProcessMemory armadill.0043AFBC
    0012E0C4 00000044 hProcess = 00000044 (window)
    0012E0C8 00425000 Address = 425000
    0012E0CC 003A2630 Buffer = 003A2630
    0012E0D0 00001000 BytesToWrite = 1000 (4096.)
    0012E0D4 0012E1DC pBytesWritten = 0012E1DC
    0012E1E8 00439D34 ? armadill.0043A07B armadill.00439D2F
    0012E21C 00437CA8 armadill.004398F5 armadill.00437CA3 0012E218
    0012F5BC 00434384 armadill.00436099 armadill.0043437F 0012F5B8
    0012FD20 00434BDA armadill.00433CF4 armadill.00434BD5 0012FD1C
    0012FF38 0043CF87 armadill.00434940 armadill.+0C9 0012FF34
    0012FF3C 00400000 Arg1 = 00400000 ASCII "MZP"
    0012FF40 00000000 Arg2 = 00000000
    0012FF44 00141EFB Arg3 = 00141EFB
    0012FF48 0000000A Arg4 = 0000000A
    
    看看这里:
    0012E0C0 0043AFC2 ? kernel32.WriteProcessMemory armadill.0043AFBC
    现在父进程的0043AFC2地址处调用了这个函数,向下看看:
    0012E1E8 00439D34 ? armadill.0043A07B armadill.00439D2F
    通过这个我们知道程序在00439D2F处也调用了这个函数。双击来到:
    00439D27 MOV ECX,DWORD PTR SS:[EBP+C]
    00439D2A PUSH ECX
    00439D2B MOV EDX,DWORD PTR SS:[EBP+8]
    00439D2E PUSH EDX
    00439D2F CALL armadill.0043A07B //这里调用了上面的函数
    00439D34 ADD ESP,0C
    00439D37 AND EAX,0FF
    00439D3C TEST EAX,EAX
    00439D3E JNZ SHORT armadill.00439D47
    00439D40 XOR AL,AL
    00439D42 JMP armadill.0043A074
    00439D2F CALL armadill.0043A07B 这个Call 是个解密的函数,那么肯定有一个破坏解密的函数。查找另外一个同样的Call的地方就是我们要找的地方,在OD中的:
    00439D2F CALL armadill.0043A07B 处右击功能菜单,选择〔查找参考〕……〔调用目标〕选项就会打开全部的调用什么Call的地方:
    参考位于armadill:.text 到 0043A07B
    地址 反汇编 注释
    00439D2F CALL armadill.0043A07B (初始 CPU 选择)
    00439FEA CALL armadill.0043A07B //原来这里有一个^_^
    双击00439FEA CALL armadill.0043A07B就会来到这个梦中的地方:
    00439FDA MOV ECX,DWORD PTR DS:[462AAC]
    00439FE0 MOV EDX,DWORD PTR DS:[462AB0]
    00439FE6 MOV EAX,DWORD PTR DS:[EDX+ECX*4]
    00439FE9 PUSH EAX
    00439FEA CALL armadill.0043A07B //这里 这里 这里
    00439FEF ADD ESP,0C
    00439FF2 PUSHFD
    00439FF3 PUSHAD
    00439FF4 JMP SHORT armadill.0043A021
    没有别的说了,nop他。
    
    SETUP 5 运行该 API 然后在 OEP 中生成一个死循环
    现在是时侯要执行 WriteProcessMemory 一次了。做法是我们可以在两个选项之间作出选择。一是要转到菜单 Debug|Execute till return 然后它将会运行直到 RET,然后按一次 F7。二是要转到堆栈第一行然后在那里设置 BPX 然后就 F9 运行它。 这段我就复制了Ricardo Narvaja的原文。我没得说了。反正会到API的调用处就可以了:
    0043AFB3 PUSH EDX ; |Address
    0043AFB4 MOV EAX,DWORD PTR DS:[462A9C] ; |
    0043AFB9 MOV ECX,DWORD PTR DS:[EAX] ; |
    0043AFBB PUSH ECX ; |hProcess
    0043AFBC CALL NEAR DWORD PTR DS:[<&KERNEL32.WriteProcessMe>; \WriteProcessMemory ********
    0043AFC2 TEST EAX,EAX
    0043AFC4 JNZ SHORT armadill.0043B001
    0043AFC6 PUSHFD
    0043AFC7 PUSHAD
    0043AFC8 JMP SHORT armadill.0043AFF5
    现在是 PUPE 运作的时候了,PUPE 是一个西班牙语的工具。你可以到 http://www.google.com网站搜索它,在下一行我们将要在子进程的 OEP 内生成一个死循环。此时将会静止子进程直到我们能够叫醒他。不过我没有找到,还是yesky1兄提供了这个工具。再次感谢!
    打开PUPE 选择进程中二个armadillo3.exe的上面的一个,位于上面的子进程,下面的父进程。我们选择的是子进程。然后在它上面按右键并选择 "Parchear"。打开了Parchear窗口。填入参数,No de bytes = 2 和 Introducir la direccion = 4251D0(子进程的OEP),然后点击 Buscar)记下在对话框见到的原始字节然后用死循环 EB FE 来替换原始字节,点击 " Parchear" 然后 INFINITE LOOP (死循环)便会就绪。
    呵呵,第一次使用还真不知道这是干什么呢,其实就是用PUPE修改入口的代码,让他变成跳转到EIP地址的代码。这样程序在入口点就会停下。跳向自己的地址,程序会向下执行吗?
    
    SETUP 6 BP WaitForDebugEvent 和 NOP 填充 API
    在命令行中键入 BP WaitForDebugEvent 然后按回车键,最后点击 RUN
    (为什么要下这个API请参照leo_cyl1大虾的文章)
    当停止后,会来到这里:
    77E605B6 >PUSH EBP //停在这里
    77E605B7 MOV EBP,ESP
    你要切记:在任何时候都绝对不要运行这个 API!留意一下在 (转储)窗口中的报表,然后转到 STACK (堆栈)窗口。这段我也只能抄,我太菜了。现在来看看堆栈窗口中的信息:
    0012E220 0043776E /CALL 到 WaitForDebugEvent //调用信息
    0012E224 0012EFF4 |pDebugEvent = 0012EFF4
    0012E228 000003E8 \Timeout = 1000. ms
    可以看到,如果我们运行这个 API,我们将要转到0043776E所以在主窗口用Go to|Expression =0043776E能到达那里。
    0043776E TEST EAX,EAX
    
    再一次提醒你:不要运行这个 API!因而在0043776E内按右键然后选择[新建起源]。这是跳过该 API 的正确做法。现在我们必须用 NOP 来填充调用到该 API 及其有关 Push。
    把下面的代码用nop修改掉:
    00437759 MOV AL,BYTE PTR DS:[E8686133]
    0043775E ADD EAX,DWORD PTR DS:[EAX]
    00437760 ADD BYTE PTR DS:[EBX+FFFA3895],CL
    00437766 CALL NEAR DWORD PTR DS:[EDX-1]
    00437769 ADC EAX,<&KERNEL32.WaitForDebugEvent>
    (不知道为什么这段代码是动态的)
    改为:
    00437759 NOP
    0043775A NOP
    0043775B NOP
    0043775C NOP
    0043775D NOP
    0043775E NOP
    0043775F NOP
    00437760 NOP
    00437761 NOP
    00437762 NOP
    00437763 NOP
    00437764 NOP
    00437765 NOP
    00437766 NOP
    00437767 NOP
    00437768 NOP
    00437769 NOP
    0043776A NOP
    0043776B NOP
    0043776C NOP
    0043776D NOP
    0043776E TEST EAX,EAX
    
    SETUP 7 打补丁
    
    “当处理这个步骤时要特别小心! 多数人并不知道究竟是如何打补钉的正确方法,所以要尽力领会我在这里是怎样做,并且你还需要在其它情况下会完成。” 我是试了几次才成功的!
    第一步是更改此跳转:
    0043776E TEST EAX,EAX
    00437770 JE armadill.004398A5 //把这里修改为 Jmp 00401000
    Why? 因为父进程的偏移是00401000 我们需要在那里打补丁。
    现在go 00401000 准备打补丁了:
    00401000 ADD BYTE PTR DS:[EAX],AL
    00401002 ADD BYTE PTR DS:[EAX],AL
    00401004 ADD BYTE PTR DS:[EAX],AL
    00401006 ADD BYTE PTR DS:[EAX],AL
    00401008 ADD BYTE PTR DS:[EAX],AL
    来看看内存镜象中的数据,Alt+M 打开内存镜象窗口:
    00400000 00001000 armadill PE header Imag R RWE
    00401000 00025000 armadill CODE Imag R RWE
    00426000 00001000 armadill DATA Imag R RWE
    00427000 00001000 armadill BSS Imag R RWE
    00428000 00002000 armadill .idata Imag R RWE
    0042A000 00001000 armadill .tls Imag R RWE
    0042B000 00001000 armadill .rdata Imag R RWE
    0042C000 00003000 armadill .reloc Imag R RWE
    0042F000 00020000 armadill .text code Imag R RWE
    0044F000 00010000 armadill .adata Imag R RWE
    0045F000 00010000 armadill .data data,imports Imag R RWE
    0046F000 00010000 armadill .reloc1 relocations Imag R RWE
    0047F000 00040000 armadill .pdata Imag R RWE
    004BF000 00011000 armadill .rsrc resources Imag R RWE
    
    看看程序的代码段开始于00401000 结束于00425FFF
    在转存窗口中把OEP的入口修改为400000,修改后是这样的:
    0012EFF4 01 00 00 00 B0 0E 00 00 ...?..
    0012EFFC B8 0E 00 00 01 00 00 80 ?.. ..€
    0012F004 00 00 00 00 00 00 00 00 ........
    0012F00C 00 00 40 00 02 00 00 00 ..@. ... //*******
    0012F014 00 00 00 00 00 00 40 00 ......@. //*******
    0012F01C 00 00 40 00 00 00 00 00 ..@..... //*******
    因为每个块循环以 1000作增量补钉,所以要解压的第一块必须是401000。
    
    现在我们必须在主窗口下以 401000 作开始行,并写入
    
    00401000 8105 0CF01200 00100000 ADD DWORD PTR DS:[12F00C],1000
    0040100A 8105 18F01200 00100000 ADD DWORD PTR DS:[12F018],1000
    00401014 8105 1CF01200 00100000 ADD DWORD PTR DS:[12F01C],1000
    这里补丁要和转存窗口中的地址一致。
    下一行就要这样写∶
    
    0040101E CMP DWORD PTR DS:[12F01C],armadill.00426000
    //测试代码段结束了吗?
    
    要知道何时我们已经解压了全部块。
    则必须写入∶
    00401028 - 0F85 F6341F00 JNZ 00437775 //没有完成就继续
    如果比较结果不是 True,那这个循环返回到的位置紧挨着哪里是调用该循环。然后我们必须写入下一行写 NOP ,我们将放置一个 BP,是要在完成转储操作时来停止它。
    下面是打好补丁的全部代码:
    00401000 ADD DWORD PTR DS:[12F00C],1000
    0040100A ADD DWORD PTR DS:[12F018],1000
    00401014 ADD DWORD PTR DS:[12F01C],1000
    0040101E CMP DWORD PTR DS:[12F01C],armadill.00426000
    00401028 JNZ armadill.00437775
    0040102E NOP //这里下个中断,如果完成了就断在这里。
    0040102F NOP
    
    好了,最关键的部分完成了,下面就可以脱壳了。
    
    SETUP 8 脱壳了
    检查一下前面的几步,保证他的正确后就运行这个程序了。F9一下,哈哈中断在:
    0040102E NOP //这里下个中断,如果完成了就断在这里
    好像成功了耶!
    看看记录里有些什么,Alt+L打开记录窗口:
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    77E65A12 COND: 未知的标示符
    0040102E 中断在 armadill.0040102E
    我不知道为什么成了未知的标示符,可能是我的条件记录没有设置好,不过那就是复制的代码块。有了这个信息就说明代码被完全的复制过来了。
    
    SETUP 9由父进程解出子进程
    
    先用OD的附加功能查看子进程的句柄,打开附加窗口,看到没有变色的那个armadillo3.exe就是子进程,他的句柄是OBDC (这个值每次加载后就会变)
    在程序中写入下面的代码:
    0040102E PUSH 0BDC //PUSH (子进程句柄)
    00401033 CALL kernel32.DebugActiveProcessStop
    00401038 NOP // 这里下个中断
    运行程序。看看寄存器窗口,如果当 程序 在 EAX = 1 停下时,可以确定子进程与他的父进程已经分离,然后我们可以关闭 OllyDbg。而如果 EAX = 0,那是因为你写入有点不对劲(可能是句柄),则你必须要从头到尾核对那些行。你可以重新写入代码,再来一次。
    现在关闭 OllyDbg 然后再次打开它。不要装入任何东西。转到菜单 文件|附加 然后寻找子进程并且 附加上 它(我们已经杀死他的父进程 :X)。
    
    只要成功附加上,运行一下,程序就会在死循环运行,因此按 F12 来暂停程序,然后在PUPE中把OEP的代码还原成55 8B ,再“Parchear”一下就会还原成原来的代码了。
    打开 LordPE 然后搜索armadillo3.exe这个进程。选择这进程以及选择〔active dump engine〕| 〔IntelliDump〕|〔select〕,然后点击 Dump full ,一旦保存完成,立即运行 PEditor (,然后再写入这个有效的入口点( ENTRY POINT)。计算一下是4251D0-400000=251D0 修复即可。
    
    第一部分已完,写了一个晚上。太冷了,第二部分关于如何查找这奇妙跳转来完成引入表、修复 IAT 和生成程序运行 还是留给明天吧。
    
    fxyang[OCN][BCG][FCG]
    







传奇3G私服


2024-07-01 18:07


上一篇:王者版NFIIIB 的BUG修改与反馈集中发放
下一篇:传奇三私服服务端怪物数据表appr值的算法二
"
火爆游戏推荐
  • 王者传奇3

  • 神话传奇3

  • 四川传奇3

  • BOSS传奇3

  • 魅22传奇3

  • 五五传奇3

5dc7站推荐

换一个