=================================================================
[翻译]Armadillo 4.30a ? 最少保护下的脱壳教程
=================================================================
译者说:
目的:学习研究,技术交流,禁止用于商业用途及目的,否则后果自负.
作者:haggar
1. 准备工作
你需要以下工具理解此教程:
目标:http://www.reversing.be/binaries/articles/2005092823071234.rar
Windows XP
OllyDbg1.10
ImpREC
LordPE
PEID0.93(可选)
脱Armadillo最小保护的程序很简单,而且这一类型的程序可以在网上找到。我不明白为什么开发者不使用所有的选项,可能当程序要维持一些如注册表清洁、碎片整理等等功能时,双进程会减缓目标程序的运行。不管怎样,现在我们需要解决以下问题:
Olly OutputDebugStringA的使用;
锁文件的PE文件头修改;
输入表重定向和修改。
2. 到达OEP
首先Olly选项忽略所有异常。在Olly中打开目标程序,(右键)单击“前往”->“表达”,输入VirtualAlloc,确定。你会来到Kernel中的VirtualAlloc:
77E7ABC5 PUSH EBP <--------------------- VirtualAlloc的开始语句
77E7ABC6 MOV EBP,ESP
77E7ABC8 PUSH DWORD PTR SS:[EBP+14]
77E7ABCB PUSH DWORD PTR SS:[EBP+10]
77E7ABCE PUSH DWORD PTR SS:[EBP+C]
77E7ABD1 PUSH DWORD PTR SS:[EBP+8]
77E7ABD4 PUSH -1
77E7ABD6 CALL kernel32.VirtualAllocEx
77E7ABDB POP EBP
77E7ABDC RETN 10 <---------------------- 此处(F2)下断Amadillo不会发现。
为什么要这样做?(因为)我们需要找到Armadillo在哪里解压和装载自身DLL。中断时,EAX寄存器将会保存内存块分配的基址,那是DLL将被解压的地方。F9中断第一次后EAX=0。再F9一次,EAX将保存一些值。在我的机器上EAX=00AA0000,你机器上的值可能会不同。现在清除断点,在命令栏中“bp OutputDebugStringA”。F9一次,你会来到这里:
77E9B493 PUSH 22C <------------------------ 来到这里(22C值可能也会不同)
77E9B498 PUSH kernel32.77E9BE60
77E9B49D CALL kernel32.77E7A22B
...
...
...
77E9B4CB CALL kernel32.RaiseException
77E9B4D0 OR DWORD PTR SS:[EBP-4],FFFFFFFF
77E9B4D4 CALL kernel32.77E7A2F2
77E9B4D9 RETN 4
这里是Armadillo准备崩溃Olly的地方。Olly不支持%s%s...(寄存器窗口中可以看到此值)字符串因此崩溃。我们需要去除此校验。这不难,只要把API的第一个操作数改为最后的(返回)语句即可。因此,(我们)清除断点,RETN 4替换PUSH 22C:
77E9B493 RETN 4 <-------------------------- 修改后
77E9B496 NOP
77E9B497 NOP
77E9B498 PUSH kernel32.77E9BE60
...
...
...
77E9B4CB CALL kernel32.RaiseException
77E9B4D0 OR DWORD PTR SS:[EBP-4],FFFFFFFF
77E9B4D4 CALL kernel32.77E7A2F2
77E9B4D9 RETN 4
现在“bp CreateThread”,(F9)运行Olly。你会中断在Kernel的CreateThread中(是在NAG窗口出现以后)(NAG窗口之后很重要,之前的中断不是),清除断点,ALT+F9返回主程序中:
00AB94C4 POP EDI <----- 来到这里
00AB94C5 POP ESI
00AB94C6 LEAVE
00AB94C7 RETN <-------- F7跟踪执行此RET
退出RET后你会来到:
00AC972D POP ECX
00AC972E MOV EDI,0AD8910
00AC9733 MOV ECX,EDI
...
...
...
00AC97ED CALL ECX
00AC97EF JMP SHORT 00AC9814
00AC97F1 CMP EDX,1
00AC97F4 JNZ SHORT 00AC9817
00AC97F6 PUSH DWORD PTR DS:[ESI+4]
00AC97F9 MOV EDX,DWORD PTR DS:[EAX+88]
00AC97FF XOR EDX,DWORD PTR DS:[EAX+84]
00AC9805 PUSH DWORD PTR DS:[ESI+8]
00AC9808 XOR EDX,DWORD PTR DS:[EAX+40]
00AC980B PUSH 0
00AC980D PUSH DWORD PTR DS:[ESI+C]
00AC9810 SUB ECX,EDX
00AC9812 CALL ECX <----------------------- 跳向OEP
00AC9814 MOV DWORD PTR SS:[EBP-4],EAX
00AC9817 MOV EAX,DWORD PTR SS:[EBP-4]
00AC981A POP EDI
00AC981B POP ESI
00AC981C LEAVE
00AC981D RETN
你看到最后的那个CALL ECX了吗?那是跳向OEP的(CALL)。在3.xx版本之前,这里是CALL EDI而不是CALL ECX,但(现在)Armadillo开发者修改了(这里)。他修改了一些小处以阻止一般的脱壳机和Olly scripts(脱壳)。这是别人告诉我的,事实也确是如此。上述CALL是跳向OEP的,因此执行它后你会来到OEP处(1。此CALL前有一跳转,跳则不能到达,需要修改。2。执行=F7进入)。
004013FB PUSH EBP <--------------------- OEP
004013FC MOV EBP,ESP
004013FE PUSH -1
00401400 PUSH Armadill.004040B8
00401405 PUSH Armadill.00401F30
0040140A MOV EAX,DWORD PTR FS:[0]
00401410 PUSH EAX
00401411 MOV DWORD PTR FS:[0],ESP
00401418 SUB ESP,58
0040141B PUSH EBX
0040141C PUSH ESI
0040141D PUSH EDI
0040141E MOV DWORD PTR SS:[EBP-18],ESP
00401421 CALL DWORD PTR DS:[40402C] <----- 这里是一些输入表
00401427 XOR EDX,EDX
00401429 MOV DL,AH
...
...
...
这时你找到了OEP,但是如果你现在DUMP文件,它将是坏的和锁定的,因为Armadillo修改了PE header的三个值。另外还有Stolen输入表的问题。
3. PE文件头问题
如果你打开内存镜象,你会发现PE文件头是坏的,Olly无法识别:
00400000 00001000 Armadill Imag R RWE <--- PE头
00401000 00003000 Armadill .text Imag R RWE
00404000 00001000 Armadill .rdata Imag R RWE
00405000 00001000 Armadill .data Imag R RWE
00406000 00050000 Armadill .text1 code Imag R RWE
00456000 00010000 Armadill .adata Imag R RWE
00466000 00020000 Armadill .data1 data,imports Imag R RWE
00486000 00030000 Armadill .pdata Imag R RWE
004B6000 00002000 Armadill .rsrc resources Imag R RWE
Armadillo删除的3个值分别是:DOS头中的PE头偏移,PE头中的节数量和程序入口点。修正方法就是打开另一个Olly,打开目标文件,二进制复制(ALT+M->PE部分双击->全选->右键->二进制->二进制复制。粘贴同)整个PE头并粘贴到这里。现在你可以用LordPE DUMP文件了,但是在ImpREC中有一些未解决的thunks,我这里是16个。跟踪级别1得到的是错误的输入表因此不能依赖它。
4. IAT问题
Rdata节是保存输入表thunks的。去看看(在你到达OEP的后),你会发现一些值是错的:
00404020 FF 7E AB 00 7E 17 E6 77 AF 81 AB 00 A3 81 AB 00 .~..~..w........
00404030 D6 69 AB 00 99 6A AB 00 0C E6 E7 77 95 9B E9 77 .i...j.....w...w
00404040 FC AC E7 77 2F E0 E9 77 E8 E4 E7 77 9C A8 E7 77 ...w/..w...w...w
例如,上面节选中的首个值FF7EAB00不是输入表的值。下个值是正确的:7E17E677。你会发现,首个值指向ArmDLL。我们需要找到IAT在哪儿被重定向并阻止它。Olly中重起目标程序,修正OutputDebugStringA问题,在00404020处下硬件写入双字断点(数据窗口->前往->表达->00404020选中->右键->断点->硬件写入->双字)。F9一次后(在OutputDebugStringA问题后)(要经常清除断点),你会中断如下:
77C42F43 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
77C42F45 JMP DWORD PTR DS:[EDX*4+77C43058] ; MSVCRT.77C43068
77C42F4C MOV EAX,EDI ; Armadill.00404024
77C42F4E MOV EDX,3
77C42F53 SUB ECX,4
77C42F56 JB SHORT MSVCRT.77C42F64
77C42F58 AND EAX,3
77C42F5B ADD ECX,EAX
77C42F5D JMP DWORD PTR DS:[EAX*4+77C42F70]
77C42F64 JMP DWORD PTR DS:[ECX*4+77C43068]
77C42F6B NOP
77C42F6C JMP DWORD PTR DS:[ECX*4+77C42FEC]
...
...
这是第一次中断,它并不重要。F9第二次,你来到了重点:
00AC6979 CMP DWORD PTR DS:[EAX+8],0 <-------------- 比较列表中的所有API是否都已校验
00AC697D JE SHORT 00AC69C8
00AC697F PUSH 100
00AC6984 LEA EAX,DWORD PTR SS:[EBP-3BF4]
00AC698A PUSH EAX
00AC698B MOV EAX,DWORD PTR SS:[EBP-3AF4]
00AC6991 PUSH DWORD PTR DS:[EAX]
00AC6993 CALL 00ACCF05
00AC6998 ADD ESP,0C
00AC699B LEA EAX,DWORD PTR SS:[EBP-3BF4]
00AC69A1 PUSH EAX
00AC69A2 LEA EAX,DWORD PTR SS:[EBP-3AE4]
00AC69A8 PUSH EAX
00AC69A9 CALL DWORD PTR DS:[ACE384] ; MSVCRT._stricmp <--- [3] 比较API名
00AC69AF POP ECX
00AC69B0 POP ECX
00AC69B1 TEST EAX,EAX
00AC69B3 JNZ SHORT 00AC69C6 <--------------- [4]如果API在列表中,则不跳转
00AC69B5 MOV EAX,DWORD PTR SS:[EBP-3AF4]
00AC69BB MOV EAX,DWORD PTR DS:[EAX+8]
00AC69BE MOV DWORD PTR SS:[EBP-32E4],EAX
00AC69C4 JMP SHORT 00AC69C8
00AC69C6 JMP SHORT 00AC6964
00AC69C8 MOV EAX,DWORD PTR SS:[EBP-28A4]
...
...
...
00AC6B64 MOV DWORD PTR DS:[EAX],ECX <--------- [2] 这里是值写入的地方。
00AC6B66 MOV EAX,DWORD PTR SS:[EBP-24EC] <---- [1] 中断在这里
我们到达了重定向值写入的地方,但是可惜的是我们远离了大段的代码。主要部分在[5],Armadillo用来比较自身列表中的所有API名。如果API在列表中,跳转[4]不实现,API被修改。这就是我们用来对付API重定向的地方。跳转[5]只是比较是否所有的API名都取完。因此我们只要修改跳转[4]的JNZ为JMP即可。但是现在已经晚了,因为大多数的输入表已经被重定向。我们记住跳转地址,在我的电脑上是00AC69B3。写下这个数据,重新开始。
Olly中重起目标,修正Olly检测。在CPU窗口中选择“前往”->“表达”,输入跳转地址,我的是00AC69B3,来到:
00AC69B3 JNZ SHORT 00AC69C6
...
...
到达跳转。很好。现在修改为JMP。接着,“bp CreateThread”,到达OEP。打开ImpREC,(get imports)得到输入表,单击“(show invalid)显示无效”,(cut)切除所有的无效thunks,(Fix dump)修正dump,运行一下,成功。
5. 优化文件
添加到加壳程序的Armadillo代码很大。加壳文件520kb,脱壳文件740kb。我们可以使用LordPE精简。使用LordPE的PE编辑器(PE editor)打开脱壳文件。首先修改BaseOfCode的6000为1000,BaseOfData的66000为5000。接着打开“节”选项。你会看到Armadillo添加了:.text1,.adata,.data1和.pdata。右键单击上述节,选择“擦除节头(wipe section header)”。“关闭”节表。“保存”修改。打开LordPE“选项”,选中(“DumpFix”,“Realign file…->hardcore”)“dump修正”,“重分配文件为…->核心”以及(“validate PE”)“使PE有效”。现在(“rebuilder”)“重组”脱壳文件,文件大小精简到原来的2%->22kb。
6. 总结
教程并不难,练习了最少保护下的Armadillo。下篇教程是关于Armadillo一般保护的。基本是同最少保护的,不同的是有一些包含阻止内存断点的加密和CRC校验而已。
感谢所有来到BIW reversing的同行们。
[课程]Android-CTF解题方法汇总!