上篇文章《之五》说到了用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.库函数名没有加密,看它调用的编号很快就弄清了每个编号代表的库名。我的00074150——00074220数据部分就是为转换它设计的。
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校验)。否则不能在代码中设断点。
代码00EC58C3——00EC5CC4是它加密地址的全部代码,它每加密一次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]进入的只是一个副本,该段代码的“正本”完好地存在于00EE3704——00EE3C58地址段中。而且这段代码地址总是不变的(不同加密对象都是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太利害了!
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: