【文章标题】: 不使用 ImportREC 来脱 PECompact 2.x 版本的壳
【文章作者】: CCDebuger
【下载地址】: 自己搜索下载
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
  
  写这篇文章主要是看到了这个求助帖:http://bbs.pediy.com/showthread.php?t=81974,就把里面提到的程序拿来看了一下。这里我把不用 ImportREC 来脱 PECompact 2.x 版本壳的过程写下来,再来一个脱壳脚本,简化一下流程。现在进入正题吧。
  载入程序后断 BP VirtualAlloc,两次中断后会返回到这里:
  

引用:
  00EA0C30    6A 40           PUSH 40
  00EA0C32    68 00100000     
PUSH 1000
  00EA0C37    51              
PUSH ECX
  
00EA0C38    6A 00           PUSH 0
  00EA0C3A    
FF95 371F9400   CALL DWORD PTR SS:[EBP+941F37]
  
00EA0C40    8985 271F9400   MOV DWORD PTR SS:[EBP+941F27],EAX        
; 返回到这
  
00EA0C46    56              PUSH ESI
  
00EA0C47    E8 F6030000     CALL 00EA1042
  00EA0C4C    8D8D 
BD1D9400   LEA ECX,DWORD PTR SS:[EBP+941DBD]
  
00EA0C52    85C0            TEST EAX,EAX
  
00EA0C54    0F85 94000000   JNZ 00EA0CEE
  00EA0C5A    56              
PUSH ESI
  
00EA0C5B    E8 40030000     CALL 00EA0FA0                            
; ; 这里面用解码后的内容填充原程序的各个区段,F7跟进
  
00EA0C60    56              PUSH ESI
  
00EA0C61    E8 55020000     CALL 00EA0EBB
  00EA0C66    90              
NOP
  
00EA0C67    90              NOP
  
00EA0C68    90              NOP
  
00EA0C69    90              NOP
  
00EA0C6A    90              NOP
  
00EA0C6B    90              NOP
  
00EA0C6C    90              NOP
  
00EA0C6D    90              NOP
  
00EA0C6E    90              NOP
  
00EA0C6F    90              NOP
  
00EA0C70    90              NOP
  
00EA0C71    90              NOP
  
00EA0C72    90              NOP
  
00EA0C73    90              NOP
  
00EA0C74    8B4E 34         MOV ECX,DWORD PTR DS:[ESI+34]
  
00EA0C77    85C9            TEST ECX,ECX
  
00EA0C79    0F84 89000000   JE 00EA0D08
  00EA0C7F    034E 08         
ADD ECX,DWORD PTR DS:[ESI+8]
  
00EA0C82    51              PUSH ECX
  
00EA0C83    56              PUSH ESI
  
00EA0C84    E8 47060000     CALL 00EA12D0                                             
; 处理输入表,F7跟进
  
00EA0C89    85C0            TEST EAX,EAX
  
00EA0C8B    74 7B           JE SHORT 00EA0D08
    一、区段:
  跟进 00EA0C5B 的 CALL:
  
引用:
  00EA0FA0    55              PUSH EBP                                 ; 一路按F8
  
00EA0FA1    8BEC            MOV EBP,ESP
  
00EA0FA3    83C4 E8         ADD ESP,-18
  00EA0FA6    53              
PUSH EBX
  
00EA0FA7    57              PUSH EDI
  
00EA0FA8    56              PUSH ESI
  
省略码...
  
00EA101B    5A              POP EDX
  
00EA101C    8B75 FC         MOV ESI,DWORD PTR SS:[EBP-4]
  
00EA101F    8B3B            MOV EDI,DWORD PTR DS:[EBX]
  
00EA1021    03FA            ADD EDI,EDX                                               
; 各个区段的开始偏移加上基址,从最后一个开始加。个区段的起始偏移就在EDI中
  
00EA1023    8B4B 08         MOV ECX,DWORD PTR DS:[EBX+8]
  
00EA1026    8BC1            MOV EAX,ECX
  可以直接按 Ctrl+S 搜索以下命令序列:
  
  MOV R32,DWORD PTR [R32-CONST]
  MOV R32,DWORD PTR [R32]
  ADD R32,R32
  
  区段:
  1000
  00281000
  00315000
  0036D000
  0037C000
  00387000
  00389000
  0038A000
  0038B000
  0038C000
  0038D000
  0038E000
  0038F000
  
  二、输入表:
  跟进 00EA0C84 那个处理输入表的CALL:
  
引用:
  00EA12D0    55              PUSH EBP                                                  ; 一路按F8到下面那个CALL
  
00EA12D1    8BEC            MOV EBP,ESP
  
00EA12D3    53              PUSH EBX
  
00EA12D4    57              PUSH EDI
  
00EA12D5    56              PUSH ESI
  
00EA12D6    8B75 0C         MOV ESI,DWORD PTR SS:[EBP+C]
  
00EA12D9    8B5D 08         MOV EBX,DWORD PTR SS:[EBP+8]
  
00EA12DC    33C0            XOR EAX,EAX
  
00EA12DE    3946 10         CMP DWORD PTR DS:[ESI+10],EAX
  
00EA12E1    75 04           JNZ SHORT 00EA12E7
  00EA12E3    3906            
CMP DWORD PTR DS:[ESI],EAX
  
00EA12E5    74 24           JE SHORT 00EA130B
  00EA12E7    0306            
ADD EAX,DWORD PTR DS:[ESI]
  
00EA12E9    74 03           JE SHORT 00EA12EE
  00EA12EB    0343 08         
ADD EAX,DWORD PTR DS:[EBX+8]
  
00EA12EE    8B4E 0C         MOV ECX,DWORD PTR DS:[ESI+C]
  
00EA12F1    034B 08         ADD ECX,DWORD PTR DS:[EBX+8]
  
00EA12F4    8B7E 10         MOV EDI,DWORD PTR DS:[ESI+10]
  
00EA12F7    85FF            TEST EDI,EDI
  
00EA12F9    74 03           JE SHORT 00EA12FE
  00EA12FB    037B 08         
ADD EDI,DWORD PTR DS:[EBX+8]
  
00EA12FE    50              PUSH EAX
  
00EA12FF    57              PUSH EDI
  
00EA1300    51              PUSH ECX
  
00EA1301    53              PUSH EBX
  
00EA1302    E8 1F000000     CALL 00EA1326                                             
; 到这个CALL时按F7跟进去
  
00EA1307    40              INC EAX
  
00EA1308    75 08           JNZ SHORT 00EA1312
    跟进 00EA1302 处的 CALL:
  
引用:
  00EA1326    55              PUSH EBP                                                  ; 一路按F8
  
00EA1327    8BEC            MOV EBP,ESP
  
00EA1329    83C4 E8         ADD ESP,-18
  00EA132C    53              
PUSH EBX
  
省略码...
  
00EA1370    5B              POP EBX
  
00EA1371    C9              LEAVE
  
00EA1372    C2 1000         RETN 10
  00EA1375    90              
NOP
  
都是 NOP...
    直接按 Ctrl+S 搜索命令序列:
  
  MOV DWORD PTR [R32],R32
  MOV DWORD PTR [R32],R32
  
  会搜到这里:
  
引用:
  00EA13F4    85C0            TEST EAX,EAX
  
00EA13F6  0F84 6FFFFFFF   JE 00EA136B                                               
; 下面两句改掉,这样不会用得到的函数地址填充输入表,可以得到原始的输入表,只要填一下dump文件的输入表相关 RVA 及大小即可
  
00EA13FC    8906            MOV DWORD PTR DS:[ESI],EAX                                ; 搜到这里。关键,改成 mov eax,[edx]
  
00EA13FE    8902            MOV DWORD PTR DS:[EDX],EAX                                ; 改成 mov dword ptr [esi],eax
  
00EA1400    83C2 04         ADD EDX,4
  00EA1403    83C6 04         
ADD ESI,4
    三、OEP
  现在设 BP VirtualFree,F9 断下后删除断点,ALT+F9 返回,在一路 F8,会到这里:
  
引用:
  008C1034    8985 3F139400   MOV DWORD PTR SS:[EBP+94133F],EAX        ; 来到这里
  
008C103A    8BF0            MOV ESI,EAX
  
008C103C    8B4B 14         MOV ECX,DWORD PTR DS:[EBX+14]
  
008C103F    5A              POP EDX
  
008C1040    EB 0C           JMP SHORT 008C104E
  008C1042    03CA            
ADD ECX,EDX
  
008C1044    68 00800000     PUSH 8000
  008C1049    6A 00           
PUSH 0
  008C104B    57              
PUSH EDI
  
008C104C    FF11            CALL DWORD PTR DS:[ECX]
  
008C104E    8BC6            MOV EAX,ESI
  
008C1050    5A              POP EDX
  
008C1051    5E              POP ESI
  
008C1052    5F              POP EDI
  
008C1053    59              POP ECX
  
008C1054    5B              POP EBX
  
008C1055    5D              POP EBP
  
008C1056    FFE0            JMP EAX                                  
; 这里跳向 OEP
  在地址 008C1056 上按 F4,再按一下 F7,就到 OEP 了:
  
引用:
  005DC009 >  E8 07BF0000     CALL 005E7F15                            ; 这就是 OEP 了
  
005DC00E  E9 17FEFFFF     JMP 005DBE2A
  005DC013    6A 0C           
PUSH 0C
  005DC015    68 101A6F00     
PUSH 006F1A10
  005DC01A    
E8 817A0000     CALL 005E3AA0
  
  四、修正 dump 文件
  现在 dump,修正 OEP 和 IAT 的起始 RVA 和大小即可。OEP = 005DC009 - 00400000 = 001DC009。根据输入表的结构(5 个字段组成,最后以 20 个 0 字节结尾),我们用 16 进制工具如 010Editor 在 dump 后的文件第一个区段中搜索 20 个 0 字节,再结合输入表结构,能很快定位的 IAT 的起始 RVA 和 大小:
  
  31:1058h: 70 1A 31 00 00 00 00 00 00 00 00 00 32 1C 31 00  p.1.........2.1. 
  31:1068h: 88 18 28 00 D8 1A 31 00 00 00 00 00 00 00 00 00  ?(.?1......... 
  31:1078h: A0 1C 31 00 F0 18 28 00 80 1A 31 00 00 00 00 00  ?1.?(..1..... 
  31:1088h: 00 00 00 00 8A 1D 31 00 98 18 28 00 78 1B 31 00  ....?1.?(.x.1. 
  31:1098h: 00 00 00 00 00 00 00 00 96 1D 31 00 90 19 28 00  ........?1.?(. 
  31:10A8h: E4 1B 31 00 00 00 00 00 00 00 00 00 CA 1D 31 00  ?1.........?1. 
  31:10B8h: FC 19 28 00 50 13 31 00 00 00 00 00 00 00 00 00  ?(.P.1......... 
  31:10C8h: 3C 2C 31 00 68 11 28 00 3C 17 31 00 00 00 00 00  <,1.h.(.<.1..... 
  31:10D8h: 00 00 00 00 70 39 31 00 54 15 28 00 40 12 31 00  ....p91.T.(.@.1. 
  31:10E8h: 00 00 00 00 00 00 00 00 82 3D 31 00 58 10 28 00  ........?1.X.(. 
  31:10F8h: 38 12 31 00 00 00 00 00 00 00 00 00 9C 3D 31 00  8.1.........?1. 
  31:1108h: 50 10 28 00 C8 1A 31 00 00 00 00 00 00 00 00 00  P.(.?1......... 
  31:1118h: E0 3D 31 00 E0 18 28 00 E8 11 31 00 00 00 00 00  ?1.?(.?1..... 
  31:1128h: 00 00 00 00 B4 3E 31 00 00 10 28 00 E0 16 31 00  ....?1...(.?1. 
  31:1138h: 00 00 00 00 00 00 00 00 BC 3F 31 00 F8 14 28 00  ........?1.?(. 
  31:1148h: 28 12 31 00 00 00 00 00 00 00 00 00 06 40 31 00  (.1..........@1. 
  31:1158h: 40 10 28 00 18 17 31 00 00 00 00 00 00 00 00 00  @.(...1......... 
  31:1168h: AE 40 31 00 30 15 28 00 DC 1B 31 00 00 00 00 00  1.0.(.?1..... 
  31:1178h: 00 00 00 00 C8 40 31 00 F4 19 28 00 88 1B 31 00  ....1.?(.?1. 
  31:1188h: 00 00 00 00 00 00 00 00 7C 42 31 00 A0 19 28 00  ........|B1.?(. 
  31:1198h: 7C 16 31 00 00 00 00 00 00 00 00 00 86 42 31 00  |.1.........1. 
  31:11A8h: 94 14 28 00 48 13 31 00 00 00 00 00 00 00 00 00  ?(.H.1......... 
  31:11B8h: 52 43 31 00 60 11 28 00 6C 1B 31 00 00 00 00 00  RC1.`.(.l.1..... 
  31:11C8h: 00 00 00 00 86 43 31 00 84 19 28 00 00 00 00 00  ....1.?(..... 
  31:11D8h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................ 
  
  由此我们知道 IAT 的起始地址 RVA 是 00311058,大小是 190。
  
  写个脚本来完成以上工作,同时也兼容一下 DLL 的脱壳。用脚本跑完后查看一下记录窗口,先把整个表复制出来,再去掉无用的内容,剩下的就是这样:
  
Log data
地址       消息
           区段 RVA = 0038F000
           区段 RVA = 0038E000
           区段 RVA = 0038D000
           区段 RVA = 0038C000
           区段 RVA = 0038B000
           区段 RVA = 0038A000
           区段 RVA = 00389000
           区段 RVA = 00387000
           区段 RVA = 0037C000
           区段 RVA = 0036D000
           区段 RVA = 00315000
           区段 RVA = 00281000
           区段 RVA = 001000
           IAT 中某函数的 RVA 地址 = 00311C0A
           IAT 可能的结束 RVA 地址 = 003111D4
           OEP RVA = 001DC009

  脚本已经生成了 dump 文件,保存为 Unpacked.exe,并已修正了 OEP。其它的你就自己填了。
  
  附上脱壳脚本。用脚本脱壳后要对 dump 的文件进行修复,要涉及到一些 PE 知识。不了解的话,请自己找一下讲述 PE 格式的文档参考一下。脚本没经过多少测试,如果有问题,不要打我,呵呵
引用:
/*
Script written by CCDebuger
Script   
PECompact 2.x_No_ImportREC
版本     v0.1
日期     
15-2-2009
调试环境 OllyDbg 1.1ODBGScript 1.65WINXPWIN2000
调试
项 设置 OllyDbg 忽略所有异常项 
工具 
OllyDbgODBGScript 1.65
谢 Oleh Yuschuk author of OllyDbg
       SHaG 
author of OllyScript
       hnhuqiong 
author of ODbgScript
*/

var tmp1
var tmp2
var VirtualAlloc
var 
section
var iatloc
var VirtualFree
var imgbase
var signVA
var modsize
var dllreloc
var oep
var oeprva
var apiloc
var unpackname

msgyn 
"为保证脚本能正确运行,请忽略所有异常。本脚本只能用于 PECompact 2.x 版本加壳的 EXE 或 DLL"
cmp $RESULT0
je exit
cmp $VERSION
"1.65"
jb errorver
bc
bphwcall
dbh
GMI eip
MODULEBASE     //get imagebase
mov imgbase$RESULT
gmi eip
MODULESIZE
mov modsize,$RESULT
mov tmp1, [imgbase+3C]    //获取 PE 签名的偏移
add tmp1imgbase         //tmp1=签名 VA
mov signVAtmp1

//设 VirtualAlloc 断点,返回到相位置

gpa 
"VirtualAlloc""kernel32.dll"
mov VirtualAlloc$RESULT
bp VirtualAlloc

VirtualAlloc_Next
:
esto
rtu

/*
查找命令序列:
ADD EDI,EDX
MOV ECX,DWORD PTR DS:[EBX+8]
MOV 
EAX,ECX
*/
find eip#03FA8B4B088BC1#
mov section, $RESULT
cmp section, 0
je VirtualAlloc_Next
bp section
bc VirtualAlloc
lc
eob logsection
esto

logsection
:
log edi
"区段 RVA = "
rtr

bc 
section
cob
log 
"区段 RVA = 001000"   //最开始的那个段
sto

isdll
:
mov 
tmp1, [signVA 5E], 2  //DLL 特征值
cmp tmp11
je dll
mov unpackname
"Unpacked.exe"
jmp findIAT

dll
:
/*
查找命令序列:
MOV EAX,DWORD PTR DS:[EDI+4]
MOV 
EBX,DWORD PTR DS:[EDI+8]
CMP 
EAX,EBX
JE SHORT 00AB0C12
*/
find eip#8B47??8B5F??3BC374??#
mov dllreloc$RESULT
cmp dllreloc0
je findIAT
add dllreloc0D
//asm dllreloc
"cmp eax,eax"
bp dllreloc
esto
bc
log 
esi
"重定位表 RVA = "
mov [dllreloc 2], 0EB1
/*
查找命令序列:
JMP SHORT 00AB0BC9
POP ESI
POP EDI
POP EBX
LEAVE
RETN 
4
令序处理重定位表数的返回部分
*/
find eip#EB??5E5F5BC9C2????#
add $RESULT2
bp $RESULT
esto
bc
mov [dllreloc 2], 0741
//asm dllreloc
"cmp eax,ebx"
mov unpackname"Unpacked.dll"

findIAT:
/*
查找命令序列:
MOV DWORD PTR DS:[ESI],EAX
MOV DWORD PTR DS:[EDX],EAX
即处理输入表的部分。
*/
find eip#89068902#
mov iatloc$RESULT
cmp iatloc,0
je exit
asm iatloc
"mov eax,[edx]"
add iatloc$RESULT
asm iatloc
"mov dword ptr [esi],eax"
bp iatloc

nextapi
:
esto
esto
mov apiloc, [esi-4]
cmp 
apiloc800000
jae nextapi

log apiloc
"IAT 中某函数的 RVA 地址 = "
div apiloc01000
mul apiloc01000
add apilocimgbase
//据 IAT 表以 个字段组成,最后以 20 个 组成的特性来
//这个不大准的。IAT 结束地址只能做参考。以根这个参考,用16进制工具开 dump 后的文件,下搜搜看 20 个 ,应该在附近,不会
find apiloc
#0000000000000000000000000000000000000000#
mov apiloc$RESULT
add apiloc1
sub apilocimgbase
log apiloc
"IAT 可能的结束 RVA 地址 = "
bc iatloc
rtr
sto
find eip
#485E5F5BC9C2????#   //在返回地址设断
add $RESULT5
bp $RESULT
esto
bc
asm iatloc
"mov dword ptr [edx],eax"
sub iatloc$RESULT
asm iatloc
"mov dword ptr [esi],eax"
gpa "VirtualFree""kernel32.dll"
mov VirtualFree$RESULT
BP VirtualFree

nextoep
:
esto
rtu
rtr
sto
find eip
#5A5E5F595B5DFFE0#
mov oep$RESULT
cmp oep0
je nextoep
bc VirtualFree
add oep6
bp oep
esto
sti
bc
mov oeprvaeip
sub oeprvaimgbase
log oeprva
"OEP RVA = "
cmt eip"这就是 OEP 了"
mov [signVA 3C], 1000    //文件对设为1000
mov [signVA 54], 1000    //部大设为1000
mov tmp10
mov tmp1, [signVA 6], //区段数目
mov tmp2signVA
add tmp20F8         //第一个区段

last
:
cmp 
tmp10
je lab1
mov [tmp2 10], [tmp2 8]   //V.size 等于 R.size
mov [tmp2 +14], [tmp2 0C]   //拟偏移等于 RW 偏移
mov [tmp2 24], 0E00000E0   //设置区段属
add tmp228                 //指向一个区段 
sub tmp11
jmp last

lab1
:
mov [
signVA 28], oeprva    //填写 OEP
dm imgbase
modsizeunpackname
msg 
"已经到 OEP 了。程序已 dump 后另存为 Unpacked.exe 或 Unpacked.dll。OEP 已修正,现在请参考记录窗口中的数据,重建各个区段及修正 IAT RVA 的起始地址和大小。若是 DLL,请根据记录窗口中的重定位表 RVA 数据,再对照各个区段的 RVA,自己填写。重定位表的大小就是对应区段减掉区段末尾全是0的数据后的大小。重建区段后,必要时需重建资源。"

exit:
ret

errorver:
msg 
"运行此脚本需要 ODbgScript 1.65 或更高的版本,您的版本过低,请更新 ODbgScript 后再试。"
ret
--------------------------------------------------------------------------------
【版权声明】: 本文纯属技术交流, 转载请注明作者并保持文章的完整, 谢谢!