上篇文章《之五》说到了用ASProtect加密的“notepad.exe”,ASProcect对它只删除了INT、IID表,保留了(加密后的)IAT表,保留了API函数的导入地址。加密得还算比较“温柔”。今天要说到的加密的“eXeScope_asp.exe”程序则全部删除了INT、IID、IAT表,API导入地址也全都作了修改。脱壳时还要触及到ASProtect的一个代码“禁区”(不能跟踪,不能设断,更不能修改),要脱壳非同小可,但我都一一解决了!
  
  一、先来个完美脱壳,提高战胜困难的信心(原理后面分析)
  
  1.前期工作:(附件中的eXeScope_asp.exe,已作好)
  
  (1)在文件尾增加2000h个0字节,准备写入IID、INT、IAT表。将文件的装载尺寸(地址00000150:改为,117000),将最后两个节区表数据改为:
----------------------------------------------------------
节区名称  实际尺寸  内存地址  对齐尺寸  文件地址  节区属性
----------------------------------------------------------
   ………………………………………………………………
.data     0001B000  000FA000  0001B000  0005A000  E0000040
.adata    00002000  00115000  00002000  00075000  E0000040

  (2)在文件尾中添加脱壳代码:(附件中已完成,可打开查看)

  脱壳代码(全部):
0074050                                00 00 00 4F 51 00  
0074060  00 00 00 00 40 42 51 00 00 00 00 00 24 46 11 00  
0074070  00 00 8B 35 E8 FE 13 00 8B 3D 5C 40 51 00 89 3D  
0074080  60 40 51 00 AC 3C 00 74 03 AA EB F8 47 47 89 3D  
0074090  5C 40 51 00 C3 52 50 38 15 5B 40 51 00 88 15 5B  
00740A0  40 51 00 74 31 0F B6 D2 C1 E2 02 81 C2 00 42 51  
00740B0  00 A1 64 40 51 00 83 C0 10 8B 12 89 10 8B 15 6C  
00740C0  40 51 00 83 C2 04 83 C0 04 89 10 A3 64 40 51 00  
00740D0  89 15 6C 40 51 00 58 5A E8 7B 0C 9B 00 C3 8B 0D  
00740E0  6C 40 51 00 A1 60 40 51 00 48 48 35 00 00 40 00  
00740F0  81 F1 00 00 40 00 89 01 89 0E 83 C1 04 83 C6 08  
0074100  89 35 68 40 51 00 81 F1 00 00 40 00 89 0D 6C 40 
0074110  51 00 C3 66 C7 46 FE FF 25 E8 C0 FF FF FF E9 A0  
0074120  17 9B 00 B8 20 46 51 00 89 06 EB F2 56 57 E8 3F  
0074130  FF FF FF EB 0D 56 57 8B 35 D4 FE 13 00 E8 36 FF  
0074140  FF FF 8B 35 68 40 51 00 E8 91 FF FF FF 5F 5E EB  
0074150  CD 00 00 00 00 00 00 00 00 00 00 00 00 00  

  库函数名称(字串)表:
0074150               00 00   
0074160  47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00 00  GetProcAddress..
0074170  6B 65 72 6E 65 6C 33 32 2E 64 6C 6C 00 75 73 65  kernel32.dll.use
0074180  72 33 32 2E 64 6C 6C 00 61 64 76 61 70 69 33 32  r32.dll.advapi32
0074190  2E 64 6C 6C 00 6F 6C 65 61 75 74 33 32 2E 64 6C  .dll.oleaut32.dl
00741A0  6C 00 76 65 72 73 69 6F 6E 2E 64 6C 6C 00 67 64  l.version.dll.gd
00741B0  69 33 32 2E 64 6C 6C 00 6F 6C 65 33 32 2E 64 6C  i32.dll.ole32.dl
00741C0  6C 00 63 6F 6D 63 74 6C 33 32 2E 64 6C 6C 00 77  l.comctl32.dll.w
00741D0  69 6E 73 70 6F 6F 6C 2E 64 72 76 00 73 68 65 6C  inspool.drv.shel
00741E0  6C 33 32 2E 64 6C 6C 00 63 6F 6D 64 6C 67 33 32  l32.dll.comdlg32
00741F0  2E 64 6C 6C 00 77 69 6E 6D 6D 2E 64 6C 6C 00 00  .dll.winmm.dll..

  库编号转换(成地址)数据表:
0074200  70 41 11 00 7D 41 11 00 88 41 11 00 95 41 11 00  
0074210  A2 41 11 00 AE 41 11 00 B8 41 11 00 C2 41 11 00  
0074220  CF 41 11 00 DC 41 11 00 E8 41 11 00 F5 41 11 00  

  IID表初始位置及初值:
0074230  00 00 00 00 00 00 00 00 00 00 00 00 70 41 11 00  
0074240  20 46 11 00 00 00 00 00 00 00 00 00 00 00 00 00  

  IAT表初始位置及初值:
0074620  5E 41 11 00 00 00 00 00 ………………………………

  (3)将前面的脱壳代码(全部)反汇编出来(装载后地址:0007xxxx变成0051xxxx)功能如下:
  
  (I) 使用的寄存器:
  [0051405B]库函数编号(初值=0),.........[0051405C]INT表当前位置(初值=514F00),
  [00514060]API函数名地址(初值=0),......[00514064]IID表当前位置(初值=514240),
  [00514068]API导入地址(初值=0),........[0051406C]IAT表当前位置(初值=514624)。

  (II) 提取API函数名字串,新建INT表:
00514072     8B35 E8FE1300......mov esi,dword ptr ds:[13FEE8]...; 入口1
00514078     8B3D 5C405100......mov edi,dword ptr ds:[51405C]...; 入口2
0051407E     893D 60405100......mov dword ptr ds:[514060],edi
00514084     AC.................lods byte ptr ds:[esi]
00514085     3C 00..............cmp al,0
00514087     74 03..............je short 0051408C...............; 0051408C
00514089     AA.................stos byte ptr es:[edi]
0051408A     EB F8..............jmp short 00514084..............; 00514084
0051408C     47.................inc edi
0051408D     47.................inc edi
0051408E     893D 5C405100......mov dword ptr ds:[51405C],edi
00514094     C3.................retn

  (III) 转换库编号,新建IID表:
00514095     52.................push edx........................; 库处理入口
00514096     50.................push eax
00514097     3815 5B405100......cmp byte ptr ds:[51405B],dl.....; 查看库编号
0051409D     8815 5B405100......mov byte ptr ds:[51405B],dl
005140A3     74 31..............je short 005140D6...............; 新库不跳
005140A5     0FB6D2.............movzx edx,dl
005140A8     C1E2 02............shl edx,2
005140AB     81C2 00425100......add edx,514200..................; 库编号换成库名(地址)
005140B1     A1 64405100........mov eax,dword ptr ds:[514064]
005140B6     83C0 10............add eax,10
005140B9     8B12...............mov edx,dword ptr ds:[edx]
005140BB     8910...............mov dword ptr ds:[eax],edx......; 库名称存入IID表第4组值
005140BD     8B15 6C405100......mov edx,dword ptr ds:[51406C]
005140C3     83C2 04............add edx,4
005140C6     83C0 04............add eax,4
005140C9     8910...............mov dword ptr ds:[eax],edx......; IAT起址存入IID表第5组
005140CB     A3 64405100........mov dword ptr ds:[514064],eax
005140D0     8915 6C405100......mov dword ptr ds:[51406C],edx
005140D6     58.................pop eax
005140D7     5A.................pop edx
005140D8     E8 7B0C9B00........call 00EC4D58...................; 返回原程序
005140DD     C3.................retn

  (IV) 新建IAT表和修改API导入地址(esi=API调用地址):
005140DE     8B0D 6C405100......mov ecx,dword ptr ds:[51406C]...; 取IAT地址
005140E4     A1 60405100........mov eax,dword ptr ds:[514060]...; 取API名地址
005140E9     48.................dec eax
005140EA     48.................dec eax
005140EB     35 00004000........xor eax,400000
005140F0     81F1 00004000......xor ecx,400000..................; ecx=自建的解密地址
005140F6     8901...............mov dword ptr ds:[ecx],eax
005140F8     890E...............mov dword ptr ds:[esi],ecx......; [esi]=API导入地址
005140FA     83C1 04............add ecx,4
005140FD     83C6 08............add esi,8
00514100     8935 68405100......mov dword ptr ds:[514068],esi
00514106     81F1 00004000......xor ecx,400000
0051410C     890D 6C405100......mov dword ptr ds:[51406C],ecx
00514112     C3.................retn

  (V) 五个“解密”转跳入口(al=x是它的加密方式):
00514113     66:C746 FE FF25....mov word ptr ds:[esi-2],25FF....; al=2时,入口
00514119     E8 C0FFFFFF........call 005140DE...................; al=1时,入口
0051411E   - E9 A0179B00........jmp 00EC58C3....................; 返回原程序

00514123     B8 20465100........mov eax,514620..................; al=3时,入口
00514128     8906...............mov dword ptr ds:[esi],eax......; GetProcAddress名地址
0051412A   ^ EB F2..............jmp short 0051411E..............; 返回

0051412C     57.................push edi........................; al=4,[ebx+8]=0,入口
0051412D     56.................push esi
0051412E     E8 3FFFFFFF........call 00514072...................; 取API字串
00514133     5E.................pop esi
00514134     56.................push esi
00514135     EB 13..............jmp short 0051414A .............; 写导入地址

00514137     57.................push edi........................; al=4,[ebx+8]非0,入口
00514138     56.................push esi
00514139     8B35 D4FE1300......mov esi,dword ptr ds:[13FED4]
0051413F     E8 34FFFFFF........call 00514078...................; 取API字串
00514144     8B35 68405100......mov esi,dword ptr ds:[514068]
0051414A     E8 8FFFFFFF........call 005140DE...................; 写导入地址
0051414F     5E.................pop esi
00514150     5F.................pop edi
00514151   ^ EB CB..............jmp short 0051411E..............; 返回

  2.脱壳操作:(假定已获取了OEP=4CC7E8)
  
  (1)用OD打开eXeScope_asp.exe,一路狂按“shift+F9”来到第18个SEH:
       将00EC58A9:“31 00”代码改为“EB 00”(删除SEH),
       将00EC4AAA:“31 00”代码改为“EB 03”(删除SEH),
       将00EC4AED:“31 00”代码改为“EB 00”(删除SEH),
       将00EC4B08:“call EC19E8”改为“mov eax,0B9CFABCD”,目的是删除它的CRC校验。这里不能用nop……删除,这个eax值后面要参与运算(CRC校验值存放在00EE504中,始终不变)。
  
  (2)连接自编代码与原程序:

(地址)   (原代码)                                  (修改代码)
00EC590B  E8 48F4FFFF...call 00EC4D58...........;改为:call 00514095
……………………
00EC59B6  75 0A.........jnz short 00EC59C2......;改为:push esi
00EC59B8  68 D05CEC00...push 0EC5CD0............;......push edi                
00EC59BD  E8 F2E3FFFF...call 00EC3DB4...........;......call 00514072
................................................;......pop edi
................................................;......pop esi  
................................................;将多余的代码nop,直到00EC59C2  
……………………
00EC5ADB  75 0A.........jnz short 00EC5AE7......;改为:push esi
00EC5ADD  68 E05CEC00...push 0EC5CE0............;......push edi        
00EC5AE2  E8 CDE2FFFF...call 00EC3DB4...........;......call 00514072
................................................;......pop edi
................................................;......pop esi
................................................;将多余的代码nop,直到00EC5AE7
……………………
00EC5A4C  jmp 00EC58C3..........................;改为:jmp 00514113
......................
00EC5B11  jmp 00EC58C3..........................;改为:jmp 00514119
......................
00EC5B83  jmp 00EC58C3..........................;改为:jmp 00514137
......................
00EC5BF6  jmp 00EC58C3..........................;改为:jmp 0051412C
......................
00EC5C52  jmp 00EC58C3..........................;改为:jmp 00514123

  (3)反复核对修改无误后,按“F9”一次,“shift+F9”两次,来到00ECEA2E:在该处依次键入(即修改):
  
        (修改后的值)  
00ECEA2E  EB 0E...........jmp  00ECEA3E.。(删除SEH)...;单步2次,到00ECEA44,键入下列代码:
......................
00ECEA44  81C4 84000000...add  esp,84.................;弹出多余的堆栈
00ECEA4A  68 E8C74C99.....push 004CC7E8...............;即OEP的值
00ECEA4F  C3..............retn........................;转跳到了脱壳后的程序
  
  单步走完这三行,然后dump,去掉重建输入表的“勾”,存盘eXeScope_new.exe,再将PE头的导入表地址:
00000180  改为:114230,后面的长度值改为:250,一切OK。

  怎么样,是不是完美脱壳?要知道,“ASProtect”号称为当今最利害的加密壳!
  脱壳代码总共不到70行,为什么能破解这样利害的加密壳,要知道,“尺有所短,寸有所长”。ASProtect设置了那么多的陷阱,一是我根本不去,二是拔除必要的障碍,三是闯关技巧,四是利用程序自身的不完善。若你有兴趣请接着往下看。

  二、找寻OEP入口的技巧  
  
  用ASProtect加密的程序,OEP藏得很深,且每次运行时,它出现的地址是不固定的。若你在程序中设了断点,或者作了修改,肯定到达不了终点,因此,把每一次运行OD目标设得简单点。现在为了寻找OEP,其它什么都不做。
  
  1.跳过时间限制:
  打开OD运行,狂按“shift+F9”。当到达00ED0B0C(第28个SEH)时停下,这里有个“时间使用限制”过不了。它的关键代码是:
  00ED0C63:  85 DB  test ebx,ebx  ;(使用过期,则ebx非0)
  若爆破该点,而下一个SEH有自校验,肯定出错。只好如下修改:
  到达00ED0B0C后,在00ED0C65设断点,按“shift+F9”停在00ED0C63,将它改为:
  00ED0C63:  33 DB  xor ebx,ebx  ;(强行通过)
  单步几次,跳过出错的“call 00EC60A0”后,再将00ED0C63:  85 DB  test ebx,ebx还原并清除断点。(一定要清除断点)
  
  2.OEP代码特征:
  再按几次“shift+F9”,到达00ECF145后,它是最后一个SEH。将它改为“EB 08”(删除SEH),跟踪时避免进入系统代码。然后单步跟踪(千万不要用F8,否则明明进入一个call,程序就玩完了),当程序在一个循环中久久不能跳出时,可在适当位置设断点,按F9跳出。注意观察地址栏,当地址出现00EExxxx时要小心了,OEP不远了,最后要到OEP时,下面这段代码是参照点(基本不变化):
………………………………………………
00EE7B78     59.................pop ecx
00EE7B79     66:8BCE............mov cx,si
00EE7B7C     5A.................pop edx
00EE7B7D     81FF D0FFFFFF......cmp edi,-30
00EE7B83   ^ 0F85 4DFFFFFF......jnz 00EE7AD6....;反复转跳点
00EE7B89     66:8BCB............mov cx,bx
…………………………………………………
  程序会在00EE7B83 反复回跳(00EE7B83地址每次不同,但00EExxxx不变)。OEP就在它下面几行,每跳回一次,后面相邻的代码就变化一次,把00EE7BAA,00EE7BAB的花指令去除后,下面形式基本不变。可试着在00EExxxx内存段搜索“8D 04 18 5C FF E0”,若搜索成功或看见了下面代码(除掉花指令才能看见),则在jmp eax设断点,eax中就是OEP,得到了。

00EE7BA8     EB 02..............jmp short 00EE7BAC
00EE7BAA     90.................nop..............................;去除了花指令
00EE7BAB     90.................nop
00EE7BAC     xx xx..............sub eax,ebp......................;它是变化的
00EE7BAE     8D0418.............lea eax,dword ptr ds:[eax+ebx]...;下面三行不变
00EE7BB1     5C.................pop esp
00EE7BB2     FFE0...............jmp eax  

  三、确定脱壳必需的几项任务

  在初步试跟踪eXeScope_asp.exe中发现,当在第11个SEH后,虽然原程序被解压还原,但代码中没有INT、IID、IAT表,且API函数名字串也是在堆栈中临时解密及时删除的,只有10来个库函数名是完整的。但调用它是通过编号来实现的,因此脱壳任务必需重建INT、IID、IAT表,而且API函数导入地址:“jmp [00xxxxxx]”被大量改为“call 00xxxxxxx”形式,要找到原[00xxxxxx]地址是根本不可能的,一切都得重建。我的脱壳程序就是按这个需要分为四个部分的。为了重建,必需知道:库函数名、全部API函数名、API导入地址。
  1.库函数名没有加密,看它调用的编号很快就弄清了每个编号代表的库名。我的0007415000074220数据部分就是为转换它设计的。

  2.全部的API名都被加密了,直到调用时才解密出来,昙花一现,有的很难跟踪到。但ASProtect犯了一个致命的错误:它将API字串在堆栈中解密,用后立即弹出。注意,“弹出”并不等于删除!只要该堆栈地址没有被冲掉,API字串还是可以被找回的,我的程序中,半数以上的API字串都是在废弃的堆栈中找回的!就连ASProtect的一个“禁区”(“00EC5B80:call [ebx+30]”是不可跟踪的),也是在该点返回后,在弹出的堆栈“13FED4”中找到它调用的API函数名字串的。  

  3.查找几种加密方式:当第18次SEH后,到达00EC58A9。改00EC58A9的“31 00”为“EB 00”(删除SEH)、00EC4AAA的“31 00”改为“EB 03”(删除SEH),00EC4AED:“31 00”改为“EB 00”(删除SEH),00EC4B08的“call EC19E8”改为“mov eax,0B9CFABCD”(去除CRC校验)。否则不能在代码中设断点。
  代码00EC58C300EC5CC4是它加密地址的全部代码,它每加密一次API,就用jmp 00EC58C3返回一次,大约共有10多个。我在全部的jmp 00EC58C3处设断(需要将花指令改为nop后才能找完),运行OD,每中断一次,就清除该处断点,直到进入下个SEH(00ECE93F)。凡剩下的断点就是没用的加密方式,最后确认有五种加密方式,这就是我程序中对应设置了五种解密“转跳入口”的原因。
  
  4.只要找到了需要的原料(API字串,库字串,API导入地址),剩下的就是如何“加工”了。其实大量的精力都放在了寻找需要的原料上了。设计程序要相对简单些,为了尽量减少对原程序的修改,不得不把自己的程序分成了多个小块,甚至一段代码有多个入口!多个入口是编程的大忌,为了减少添加代码这也是不得已。

  5.完全不必了解原程序是怎样加密API地址的,只需要知道API的导入地址在那里,而个别只调用1至2次的加密方式,如“00EC5C52:jmp 00EC58C3”的返回点前的调用,怎么也没有找到API字串,跟踪它前面的“call xxxxxxx”居然跑到了“GetProcAddress”程序中,原来它就是调用GetProcAddress,类似的还有“LoadLibrary”等。我的al=3的入口就是专门为它准备的。

  四、对脱壳程序00514137入口的说明

  “解密”入口“00514137”是针对“ASProtect禁区00EC5B80:call [ebx+30]”设计的。其实“call [ebx+30]”已我被攻克,它加密的API字串、API导入地址一旦返回就销毁,如果要在程序中将它取出,就必需摘除它的“反anti”代码。而这样工作量较大,好在废弃的堆栈中留下了它调用API字串的痕迹,而API的导入地址始终没有蛛丝马迹。经跟踪发现,call [ebx+30]中使用的“API导入地址”总是跟在前一次调用后的相距8个字节的地址中。这有些冒险,但换来的是大大简化了解密程序。也不必去攻克“call [ebx+30]”了。
…………………………………………………………………………
00514137     57.................push edi........................; al=4,[ebx+8]非0,入口
00514138     56.................push esi
00514139     8B35 D4FE1300......mov esi,dword ptr ds:[13FED4]
0051413F     E8 34FFFFFF........call 00514078 ..................; 取API字串
00514144     8B35 68405100......mov esi,dword ptr ds:[514068]...; esi是上次导入地址值+8
0051414A     E8 8FFFFFFF........call 005140DE...................; 写导入地址
0051414F     5E.................pop esi
00514150     5F.................pop edi
00514151   ^ EB CB..............jmp short 0051411E 

  (附录):
  五、攻克“00EC5B80:call [ebx+30]”禁区
  
  我把“call [ebx+30]”称为“ASProtect禁区”是因为它有不可跟踪性。表现如下:
  1.该段代码特征:
  (1)每次运行跟进后的地址都不同;
  (2)它的花指令几乎每两行代码中就有一个;
  (3)每调用该段代码一次,就自校验本段代码一次,因为设定校验段长度每次不同,校验值也每次不同;
  (4)校验值要参与API导入地址的运算(?),不能nop,也不能修改;
  (5)若设断点、修改一个字节、或者单步跟踪都会出错。
  不信,你就试试。它的“校验”代码如下:(去除了花指令,地址每次运行后都不同)
……………………………………
00EEACFD     90.................nop.....................................;花指令去除
00EEACFE     90.................nop
00EEACFF     2BDE...............sub ebx,esi.............................;esi总是从call入口开始
00EEAD01     8D5C0E 6C..........lea ebx,dword ptr ds:[esi+ecx+6C]
00EEAD05     2BD9...............sub ebx,ecx.............................;这3行相当于
00EEAD07     83EB 6C............sub ebx,6C..............................;mov ebx,esi
00EEAD0A     0371 1C............add esi,dword ptr ds:[ecx+1C]...........;每次运行,
00EEAD0D     2B71 0C............sub esi,dword ptr ds:[ecx+C]............;终点esi值不同
00EEAD10     2B2B...............sub ebp,dword ptr ds:[ebx]..............;ebp保存了校验值
00EEAD12     64:EB 02...........jmp short 00EEAD17
00EEAD15     90.................nop.....................................;花指令去除
00EEAD16     90.................nop
00EEAD17     8D5C0B 01..........lea ebx,dword ptr ds:[ebx+ecx+1]........;这两行相当于
00EEAD1B     2BD9...............sub ebx,ecx.............................;ebx+1
00EEAD1D     3BDE...............cmp ebx,esi
00EEAD1F   ^ 0F82 EBFFFFFF......jb 00EEAD10
00EEAD25     8DB411 30104100....lea esi,dword ptr ds:[ecx+edx+411030]
00EEAD2C     2BF2...............sub esi,edx
00EEAD2E     5B.................pop ebx
……………………………………
  校验方法是将每个字节的“数值”与前面的“结果”累减,因此只要一个字节值改变结果就不同,而“总结果”还要用于对API导入地址的计算。因导入地址每次不同,参与校验的字节段长度每次也不同,“总结果”就不是固定值。这样,ebp中的结果是删也删不得,改也改不得。可以想见,你能跟踪吗?在本段代码中,有一些看似“垃圾”的代码,其实它是为平衡ebp结果设置的,个字都不能改:
……………………………………
00EEA568     51.................push ecx
00EEA569     EB 01..............jmp short 00EEA56C
00EEA56B     90.................nop.............................;花指令去除
00EEA56C     B9 C6434700........mov ecx,4743C6
00EEA571     B9 7EAC4800........mov ecx,48AC7E
00EEA576     B9 6EF74400........mov ecx,44F76E
00EEA57B     C1C9 D3............ror ecx,0D3.....................;以上对ecx的操作看似垃圾
00EEA57E     8D4C24 0B..........lea ecx,dword ptr ss:[esp+B]
00EEA582     83E9 0B............sub ecx,0B
00EEA585     3E:EB 02...........jmp short 00EEA58A
00EEA588     90.................nop.............................;花指令去除
00EEA589     90.................nop
00EEA58A     83EC 10............sub esp,10
00EEA58D     55.................push ebp
00EEA58E     56.................push esi
00EEA58F     53.................push ebx
00EEA590     0BED...............or ebp,ebp
00EEA592     23ED...............and ebp,ebp
00EEA594     BD 666D4000........mov ebp,406D66
00EEA599     EB 01..............jmp short 00EEA59C
00EEA59B     90.................nop
00EEA59C     C1D5 95............rcl ebp,95
00EEA59F     8DAC11 00000000....lea ebp,dword ptr ds:[ecx+edx]
00EEA5A6     2BEA...............sub ebp,edx.....................;前面对ebp的操作也看似垃圾
00EEA5A8     2BE9...............sub ebp,ecx.....................;相当于mov ebp,0
00EEA5AA     E8 A2040000........call 00EEAA51

  2.破解思路:
  正当我一筹莫展准备放弃对“ASProtect”破解时,我突然想到:软件作者是如何调试的?难道有一气呵成不需要调试的软件设计?一定有“调试遂道”!为了找到这个“调试遂道”,我构思为:复制本段代码为一个“替身”,当我在“痛改”这段代码时,而让程序不厌其烦去一次一次去扫描其“替身”。原来软件也是很好“欺骗”的。

  3.“移花接木”术,攻克ASProtect禁区:
  正当我在程序中寻找空字段时,意外地发现call [ebx+30]进入的只是一个副本,该段代码的“正本”完好地存在于00EE370400EE3C58地址段中。而且这段代码地址总是不变的(不同加密对象都是xxxx3704开始),而“副本”地址是临时由内存00EE0500指定的。这真是“踏破铁鞋无觅处,得来全不费功夫”。这不正是我苦苦搜寻的“调试遂道”吗?
  按前面提到的“脱壳操作”中处理第18个SEH的方法,删除连续的3个SEH,和一个CRC校验。在00EC5B80设断点,按F9到达。这时00EE0500已准备好了“副本”地址,记下该地址(准备修改正本,因为它的地址是不变的,将副本作为“替身”),并将00EE0500内存值改为“04 37 EE 00”,让call [ebx+30]进入“正本”。这时跟进call [ebx+30],来到“累差校验”地址(已除掉花指令):
………………………………………………………………
00EE3C06    F2:...................prefix repne:
00EE3C07    BB 62C94100...........mov ebx,41C962
00EE3C0C    83CB E9...............or ebx,FFFFFFE9
00EE3C0F    C1DB E5...............rcr ebx,0E5
00EE3C12    F3:...................prefix rep:
00EE3C13    EB 02.................jmp short 00EE3C17
00EE3C15    90....................nop
00EE3C16    90....................nop
00EE3C17    2BDE..................sub ebx,esi.......................;需要修改
00EE3C19    8D5C0E 6C.............lea ebx,dword ptr ds:[esi+ecx+6C].;需要修改
00EE3C1D    2BD9..................sub ebx,ecx.......................;需要修改
00EE3C1F    83Eb 6C...............sub ebx,6C........................;需要修改
00EE3C22    0371 1C...............add esi,dword ptr ds:[ecx+1C]
00EE3C25    2B71 0C...............sub esi,dword ptr ds:[ecx+C]
00EE3C28    2B2B..................sub ebp,dword ptr ds:[ebx]
00EE3C2A    64:EB 02..............jmp short 00EE3C2F
00EE3C2D    90....................nop
00EE3C2E    90....................nop
00EE3C2F    8D5C0B 01.............lea ebx,dword ptr ds:[ebx+ecx+1]
00EE3C33    2BD9..................sub ebx,ecx
00EE3C35    3BDE..................cmp ebx,esi
00EE3C37  ^ 0F82 EBFFFFFF.........jb 00EE3C28
00EE3C3D    8DB411 30104100.......lea esi,dword ptr ds:[ecx+edx+411030]

  将“需要修改”的位置,稍加修改如下:
……………………………………………………
00EE3C17    BB xxxxxxxx...........mov ebx,xxxxxxx........;xxxxxxxx由00EE0500中值决定
00EE3C1C    8BF3..................mov esi,ebx
00EE3C1E    90....................nop
00EE3C1F    90....................nop
00EE3C20    90....................nop
00EE3C21    90....................nop

  当这项工作完成后,你就放心在本段代码中“放开手脚”痛改吧!跟踪、设断随便,回头看看00EE3C17,每调用call [ebx+30]一次,它就“傻乎乎”扫描一遍“替身”,还报平安。真痛快!
  当我弄清了它是如何恢复出API字串,何时出现API函数导入地址后,我的“脱壳代码”构思已经完成,为了简化脱壳程序,最后竟然根本不用改动call [ebx+30]中的代码。但不能保证ASProtect加密其它程序时,call [ebx+30]中的API函数导入地址一定是“前一次地址加8个字节”。例如,当API函数导入地址不是连续出现时,一定出错。

  感谢你耐心地读完本文!太长了,谁叫ASProtect太利害了!