目标软件:The Armadillo Software Protection System Version2.52 build 1164
目标文件:Armadillo.exe
加壳方式:Armadillo 2.52
使用工具:WinDbg或trw2000, peditor, WinHex 10.2 SR-2,m$的win32
sdk文档,<winnt.h>
URL:
http://www.siliconrealms.com/
本文作者:leo_cyl
Armadillo
的保护方式主要有以下特点:修改pe头,使WinDbg等调试器无法“attach”,使procdump无法dump;anti-debug代码(我以前的贴子有讨论);利用调试器捕获页面保护异常,分段解码。
(一)捕获页面保护异常、找OEP
和以前版本一样,Armadillo 外壳产生另外一个进程(被加壳的程序)。所以下断点“bpx
createprocessa”
004C39E4
call ds:CreateProcessA
004C39EA
test eax, eax
004C39EC
jnz 4C39F5
……
……
0187:004C3A0F 51
PUSH ECX
0187:004C3A10 68E6314C00 PUSH
DWORD 004C31E6
0187:004C3A15 6A00
PUSH BYTE +00
0187:004C3A17 6A00
PUSH BYTE +00
0187:004C3A19 FF1554A04C00
CALL `KERNEL32!CreateThread`
0187:004C3A1F 6A00
PUSH BYTE +00
0187:004C3A21
FF1530A14C00 CALL `KERNEL32!GetModuleHandleA`
……
……
……
0187:004C3A42 6A00
PUSH BYTE +00
0187:004C3A44 FF1530A14C00 CALL
`KERNEL32!GetModuleHandleA`
……
……
0187:004C3A68 E80C0C0000
CALL 004C4679
0187:004C3A6D 83C40C
ADD ESP,BYTE +0C
0187:004C3A70 8B8D68FEFFFF
MOV ECX,[EBP+FFFFFE68]
0187:004C3A76 51
PUSH ECX
0187:004C3A77
8B157CE54C00 MOV EDX,[004CE57C]
0187:004C3A7D
52 PUSH EDX
0187:004C3A7E E8BA0C0000 CALL 004C473D
以上代码在被加壳的程序第一条指令处写入“eb fe”代码,即“jmp eip” 使被加壳程序“挂起”
0187:004C3A83
83C408 ADD ESP,BYTE +08
0187:004C3A86 A17CE54C00 MOV EAX,[004CE57C]
0187:004C3A8B 8B4804 MOV
ECX,[EAX+04]
0187:004C3A8E 51
PUSH ECX
0187:004C3A8F FF158CA04C00 CALL
`KERNEL32!ResumeThread`
0187:004C3A95 8B157CE54C00 MOV
EDX,[004CE57C]
0187:004C3A9B 8B4208
MOV EAX,[EDX+08]
0187:004C3A9E 50
PUSH EAX
0187:004C3A9F FF1588A04C00
CALL `KERNEL32!DebugActiveProcess`
0187:004C3AA5 8B0D7CE54C00
MOV ECX,[004CE57C]
0187:004C3AAB 8B5104
MOV EDX,[ECX+04]
0187:004C3AAE
52 PUSH EDX
0187:004C3AAF FF1584A04C00 CALL `KERNEL32!SuspendThread`
以上代码通过调用“DebugActiveProcess”开始调试被加壳的程序。
……
……
……
0187:004C3AFA 68E8030000 PUSH DWORD 03E8
《===== dwMilliseconds
0187:004C3AFF 8B9574FEFFFF MOV
EDX,[EBP+FFFFFE74]
0187:004C3B05 52
PUSH EDX 〈====
lpDebugEvent
0187:004C3B06 FF1580A04C00 CALL `KERNEL32!WaitForDebugEvent`
0187:004C3B0C 85C0 TEST
EAX,EAX
0187:004C3B0E 0F8414060000 JZ NEAR
004C4128
0187:004C3B14 C78570FDFFFF68B4+MOV DWORD [EBP+FFFFFD70],004CB468
0187:004C3B1E 33C0 XOR
EAX,EAX
0187:004C3B20 A06EE54C00 MOV
AL,[004CE56E]
0187:004C3B25 85C0
TEST EAX,EAX
0187:004C3B27 7565
JNZ 004C3B8E
0187:004C3B29 8B4DE4
MOV ECX,[EBP-1C]
0187:004C3B2C
81E1FF000000 AND ECX,FF
0187:004C3B32 85C9
TEST ECX,ECX
0187:004C3B34
7458 JZ 004C3B8E
〈==跳转
0187:004C3B36 C645E400 MOV
BYTE [EBP-1C],00
0187:004C3B3A C745FC00000000 MOV
DWORD [EBP-04],00
0187:004C3B41 669C
PUSHFW +
0187:004C3B43
6658 POP AX
|
0187:004C3B45 66056200
ADD AX,62 |设置单步调试标志
0187:004C3B49 66059E00
ADD AX,9E |anti-debug???
0187:004C3B4D 6650 PUSH
AX |
0187:004C3B4F 669D
POPFW +
0187:004C3B51
66050100 ADD AX,01
0187:004C3B55
C745FCFFFFFFFF MOV DWORD [EBP-04],FFFFFFFF
0187:004C3B5C
EB30 JMP SHORT 004C3B8E
以上进入调试循环,根据DebugEvent结构中的debugging event code 做相应处理。其中最重要的是EXCEPTION_DEBUG_EVENT(0x00000001)。请参考MSDN中WaitForDebugEvent的描述。
……
……
0187:004C3BA5 MOV DWORD [EBP+FFFFFB60],80010001
0187:004C3BAF MOV EDX,[EBP+FFFFFE74] 〈====注意!edx
= lpDebugEvent
0187:004C3BB5 CMP DWORD [EDX],BYTE +01
//EXCEPTION_DEBUG_EVENT
0187:004C3BB8 JNZ
NEAR 004C3ED7
0187:004C3BBE MOV DWORD [EBP+FFFFFD70],004CB470
0187:004C3BC8 XOR EAX,EAX
0187:004C3BCA MOV
AL,[004CE56F]
0187:004C3BCF TEST EAX,EAX
0187:004C3BD1 JZ NEAR 004C3CB7
0187:004C3BD7 MOV
ECX,[EBP+FFFFFE74]
0187:004C3BDD CMP DWORD
[ECX+0C],80000001 //STATUS_GUARD_PAGE_VIOLATION
0187:004C3BE4
JNZ NEAR 004C3CB7
0187:004C3BEA MOV
DWORD [EBP+FFFFFD70],004CB488
0187:004C3BF4 MOV EDX,[EBP+FFFFFE74]
〈====注意!edx = lpDebugEvent
0187:004C3BFA MOV EAX,[EDX+24]
……
……
0187:004C3BB5 处比较是否是EXCEPTION_DEBUG_EVENT。(包括页面异常)
注意在NT下,页面异常的代码是0x80000001(STATUS_GUARD_PAGE_VIOLATION)而9x下是0xc0000005(STATUS_ACCESS_VIOLATION)所以在9x下会来到这里:
0187:004C3CBD CMP DWORD [ECX+0C],C0000005
//STATUS_ACCESS_VIOLATION
0187:004C3CC4 JNZ NEAR
004C3E89
0187:004C3CCA MOV EDX,[EBP+FFFFFE74] 〈====注意!edx
= lpDebugEvent
0187:004C3CD0 CMP DWORD [EDX+5C],BYTE
+00
0187:004C3CD4 JZ 004C3CE2
0187:004C3CD6 MOV
DWORD [EBP+FFFFFD70],004CB4E8
0187:004C3CE0 JMP
SHORT 004C3CEC
当被加壳的程序需要解码时,会触发页面异常,来到上述代码,如果你参考MSDN中有关DebugEvent结构的描述,很容易找到产生页面异常的地址,是在lpDebugEvent+0x18处。(即edx+18)。
这有什么用呢?呵呵……这是找OEP的关键!!!假如在0187:004C3CD0下断点,当程序第一次在这里中断时,查看edx+18h的内容“dd edx+18”即可看到OEP。(41EF80H)
继续跟踪……
0187:004C3D52 8B8D50FBFFFF MOV
ECX,[EBP+FFFFFB50]
0187:004C3D58 83E901
SUB ECX,BYTE +01
0187:004C3D5B 85C9
TEST ECX,ECX
0187:004C3D5D 7C14
JL 004C3D73
0187:004C3D5F
6A01 PUSH BYTE +01
0187:004C3D61 8B9550FBFFFF MOV EDX,[EBP+FFFFFB50]
0187:004C3D67 83EA01 SUB
EDX,BYTE +01
0187:004C3D6A 52
PUSH EDX
0187:004C3D6B E8D0030000 CALL
004C4140 //关键,进入
0187:004C3D70
83C408 ADD ESP,BYTE +08
0187:004C3D73 8B8550FBFFFF MOV EAX,[EBP+FFFFFB50]
0187:004C3D79 83C001 ADD
EAX,BYTE +01
0187:004C3D7C 3B0588E54C00 CMP
EAX,[004CE588]
0187:004C3D82 7D14
JNL 004C3D98
以上代码将产生页面异常的地址进行页对齐,并CALL 004C4140解码。
进入函数004C4140后,来到这里:
0187:004C4271 PUSH BYTE +00
0187:004C4273 MOV ECX,[EBP+08]
0187:004C4276 PUSH
ECX
0187:004C4277 CALL 004C432A 〈==这里对异常页解码
0187:004C427C ADD ESP,BYTE +08
0187:004C427F AND
EAX,FF
0187:004C4284 TEST EAX,EAX
0187:004C4286
JNZ 004C428F〈===解码成功?
0187:004C4288 XOR
AL,AL
0187:004C428A JMP 004C4326
0187:004C428F
MOV EDX,[004CE58C] 〈==注意!!!
0187:004C4295 ADD
EDX,BYTE +01
0187:004C4298 MOV [004CE58C],EDX
0187:004C429E MOV EAX,[004CE588]
0187:004C42A3 LEA
ECX,[EAX*4+FFFFFFFC]
注意0187:004C428F处,[004CE58C]是一个记数器,解码成功后加一,有什么用呢?看后面……
……
……
0187:004C42D1 25FF000000 AND
EAX,FF
0187:004C42D6 85C0
TEST EAX,EAX
0187:004C42D8 754A
JNZ 004C4324
0187:004C42DA 8B0D8CE54C00
MOV ECX,[004CE58C]
0187:004C42E0 3B0D2CB44C00
CMP ECX,[004CB42C]
0187:004C42E6 7E3C
JNG 004C4324
0187:004C42E8
6A01 PUSH BYTE +01
0187:004C42EA 8B158CE54C00 MOV EDX,[004CE58C]
当解码一定数量的页面后,将以前解码的页面重新加密!在这里的数量是0x13页。为了以后脱壳方便。
我用peditor计算出0187:004C428F的实际偏移,然后用WinHex,将ADD
EDX,BYTE +01改为ADD EDX,BYTE +00;只改了一个byte。
简要分析0187:004C4277处的解码函数“ CALL
004C432A”。以下是它的流程:
1。调用VirtualProtectEx将异常页面属性改为PAGE_READWRITE
2。调用ReadProcessMemory,将异常页面读入
3。解码。
4。调用WriteProcessMemory,写入正确代码。
5。调用VirtualProtectEx将页面属性改为PAGE_EXECUTE_READ
(二)修改PE头
被加壳的程序运行后,用procdump没法dump出,会非法操作。用ring3的调试器没法“attach”,用peditor
dump出的文件是无效的exe。为什么呢?通过比较dump出的PE头,发现偏移0x3c处被修改。重新装入Armadillo,下断点“bpm 400003c
w”,执行……
0187:00A8EF54 MOV EAX,[EBP-0C] 〈==
EAX=400000H
0187:00A8EF57 LEA ECX,[EBP-04]
0187:00A8EF5A SUB EBX,EDI 〈=== EBX = 4E7119
0187:00A8EF5C PUSH ECX
0187:00A8EF5D ADD
[EAX+3C],EBX 〈== 修改PE头,[EAX+3C] = 40003C
0187:00A8EF60 PUSH
DWORD [EBP-04]
0187:00A8EF63 PUSH BYTE +40
……
……
……
0187:00A8EF89 AND EAX,BYTE +03
0187:00A8EF8C
LEA ECX,[EBP-08]
0187:00A8EF8F INC
EAX
0187:00A8EF90 ADD [EDI+06],AX 〈==修改PE头,[EDI+06]
= 4000FE
0187:00A8EF94 CALL 00A88597
0187:00A8EF99 MOV
EBX,EAX
共修改3C和FE两处,参考有关PE头的资料,可知偏移3C的DWORD是PE表头的偏移(原值是F8)。偏移FE的WORD是sections
的个数(原值是8)。Armadillo故意将PE头改错,难怪没法dump出。
(三)dump出加壳的程序
好了,原理分析清楚了。要dump出加壳程序就很容易了。
事先用peditor查看,得知size of image 是104000;
首先找到OEP是41EF80,另外修改004C428F,将ADD
EDX,BYTE +01改为ADD EDX,BYTE +00。
在41EF80下断点。中断后,将PE头改回,40003C处是000000F8,4000FE处是0008,注意高位在前!!!
在程序中找一个没用到的地址。不要选代码段,因为解码后会覆盖掉我们写的代码。我选.Data1段的最后256个byte即4CF000处,将eip改为4CF000,输入以下代码:
push esi
push ecx
push eax
mov esi,401000
mov ecx,103000
//size of image 减 1000h
rep lodsb
pop eax
pop ecx
pop esi
int3
注意我没有使用hying的代码,因为要申请内存和查找api地址,而且内存不够的话也麻烦,其实只要扫描一遍内存就足以触发调试器了,修改004C428F的原因就是防止外壳把解码过的页面从新加密。
f5执行,在int3处停下(注意打开 i3here on),将eip改回41EF80,可看到已经解码了,挂起进程,回到window桌面,这时用什么工具都可以dump出了!(任我鱼肉了!!)。
另外,还有一个地方要注意,最好在OEP处挂起进程再dump,因为这时程序的初始化变量还没有被修改。
大家可对比一下,当程序运行后,用procdump
dump出的文件和在OEP处dump出的有何不同。(会非法操作!!因为初始化变量[438110]已经不同了。)
(四)修复IAT
没什么方便的方法!:(
我的方法是用peditor查看,得知IAT在426000处,下断点“bpm 426000 w” 在这停下:
0187:00A8E4BB 8A06 MOV
AL,[ESI]
0187:00A8E4BD 3AC3
CMP AL,BL
0187:00A8E4BF 7468
JZ 00A8E529
0187:00A8E4C1 3CFF
CMP AL,FF
0187:00A8E4C3 7537
JNZ 00A8E4FC
0187:00A8E4C5
668B7E01 MOV DI,[ESI+01]
0187:00A8E4C9
46 INC ESI
0187:00A8E4CA 0FB7C7 MOVZX
EAX,DI
0187:00A8E4CD 50 PUSH
EAX
0187:00A8E4CE 46
INC ESI
0187:00A8E4CF FF75F4
PUSH DWORD [EBP-0C]
0187:00A8E4D2 46
INC ESI
0187:00A8E4D3 E84075FFFF
CALL 00A85A18
然后把[ESI]前后的内容保存下来,以后手工修复IAT时参考。
(五)小结
无论用什么方法脱壳,原理和hying的是一样的。
但为什么用procdump 不会触发调试器呢?我的解释是这样:无论procdump
或 WinHex 在读取其他进程的内容时,是通过WriteProcessMemory来完成的,而WriteProcessMemory最终调用VMM完成读取,VMM是在ring0上,所以不会触发ring3上的调试器。我能想到的方法是:在ring3下、在自身进程空间内扫描整个内存,以触发调试器。
附录:
附上部分DEBUG_EVENT结构的声明:(注释是我加的)
typedef struct _DEBUG_EVENT
{
DWORD dwDebugEventCode; //偏移0x0
DWORD dwProcessId;
//偏移0x4
DWORD dwThreadId;
//偏移0x8
union {
EXCEPTION_DEBUG_INFO Exception;
//偏移0xC
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO
ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO
DebugString;
RIP_INFO RipInfo;
} u;
} DEBUG_EVENT, *LPDEBUG_EVENT;
typedef struct _EXCEPTION_DEBUG_INFO
{ //即异常代码是0x00000001时的结构
EXCEPTION_RECORD ExceptionRecord;
//偏移0xC;0x80000001为STATUS_GUARD_PAGE_VIOLATION(NT)
//0xC0000005为STATUS_ACCESS_VIOLATION(WIN (win
9x)
DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
//偏移0xc
DWORD ExceptionFlags;
//偏移0x10
struct _EXCEPTION_RECORD
*ExceptionRecord; //偏移0x14
PVOID ExceptionAddress;
//偏移0x18
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;
- 标 题:Armadillo 2.52加壳原理分析和改进的脱壳方法 (12千字)
- 作 者:leo_cyl1
- 时 间:2002-4-18
17:08:58
- 链 接:http://bbs.pediy.com