-
-
[旧帖] [转帖]Asprotect V2.X 的脱壳与修复的总结及练习 0.00雪花
-
发表于: 2013-2-15 09:06 1622
-
关于Asprotect V2.X 的脱壳与修复, loveboom的《ASPROTECT 2.x 脱壳系列》已经非常的全面与经典.
本人在此只是依葫芦画瓢, 并将有些地方再详细的解释一下, 给菜鸟们脱Asprotect V2.X时进行参考, 高手就不要看了.
先在理论上研讨一下, 由于编译器的不一样, C 与 Delphi 所编译的汇编结果有差别, Asprotect 加壳时处理的也不一样.
先假设有一程序:
OEP: 00401000
IAT: 00407000 - 00407FFF
在00401100 CALL DLL1.API1
在00401180 CALL DLL1.API2
Ospr 加壳后, 好多的API CALL 被改成 CALL 12000000
然后开始研究.
1. C/C++ Program
1.1) 未被加壳的程序
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 FF15 00704000 CALL DWORD PTR DS:[00407000] ; DLL1.API1
00401106 ...
00401180 FF15 04704000 CALL DWORD PTR DS:[00407004] ; DLL1.API2
00401186 ...
********* IAT 可能是这样的 ************
00407000 DD 7C571000 // DLL1.API1
00407004 DD 7C572000 // DLL1.API2
...
004070FC DD 00000000
00407100 DD 7D00XXXX // DLL2.API1
...
004071FC DD 00000000
...
00407F00 DD 7F00XXXX // DLLn.API1
...
00407FFC DD 00000000
1.2) 被 Asprotect 加壳后的程序在OEP
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 FBEEBF11 CALL 12000000 ; 壳
00401105 ??
00401106 ...
00401180 E8 7BEEBF11 CALL 12000000 ; 壳
00401185 ??
00401186 ...
*************IAT************************
00407000 DD ???????? // 被加密的 DLL1.API1 信息
00407004 DD 7C572000 // 未加密的 DLL1.API2
ASPR 将许多的API CALL 都改成了统一的 CALL 12000000
即改 CALL DWORD PTR DS:[xxxxx] 成 CALL 12000000
1.3) 加壳后的程序运行过API CALL 后
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 00EFBF12 CALL 13000005 ; 壳
00401105 ??
00401106 ... ; 假设运行到这
00401180 E8 7BEEBF11 CALL 12000000 ; 壳
00401185 ??
00401186 ...
即 每经过一个CALL12000000 后, 这个 CALL12000000 就会被改成 CALL13000005 或者 其它的 CALL 14000005, 反正每个API 还不一样, 分别
对应一段壳的CODE, 等于相对应的API CODE
1.4) 脱壳与修复需要解决以下几个问题
A.) 找到每一个 CALL 12000000 所在的地址, 例如00401100, 00401180, ...
B.) 找到每一个 CALL 12000000 实际应该CALL到的 API 地址, 例如 7C571000, 7C572000, ...
C.) 如果IAT的地址未加密, 修复 CALL 12000000 成 CALL DWORD PTR DS:[0040xxxx]
例如 00401180 E8 7BEEBF11 CALL 12000000 这句, 根据 API 地址 7C572000 可在 IAT 中找到, 位于00407004
修复成 00401180 FF15 04704000 CALL DWORD PTR DS:[00407004]
注意: CALL 12000000 是 5 个字节, 而 CALL DWORD PTR DS:[xxxxxxxx] 是 6 个字节
D.) 如果IAT的地址被加密, 可在IAT强行加上一项, 再修复 CALL 12000000 成 CALL DWORD PTR DS:[0040xxxx]
例如 00401100 E8 FBEEBF11 CALL 12000000 这句, API 地址 7C571000 在 IAT 中找不到,
可在IAT最后加上一项, 最后的IAT可能会变成这样:
...
00407F00 DD 7D00XXXX // DLLn.API1
...
00407FFC DD 00000000 // 原来的 IAT 到这结束
00408000 DD 7C571000 // 新加的 IAT 项
00408004 DD 7C573000 // 新加的 IAT 项
...
00408080 DD 00000000 // 每个DLL的IAT必须以 00000000 结束, 不能与其它DLL 的IAT连成一片
00408084 DD 7D00xxxx // 新加的 IAT 项, 另外的DLL的
...
........ DD 00000000 // 修复的 IAT 到这结束
再把00401180 修复成 00401100 FF15 04704000 CALL DWORD PTR DS:[00408004]
注意: 这里已经与未加壳的程序有一点不一样了, 即 API 地址在 IAT 的位置不一样, 但功能是一样的,
1.5) 在到达OEP之前, 壳把程序中的API CALL 改成 CALL 12000000 的地方的CODE 是这样的:
012C5F7A 45 INC EBP ; EBP 就是 CALL 12000000 处的地址, Pactch1
012C5F7B 8945 00 MOV DWORD PTR SS:[EBP],EAX
012C5F7E 6A 0A PUSH 0A
012C5F80 E8 8FCEFEFF CALL 012B2E14
012C5F85 8BC8 MOV ECX,EAX
012C5F87 038B E4000000 ADD ECX,DWORD PTR DS:[EBX+E4]
012C5F8D 8BD6 MOV EDX,ESI
012C5F8F 8BC3 MOV EAX,EBX
012C5F91 E8 9EE5FFFF CALL 012C4534
012C5F96 FF0C24 DEC DWORD PTR SS:[ESP]
012C5F99 03B3 E4000000 ADD ESI,DWORD PTR DS:[EBX+E4]
012C5F9F 833C24 00 CMP DWORD PTR SS:[ESP],0
012C5FA3 ^ 0F87 55FEFFFF JA 012C5DFE ; 循环
012C5FA9 53 PUSH EBX ; 到这, 所有的 API CALL 改 CALL 12000000 都完成了
所以 在 012C5F7A 处, 要先到我们的修复 CODE1, 把 EBP 保存下来, 然后再回来
012C5F7A E8 xxxxxxxx JMP 1540100 ;
012C5F7F 90 NOP
到 012C5FA9 后, 把 012C5F7A 处的CODE 还原, 再运行到 OEP
找这段CODE, 可在壳解压缩自身后, SEARCH
inc ebp
move [ebp], eax
push 0A
1.6) 到达OEP之后, 如果跟踪 CALL 12000000 到壳中, 壳会根据CALL是从哪儿来的 解码 API 地址,
API 地址 出现并且不会被壳检测的地方的CODE是这样的:
012A2544 85C0 TEST EAX,EAX
012A2546 74 0A JE SHORT 012A2552
012A2548 FF15 18902C01 CALL DWORD PTR DS:[12C9018] // 到这儿时, EDX=API 地址, Patch2
012A254E 09C0 OR EAX,EAX
012A2550 74 01 JE SHORT 012A2553
012A2552 C3 RETN
然后壳把CALL 12000000 改成 相应的CALL 13000005
最后壳再跳到新改成的 CALL 13000005 处继续执行
015100A1 9D POPFD
015100A2 5C POP ESP
015100A3 FF6424 FC JMP DWORD PTR SS:[ESP-4] // 这儿跳到00401100 处的 CALL 13000005
015100A7 F2: PREFIX REPNE: ; Superfluous prefix
015100A8 EB 01 JMP SHORT 015100AB
015100AA - E9 94CA2E01 JMP 027FCB43
015100AF C3 RETN
在 OEP 处修复时, 就是用修复 CODE2 模拟的运行 每一处的 CALL 12000000, 取得 每一处的对应的API 地址,
然后在 JMP DWORD PTR SS:[ESP-4] 处截住, 这时当然还不能让它跑去执行真正的API CODE
根据CALL 12000000 所在的地址与得到的 API 地址就可修复 IAT
然后再模拟运行下一个 CALL 12000000
所以 在 012A2548 处, 要先到我们的修复 CODE3, 把 EDX 保存下来, 然后再回来
012A2548 FF15 xxxxxxxx CALL DWORD PTR DS:[01540050]
// 01540050 中是CODE3 的起始地址 015401F0
在 015100A3 处, 不能让它自由的跳, 要让它回到 修复 CODE2 去 修复 IAT
loveboom 说这儿的 CODE 不能改, 壳会检测出来, 我没试过. 反正可在这儿设硬件断点, 再靠script 强行跳到 CODE2 中.
只是每次运行时 JMP DWORD PTR SS:[ESP-4] 这一句所在的地址还都不一样, 真的挺烦人的
找这段CODE, 可 在OD 中 用 Ctrl+T 设 Run Trace 的暂停条件 command is POPFD, 再 Ctrl+F11,
找到后去掉暂停条件,并设硬件断点, 否则 Script 运行会出错的.
2. 再讨论 Delph 程序
2.1) 未被加壳的程序
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 FB4E0000 CALL 00406000 ; DLL1.API1
00401105 ...
00401180 E8 7F4E0000 CALL 00406004 ; DLL1.API2
00401185 ...
00406000 FF25 00704000 JMP DWORD PTR DS:[00407000] ; DLL1.API1
00406006 FF25 04704000 JMP DWORD PTR DS:[00407004] ; DLL1.API2
*************IAT************************
IAT 与 C/C++ 程序一样
2.2) 被 Asprotect 加壳后的程序在OEP
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 FB4E0000 CALL 00406000 ; DLL1.API1
00401105 ...
00401180 E8 7F4E0000 CALL 00406004 ; DLL1.API2
00401185 ...
00406000 E8 FB9FBF11 CALL 12000000 ; 壳
00406005 ??
00406006 E8 7B9FBF11 CALL 12000000 ; 壳
0040600B ??
注意: ASPR 修改的地方与C/C++程序是不一样的, 它是改 JMP DWORD PTR DS:[xxxxx] 成 CALL 12000000
2.3) 加壳后的程序运行过API CALL 后
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 FB4E0000 CALL 00406000 ; DLL1.API1
00401105 ...
00401180 E8 7F4E0000 CALL 00406004 ; DLL1.API2
00401185 ...
00406000 E8 00A0BF12 CALL 13000005 ; 壳
00406005 ??
00406006 E8 7B9FBF11 CALL 12000000 ; 壳
0040600B ??
同样是把CALL 12000000 改成 CALL 13000005
2.4) 修复与C程序类似, 只是要把
CALL 12000000
修复成
FF25 xxxxxxxx JMP DWORD PTR DS:[xxxxxxxx]
3.0 理论上研讨完了, 再说实际修复
3.1) 与位置无关的修复CODE
需要OLLYDBG 的plugin: Memory Manage, 分配 一块内存
01540000 DD 012C5F7A ; // Patch1 Address, 供 第 1 段CODE 用
01540010 DD 012A2548 ; // Patch2 Address, 供 第 2 段CODE 用
01540020 DD 0077A180 ; // IAT Start Address, 供 第 2 段CODE 用
01540024 DD 00001408 ; // Original IAT Size, 供 第 2 段CODE 用
01540040 DD ?? ; // Patch1 后的 Address
01540050 DD ?? ; // 第 3 段CODE 的 Address
01540054 DD ?? ; // CurrAPIAddress
01540058 DD ?? ; // CurrDLLBase
01540060 DD ?? ; // IAT Start Address
01540064 DD ?? ; // Current IAT Size
01540080 DD ?? ; // 指向 CALL Address Struct
01540084 DD ?? ; // 指向 API Address Struct
// 01540100 - 01540165 为 第 1 段CODE, 保存 每一个 CALL 的地址 到我们的表中, 即EBP
// 需要 01540000 处为 Patch1 Address
01540100 60 PUSHAD
01540101 55 PUSH EBP ; EBP 中为CALL 12000000 处的地址
01540102 90 NOP
01540103 E8 00000000 CALL 01540108
01540108 5D POP EBP
01540109 8D55 5A LEA EDX,DWORD PTR SS:[EBP+5A] ; EDX = 01540162
0154010C 81E5 0000FFFF AND EBP,FFFF0000 ; EBP = 01540000, 我们的基址
01540112 90 NOP
01540113 83BD 80000000 0>CMP DWORD PTR SS:[EBP+80],0
0154011A 75 1B JNZ SHORT 01540137
0154011C 8B45 00 MOV EAX,DWORD PTR SS:[EBP] ; Patch1 Address
0154011F 83C0 05 ADD EAX,5
01540122 8945 40 MOV DWORD PTR SS:[EBP+40],EAX ; Patch1 后的 返回Address
01540125 8D45 40 LEA EAX,DWORD PTR SS:[EBP+40]
01540128 8902 MOV DWORD PTR DS:[EDX],EAX ; 自动修正 01540160 中的 JMP DWORD PTR: [xxxx]
0154012A 8D95 00030000 LEA EDX,DWORD PTR SS:[EBP+300] ; CALL Address Struct @ 基址+300, 够了吧!
01540130 8995 80000000 MOV DWORD PTR SS:[EBP+80],EDX ; 保存
01540136 90 NOP
01540137 8B8D 80000000 MOV ECX,DWORD PTR SS:[EBP+80]
0154013D 8379 04 00 CMP DWORD PTR DS:[ECX+4],0 ; CurrCallAddr
01540141 75 09 JNZ SHORT 0154014C
01540143 8D51 10 LEA EDX,DWORD PTR DS:[ECX+10]
01540146 8911 MOV DWORD PTR DS:[ECX],EDX
01540148 8951 04 MOV DWORD PTR DS:[ECX+4],EDX
0154014B 90 NOP
0154014C 8B79 04 MOV EDI,DWORD PTR DS:[ECX+4]
0154014F 90 NOP
01540150 5D POP EBP ; EBP 中为CALL 12000000 处的地址
01540151 892F MOV DWORD PTR DS:[EDI],EBP ; 保存 CALL 地址 到我们的表中
01540153 83C7 04 ADD EDI,4
01540156 8979 04 MOV DWORD PTR DS:[ECX+4],EDI ; CurrCallAddr 指向表中的下一处
01540159 61 POPAD
0154015A 45 INC EBP ; 执行壳原来的代码
0154015B 8945 00 MOV DWORD PTR SS:[EBP],EAX ; 执行壳原来的代码
0154015E 6A 0A PUSH 0A ; 执行壳原来的代码
01540160 FF25 40005401 JMP DWORD PTR DS:[1540040] ; 回到壳中去
01540166 90 NOP
01540167 90 NOP
01540168 90 NOP
01540169 90 NOP
0154016A 90 NOP
0154016B 90 NOP
0154016C 90 NOP
0154016D 90 NOP
0154016E 90 NOP
0154016F 90 NOP
01540170 90 NOP
01540171 90 NOP
01540172 90 NOP
01540173 90 NOP
01540174 90 NOP
01540175 90 NOP
01540176 90 NOP
01540177 90 NOP
01540178 90 NOP
01540179 90 NOP
0154017A 90 NOP
0154017B 90 NOP
0154017C 90 NOP
0154017D 90 NOP
0154017E 90 NOP
0154017F 90 NOP
// 01540180 - 015402BF 为 第 2 段CODE, 修复每一个CALL, 及IAT
// 需要 01540010 处为 Patch2 Address, 01540020 处为IAT Address & Size
01540180 60 PUSHAD
01540181 E8 00000000 CALL 01540186
01540186 5D POP EBP
01540187 8D55 7D LEA EDX,DWORD PTR SS:[EBP+7D] ; EDX = 01540203
0154018A 90 NOP
0154018B 90 NOP
0154018C 90 NOP
0154018D 81E5 0000FFFF AND EBP,FFFF0000 ; EBP = 01540000, 我们的基址
01540193 90 NOP
01540194 8B5D 10 MOV EBX,DWORD PTR SS:[EBP+10] ; Patch2 Address
01540197 8B43 02 MOV EAX,DWORD PTR DS:[EBX+2] ; 取 012A2548处 CALL [12C9018] 中的 12C9018
0154019A 8902 MOV DWORD PTR DS:[EDX],EAX ; 改 01540201处 为 CALL DWORD PTR DS:[12C9018]
0154019C 8D42 ED LEA EAX,DWORD PTR DS:[EDX-13] ; 第 3 段 CODE 的 Address
0154019F 8945 50 MOV DWORD PTR SS:[EBP+50],EAX
015401A2 8D45 50 LEA EAX,DWORD PTR SS:[EBP+50]
015401A5 8943 02 MOV DWORD PTR DS:[EBX+2],EAX ; 改 Patch2 处 为 CALL DWORD PTR DS:[1540050]
015401A8 90 NOP
015401A9 8BB5 80000000 MOV ESI,DWORD PTR SS:[EBP+80] ; 指向 CALL Address Struct
015401AF 8B46 04 MOV EAX,DWORD PTR DS:[ESI+4] ; CALL Address Struct 的结束地址
015401B2 05 00010000 ADD EAX,100
015401B7 25 00FFFFFF AND EAX,FFFFFF00
015401BC 8985 84000000 MOV DWORD PTR SS:[EBP+84],EAX ; 指向 API Address Struct
015401C2 90 NOP
015401C3 8D50 10 LEA EDX,DWORD PTR DS:[EAX+10]
015401C6 8910 MOV DWORD PTR DS:[EAX],EDX ; FirstAPIAddr
015401C8 8950 04 MOV DWORD PTR DS:[EAX+4],EDX ; CurrAPIAddr
015401CB 90 NOP
015401CC 8B85 80000000 MOV EAX,DWORD PTR SS:[EBP+80]
015401D2 8B30 MOV ESI,DWORD PTR DS:[EAX] ; ESI=CallAddrs[]
015401D4 8B85 84000000 MOV EAX,DWORD PTR SS:[EBP+84]
015401DA 8B38 MOV EDI,DWORD PTR DS:[EAX] ; EDI=APIAddrs[]
015401DC 90 NOP
015401DD AD LODS DWORD PTR DS:[ESI] ; CallAddr[i]
015401DE 83F8 00 CMP EAX,0
015401E1 74 3D JE SHORT 01540220 ; 所有的API 地址都得到了, 跳去IAT 修复
015401E3 FFE0 JMP EAX ; 跳到 CallAddr[i] 去执行 CALL 12000000
015401E5 90 NOP
015401E6 90 NOP
015401E7 90 NOP
015401E8 90 NOP
015401E9 90 NOP
015401EA 90 NOP
015401EB 90 NOP
015401EC 90 NOP
015401ED 90 NOP
015401EE 90 NOP
015401EF 90 NOP
015401F0 60 PUSHAD ; 015401F0 - 01540207 是 第 3 段CODE, 保存 EDX
015401F1 E8 00000000 CALL 015401F6
015401F6 5D POP EBP
015401F7 81E5 0000FFFF AND EBP,FFFF0000
015401FD 8955 54 MOV DWORD PTR SS:[EBP+54],EDX ; 保存 API Address 到 [EBP+54], 01540054
01540200 61 POPAD
01540201 FF15 18902C01 CALL DWORD PTR DS:[12C9018] ; 执行壳原来的代码
01540207 C3 RETN ; 回到壳中去
01540208 90 NOP
01540209 90 NOP
0154020A 90 NOP
0154020B 90 NOP
0154020C 90 NOP
0154020D 90 NOP
0154020E 90 NOP
0154020F 90 NOP
01540210 8B45 54 MOV EAX,DWORD PTR SS:[EBP+54] ; 由 Script 在 015100A3 JMP DWORD PTR SS:[ESP-4] 断点强行跳到这
01540213 AB STOS DWORD PTR ES:[EDI] ; API Address ==> APIAddrs[i]
01540214 ^ EB C7 JMP SHORT 015401DD
01540216 90 NOP
01540217 90 NOP
01540218 90 NOP
01540219 90 NOP
0154021A 90 NOP
0154021B 90 NOP
0154021C 90 NOP
0154021D 90 NOP
0154021E 90 NOP
0154021F 90 NOP
01540220 8B45 20 MOV EAX,DWORD PTR SS:[EBP+20] ; IAT address
01540223 8945 60 MOV DWORD PTR SS:[EBP+60],EAX ; IAT address
01540226 8B45 24 MOV EAX,DWORD PTR SS:[EBP+24] ; IAT size
01540229 8945 64 MOV DWORD PTR SS:[EBP+64],EAX ; IAT size
0154022C 90 NOP
0154022D 90 NOP
0154022E 90 NOP
0154022F 90 NOP
01540230 8B85 80000000 MOV EAX,DWORD PTR SS:[EBP+80]
01540236 8B30 MOV ESI,DWORD PTR DS:[EAX] ; ESI=CallAddrs[]
01540238 8B85 84000000 MOV EAX,DWORD PTR SS:[EBP+84]
0154023E 8B18 MOV EBX,DWORD PTR DS:[EAX] ; EBX=APIAddrs[]
01540240 90 NOP
01540241 AD LODS DWORD PTR DS:[ESI] ; EAX = Call Address
01540242 8B13 MOV EDX,DWORD PTR DS:[EBX] ; EDX = API Address
01540244 83F8 00 CMP EAX,0
01540247 74 6C JE SHORT 015402B5
01540249 50 PUSH EAX
0154024A 52 PUSH EDX
0154024B 90 NOP
0154024C 90 NOP
0154024D 90 NOP
0154024E 90 NOP
0154024F 8B7D 60 MOV EDI,DWORD PTR SS:[EBP+60] ; IAT
01540252 8B4D 64 MOV ECX,DWORD PTR SS:[EBP+64] ; IAT size
01540255 C1E9 02 SHR ECX,2
01540258 8BC2 MOV EAX,EDX
0154025A F2:AF REPNE SCAS DWORD PTR ES:[EDI] ; 在IAT中找 API Address
0154025C 90 NOP
0154025D 83F9 00 CMP ECX,0
01540260 75 3D JNZ SHORT 0154029F ; 找到就跳
01540262 90 NOP ; 没找到, 要加进去
01540263 A9 000000C0 TEST EAX,C0000000 ; 是系统API 地址吗?
01540268 74 17 JE SHORT 01540281 ; 不是的, 就把API Address 当 DLL 基址好了
0154026A 25 0000FFFF AND EAX,FFFF0000 ; 这5行是从ASPR抄过来的, 根据地址求 DLL 基址
0154026F 05 00000100 ADD EAX,10000 ;
01540274 2D 00000100 SUB EAX,10000 ;
01540279 66:8138 4D5A CMP WORD PTR DS:[EAX],5A4D
0154027E ^ 75 F4 JNZ SHORT 01540274
01540280 90 NOP
01540281 837D 58 00 CMP DWORD PTR SS:[EBP+58],0 ; 以前的 DLL 基址有值吗?
01540285 74 10 JE SHORT 01540297 ; 没有, 跳去保存 DLL 基址, 并加 IAT
01540287 3B45 58 CMP EAX,DWORD PTR SS:[EBP+58] ; 基址 == 以前的 DLL 基址 ?
0154028A 74 0B JE SHORT 01540297 ; 是, 相同的DLL
0154028C 90 NOP ; 不同的DLL
0154028D 8945 58 MOV DWORD PTR SS:[EBP+58],EAX ; 保存 DLL 基址
01540290 33C0 XOR EAX,EAX
01540292 AB STOS DWORD PTR ES:[EDI] ; 加 00000000 到 IAT, 表示上一个DLL的IAT结束
01540293 8345 64 04 ADD DWORD PTR SS:[EBP+64],4 ; size + = 4
01540297 8945 58 MOV DWORD PTR SS:[EBP+58],EAX ; 保存 DLL 基址
0154029A 8BC2 MOV EAX,EDX
0154029C AB STOS DWORD PTR ES:[EDI] ; 加 API 地址 到 IAT
0154029D 8345 64 04 ADD DWORD PTR SS:[EBP+64],4 ; size += 4
015402A1 90 NOP
015402A2 83EF 04 SUB EDI,4 ; EDI 指向 IAT 中的 API
015402A5 5A POP EDX
015402A6 58 POP EAX
015402A7 66:C700 FF25 MOV WORD PTR DS:[EAX],25FF ; 改 CALL 12000000 成 JMP DWORD PTR DS:[xxxx], (Delphi)
如果是C的话, 就应该是 MOV WORD PTR DS:[EAX],15FF, 即改成 CALL DWORD PTR DS:[xxxx]
015402AC 8978 02 MOV DWORD PTR DS:[EAX+2],EDI
015402AF 83C3 04 ADD EBX,4
015402B2 ^ EB 8D JMP SHORT 01540241 ; 循环
015402B4 90 NOP
015402B5 33C0 XOR EAX,EAX ; 最后再往IAT中加一个00000000
015402B7 83C7 04 ADD EDI,4
015402BA AB STOS DWORD PTR ES:[EDI]
015402BB 8345 58 04 ADD DWORD PTR SS:[EBP+58],4 ; 用ImportRec修复时, 用[EBP+58]做IAT size
015402BF 61 POPAD ;
015402C0 - EB FE JMP SHORT 015402C0 ; 死循环, 万一忘了在这设断点, 修复代码就跑飞了
015402C2 90 NOP ; 修复完毕, 回OEP DUMP & 修复
015402C3 90 NOP
015402C4 90 NOP
01540300 DD FirstCallAddr // 01540310
01540304 DD CurrCallAddr
01540310 DD CallAddrs[]
0154xx00 DD FirstAPIAddr
0154xx04 DD CurrAPIAddr
0154xx10 DD APIAddrs[]
Binary Code@1540100
60 55 90 E8 00 00 00 00 5D 8D 55 5A 81 E5 00 00 FF FF 90 83 BD 80 00 00 00 00 75 1B 8B 45 00 83
C0 05 89 45 40 8D 45 40 89 02 8D 95 00 03 00 00 89 95 80 00 00 00 90 8B 8D 80 00 00 00 83 79 04
00 75 09 8D 51 10 89 11 89 51 04 90 8B 79 04 90 5D 89 2F 83 C7 04 89 79 04 61 45 89 45 00 6A 0A
FF 25 40 00 54 01 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
60 E8 00 00 00 00 5D 8D 55 7D 90 90 90 81 E5 00 00 FF FF 90 8B 5D 10 8B 43 02 89 02 8D 42 ED 89
45 50 8D 45 50 89 43 02 90 8B B5 80 00 00 00 8B 46 04 05 00 01 00 00 25 00 FF FF FF 89 85 84 00
00 00 90 8D 50 10 89 10 89 50 04 90 8B 85 80 00 00 00 8B 30 8B 85 84 00 00 00 8B 38 90 AD 83 F8
00 74 3D FF E0 90 90 90 90 90 90 90 90 90 90 90 60 E8 00 00 00 00 5D 81 E5 00 00 FF FF 89 55 54
61 FF 15 18 90 2C 01 C3 90 90 90 90 90 90 90 90 8B 45 54 AB EB C7 90 90 90 90 90 90 90 90 90 90
8B 45 20 89 45 60 8B 45 24 89 45 64 90 90 90 90 8B 85 80 00 00 00 8B 30 8B 85 84 00 00 00 8B 18
90 AD 8B 13 83 F8 00 74 6C 50 52 90 90 90 90 8B 7D 60 8B 4D 64 C1 E9 02 8B C2 F2 AF 90 83 F9 00
75 3D 90 A9 00 00 00 C0 74 17 25 00 00 FF FF 05 00 00 01 00 2D 00 00 01 00 66 81 38 4D 5A 75 F4
90 83 7D 58 00 74 10 3B 45 58 74 0B 90 89 45 58 33 C0 AB 83 45 64 04 89 45 58 8B C2 AB 83 45 64
04 90 83 EF 04 5A 58 66 C7 00 FF 25 89 78 02 83 C3 04 EB 8D 90 33 C0 83 C7 04 AB 83 45 58 04 61
EB FE 90 90 90
3.2) 修复用的 Script
// filename: ospr.osc
var addr // 定义变量
start:
eob label_break // 中断发生时,跳到 label_break
run // == F9
label_break:
cmp eip,015100A3 // 每次运行这个值都要重新修改
jne label_exit
mov addr,esp
sub addr,4
mov [addr],1540210 // 回到CODE2中去
jmp start
label_exit:
ret // 退出 script
3.3) 例子下载:
http://www.crystaloffice.com/download.html
4.0 心得与感受
4.1) 感觉现在用 ASPR V2.X 加密的软件越来越多了.
4.2) 有些版本的 ASPR 会在INT 3 的 SEH 中 清 调试寄存器, 所以在INT 3 之前设的硬件断点会失效.
4.3) 有些版本的 ASPR 在 CALL 12000000 后, 并不改写 CALL 12000000, 并且出现API Address 的CODE 也与本文中的
Patch2 处的CODE 不一样; 但用 Ctrl+F11 跟到POPF 处, 在 Run Trace 中总有个地方 API Address 会出现在某
一寄存器中.
4.4) ASPR V2.X 真的好难脱, 即使没有 Stolen code
本人在此只是依葫芦画瓢, 并将有些地方再详细的解释一下, 给菜鸟们脱Asprotect V2.X时进行参考, 高手就不要看了.
先在理论上研讨一下, 由于编译器的不一样, C 与 Delphi 所编译的汇编结果有差别, Asprotect 加壳时处理的也不一样.
先假设有一程序:
OEP: 00401000
IAT: 00407000 - 00407FFF
在00401100 CALL DLL1.API1
在00401180 CALL DLL1.API2
Ospr 加壳后, 好多的API CALL 被改成 CALL 12000000
然后开始研究.
1. C/C++ Program
1.1) 未被加壳的程序
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 FF15 00704000 CALL DWORD PTR DS:[00407000] ; DLL1.API1
00401106 ...
00401180 FF15 04704000 CALL DWORD PTR DS:[00407004] ; DLL1.API2
00401186 ...
********* IAT 可能是这样的 ************
00407000 DD 7C571000 // DLL1.API1
00407004 DD 7C572000 // DLL1.API2
...
004070FC DD 00000000
00407100 DD 7D00XXXX // DLL2.API1
...
004071FC DD 00000000
...
00407F00 DD 7F00XXXX // DLLn.API1
...
00407FFC DD 00000000
1.2) 被 Asprotect 加壳后的程序在OEP
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 FBEEBF11 CALL 12000000 ; 壳
00401105 ??
00401106 ...
00401180 E8 7BEEBF11 CALL 12000000 ; 壳
00401185 ??
00401186 ...
*************IAT************************
00407000 DD ???????? // 被加密的 DLL1.API1 信息
00407004 DD 7C572000 // 未加密的 DLL1.API2
ASPR 将许多的API CALL 都改成了统一的 CALL 12000000
即改 CALL DWORD PTR DS:[xxxxx] 成 CALL 12000000
1.3) 加壳后的程序运行过API CALL 后
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 00EFBF12 CALL 13000005 ; 壳
00401105 ??
00401106 ... ; 假设运行到这
00401180 E8 7BEEBF11 CALL 12000000 ; 壳
00401185 ??
00401186 ...
即 每经过一个CALL12000000 后, 这个 CALL12000000 就会被改成 CALL13000005 或者 其它的 CALL 14000005, 反正每个API 还不一样, 分别
对应一段壳的CODE, 等于相对应的API CODE
1.4) 脱壳与修复需要解决以下几个问题
A.) 找到每一个 CALL 12000000 所在的地址, 例如00401100, 00401180, ...
B.) 找到每一个 CALL 12000000 实际应该CALL到的 API 地址, 例如 7C571000, 7C572000, ...
C.) 如果IAT的地址未加密, 修复 CALL 12000000 成 CALL DWORD PTR DS:[0040xxxx]
例如 00401180 E8 7BEEBF11 CALL 12000000 这句, 根据 API 地址 7C572000 可在 IAT 中找到, 位于00407004
修复成 00401180 FF15 04704000 CALL DWORD PTR DS:[00407004]
注意: CALL 12000000 是 5 个字节, 而 CALL DWORD PTR DS:[xxxxxxxx] 是 6 个字节
D.) 如果IAT的地址被加密, 可在IAT强行加上一项, 再修复 CALL 12000000 成 CALL DWORD PTR DS:[0040xxxx]
例如 00401100 E8 FBEEBF11 CALL 12000000 这句, API 地址 7C571000 在 IAT 中找不到,
可在IAT最后加上一项, 最后的IAT可能会变成这样:
...
00407F00 DD 7D00XXXX // DLLn.API1
...
00407FFC DD 00000000 // 原来的 IAT 到这结束
00408000 DD 7C571000 // 新加的 IAT 项
00408004 DD 7C573000 // 新加的 IAT 项
...
00408080 DD 00000000 // 每个DLL的IAT必须以 00000000 结束, 不能与其它DLL 的IAT连成一片
00408084 DD 7D00xxxx // 新加的 IAT 项, 另外的DLL的
...
........ DD 00000000 // 修复的 IAT 到这结束
再把00401180 修复成 00401100 FF15 04704000 CALL DWORD PTR DS:[00408004]
注意: 这里已经与未加壳的程序有一点不一样了, 即 API 地址在 IAT 的位置不一样, 但功能是一样的,
1.5) 在到达OEP之前, 壳把程序中的API CALL 改成 CALL 12000000 的地方的CODE 是这样的:
012C5F7A 45 INC EBP ; EBP 就是 CALL 12000000 处的地址, Pactch1
012C5F7B 8945 00 MOV DWORD PTR SS:[EBP],EAX
012C5F7E 6A 0A PUSH 0A
012C5F80 E8 8FCEFEFF CALL 012B2E14
012C5F85 8BC8 MOV ECX,EAX
012C5F87 038B E4000000 ADD ECX,DWORD PTR DS:[EBX+E4]
012C5F8D 8BD6 MOV EDX,ESI
012C5F8F 8BC3 MOV EAX,EBX
012C5F91 E8 9EE5FFFF CALL 012C4534
012C5F96 FF0C24 DEC DWORD PTR SS:[ESP]
012C5F99 03B3 E4000000 ADD ESI,DWORD PTR DS:[EBX+E4]
012C5F9F 833C24 00 CMP DWORD PTR SS:[ESP],0
012C5FA3 ^ 0F87 55FEFFFF JA 012C5DFE ; 循环
012C5FA9 53 PUSH EBX ; 到这, 所有的 API CALL 改 CALL 12000000 都完成了
所以 在 012C5F7A 处, 要先到我们的修复 CODE1, 把 EBP 保存下来, 然后再回来
012C5F7A E8 xxxxxxxx JMP 1540100 ;
012C5F7F 90 NOP
到 012C5FA9 后, 把 012C5F7A 处的CODE 还原, 再运行到 OEP
找这段CODE, 可在壳解压缩自身后, SEARCH
inc ebp
move [ebp], eax
push 0A
1.6) 到达OEP之后, 如果跟踪 CALL 12000000 到壳中, 壳会根据CALL是从哪儿来的 解码 API 地址,
API 地址 出现并且不会被壳检测的地方的CODE是这样的:
012A2544 85C0 TEST EAX,EAX
012A2546 74 0A JE SHORT 012A2552
012A2548 FF15 18902C01 CALL DWORD PTR DS:[12C9018] // 到这儿时, EDX=API 地址, Patch2
012A254E 09C0 OR EAX,EAX
012A2550 74 01 JE SHORT 012A2553
012A2552 C3 RETN
然后壳把CALL 12000000 改成 相应的CALL 13000005
最后壳再跳到新改成的 CALL 13000005 处继续执行
015100A1 9D POPFD
015100A2 5C POP ESP
015100A3 FF6424 FC JMP DWORD PTR SS:[ESP-4] // 这儿跳到00401100 处的 CALL 13000005
015100A7 F2: PREFIX REPNE: ; Superfluous prefix
015100A8 EB 01 JMP SHORT 015100AB
015100AA - E9 94CA2E01 JMP 027FCB43
015100AF C3 RETN
在 OEP 处修复时, 就是用修复 CODE2 模拟的运行 每一处的 CALL 12000000, 取得 每一处的对应的API 地址,
然后在 JMP DWORD PTR SS:[ESP-4] 处截住, 这时当然还不能让它跑去执行真正的API CODE
根据CALL 12000000 所在的地址与得到的 API 地址就可修复 IAT
然后再模拟运行下一个 CALL 12000000
所以 在 012A2548 处, 要先到我们的修复 CODE3, 把 EDX 保存下来, 然后再回来
012A2548 FF15 xxxxxxxx CALL DWORD PTR DS:[01540050]
// 01540050 中是CODE3 的起始地址 015401F0
在 015100A3 处, 不能让它自由的跳, 要让它回到 修复 CODE2 去 修复 IAT
loveboom 说这儿的 CODE 不能改, 壳会检测出来, 我没试过. 反正可在这儿设硬件断点, 再靠script 强行跳到 CODE2 中.
只是每次运行时 JMP DWORD PTR SS:[ESP-4] 这一句所在的地址还都不一样, 真的挺烦人的
找这段CODE, 可 在OD 中 用 Ctrl+T 设 Run Trace 的暂停条件 command is POPFD, 再 Ctrl+F11,
找到后去掉暂停条件,并设硬件断点, 否则 Script 运行会出错的.
2. 再讨论 Delph 程序
2.1) 未被加壳的程序
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 FB4E0000 CALL 00406000 ; DLL1.API1
00401105 ...
00401180 E8 7F4E0000 CALL 00406004 ; DLL1.API2
00401185 ...
00406000 FF25 00704000 JMP DWORD PTR DS:[00407000] ; DLL1.API1
00406006 FF25 04704000 JMP DWORD PTR DS:[00407004] ; DLL1.API2
*************IAT************************
IAT 与 C/C++ 程序一样
2.2) 被 Asprotect 加壳后的程序在OEP
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 FB4E0000 CALL 00406000 ; DLL1.API1
00401105 ...
00401180 E8 7F4E0000 CALL 00406004 ; DLL1.API2
00401185 ...
00406000 E8 FB9FBF11 CALL 12000000 ; 壳
00406005 ??
00406006 E8 7B9FBF11 CALL 12000000 ; 壳
0040600B ??
注意: ASPR 修改的地方与C/C++程序是不一样的, 它是改 JMP DWORD PTR DS:[xxxxx] 成 CALL 12000000
2.3) 加壳后的程序运行过API CALL 后
00401000 55 PUSH EBP ; 程序 OEP
00401001 8BEC MOV EBP, ESP
...
00401100 E8 FB4E0000 CALL 00406000 ; DLL1.API1
00401105 ...
00401180 E8 7F4E0000 CALL 00406004 ; DLL1.API2
00401185 ...
00406000 E8 00A0BF12 CALL 13000005 ; 壳
00406005 ??
00406006 E8 7B9FBF11 CALL 12000000 ; 壳
0040600B ??
同样是把CALL 12000000 改成 CALL 13000005
2.4) 修复与C程序类似, 只是要把
CALL 12000000
修复成
FF25 xxxxxxxx JMP DWORD PTR DS:[xxxxxxxx]
3.0 理论上研讨完了, 再说实际修复
3.1) 与位置无关的修复CODE
需要OLLYDBG 的plugin: Memory Manage, 分配 一块内存
01540000 DD 012C5F7A ; // Patch1 Address, 供 第 1 段CODE 用
01540010 DD 012A2548 ; // Patch2 Address, 供 第 2 段CODE 用
01540020 DD 0077A180 ; // IAT Start Address, 供 第 2 段CODE 用
01540024 DD 00001408 ; // Original IAT Size, 供 第 2 段CODE 用
01540040 DD ?? ; // Patch1 后的 Address
01540050 DD ?? ; // 第 3 段CODE 的 Address
01540054 DD ?? ; // CurrAPIAddress
01540058 DD ?? ; // CurrDLLBase
01540060 DD ?? ; // IAT Start Address
01540064 DD ?? ; // Current IAT Size
01540080 DD ?? ; // 指向 CALL Address Struct
01540084 DD ?? ; // 指向 API Address Struct
// 01540100 - 01540165 为 第 1 段CODE, 保存 每一个 CALL 的地址 到我们的表中, 即EBP
// 需要 01540000 处为 Patch1 Address
01540100 60 PUSHAD
01540101 55 PUSH EBP ; EBP 中为CALL 12000000 处的地址
01540102 90 NOP
01540103 E8 00000000 CALL 01540108
01540108 5D POP EBP
01540109 8D55 5A LEA EDX,DWORD PTR SS:[EBP+5A] ; EDX = 01540162
0154010C 81E5 0000FFFF AND EBP,FFFF0000 ; EBP = 01540000, 我们的基址
01540112 90 NOP
01540113 83BD 80000000 0>CMP DWORD PTR SS:[EBP+80],0
0154011A 75 1B JNZ SHORT 01540137
0154011C 8B45 00 MOV EAX,DWORD PTR SS:[EBP] ; Patch1 Address
0154011F 83C0 05 ADD EAX,5
01540122 8945 40 MOV DWORD PTR SS:[EBP+40],EAX ; Patch1 后的 返回Address
01540125 8D45 40 LEA EAX,DWORD PTR SS:[EBP+40]
01540128 8902 MOV DWORD PTR DS:[EDX],EAX ; 自动修正 01540160 中的 JMP DWORD PTR: [xxxx]
0154012A 8D95 00030000 LEA EDX,DWORD PTR SS:[EBP+300] ; CALL Address Struct @ 基址+300, 够了吧!
01540130 8995 80000000 MOV DWORD PTR SS:[EBP+80],EDX ; 保存
01540136 90 NOP
01540137 8B8D 80000000 MOV ECX,DWORD PTR SS:[EBP+80]
0154013D 8379 04 00 CMP DWORD PTR DS:[ECX+4],0 ; CurrCallAddr
01540141 75 09 JNZ SHORT 0154014C
01540143 8D51 10 LEA EDX,DWORD PTR DS:[ECX+10]
01540146 8911 MOV DWORD PTR DS:[ECX],EDX
01540148 8951 04 MOV DWORD PTR DS:[ECX+4],EDX
0154014B 90 NOP
0154014C 8B79 04 MOV EDI,DWORD PTR DS:[ECX+4]
0154014F 90 NOP
01540150 5D POP EBP ; EBP 中为CALL 12000000 处的地址
01540151 892F MOV DWORD PTR DS:[EDI],EBP ; 保存 CALL 地址 到我们的表中
01540153 83C7 04 ADD EDI,4
01540156 8979 04 MOV DWORD PTR DS:[ECX+4],EDI ; CurrCallAddr 指向表中的下一处
01540159 61 POPAD
0154015A 45 INC EBP ; 执行壳原来的代码
0154015B 8945 00 MOV DWORD PTR SS:[EBP],EAX ; 执行壳原来的代码
0154015E 6A 0A PUSH 0A ; 执行壳原来的代码
01540160 FF25 40005401 JMP DWORD PTR DS:[1540040] ; 回到壳中去
01540166 90 NOP
01540167 90 NOP
01540168 90 NOP
01540169 90 NOP
0154016A 90 NOP
0154016B 90 NOP
0154016C 90 NOP
0154016D 90 NOP
0154016E 90 NOP
0154016F 90 NOP
01540170 90 NOP
01540171 90 NOP
01540172 90 NOP
01540173 90 NOP
01540174 90 NOP
01540175 90 NOP
01540176 90 NOP
01540177 90 NOP
01540178 90 NOP
01540179 90 NOP
0154017A 90 NOP
0154017B 90 NOP
0154017C 90 NOP
0154017D 90 NOP
0154017E 90 NOP
0154017F 90 NOP
// 01540180 - 015402BF 为 第 2 段CODE, 修复每一个CALL, 及IAT
// 需要 01540010 处为 Patch2 Address, 01540020 处为IAT Address & Size
01540180 60 PUSHAD
01540181 E8 00000000 CALL 01540186
01540186 5D POP EBP
01540187 8D55 7D LEA EDX,DWORD PTR SS:[EBP+7D] ; EDX = 01540203
0154018A 90 NOP
0154018B 90 NOP
0154018C 90 NOP
0154018D 81E5 0000FFFF AND EBP,FFFF0000 ; EBP = 01540000, 我们的基址
01540193 90 NOP
01540194 8B5D 10 MOV EBX,DWORD PTR SS:[EBP+10] ; Patch2 Address
01540197 8B43 02 MOV EAX,DWORD PTR DS:[EBX+2] ; 取 012A2548处 CALL [12C9018] 中的 12C9018
0154019A 8902 MOV DWORD PTR DS:[EDX],EAX ; 改 01540201处 为 CALL DWORD PTR DS:[12C9018]
0154019C 8D42 ED LEA EAX,DWORD PTR DS:[EDX-13] ; 第 3 段 CODE 的 Address
0154019F 8945 50 MOV DWORD PTR SS:[EBP+50],EAX
015401A2 8D45 50 LEA EAX,DWORD PTR SS:[EBP+50]
015401A5 8943 02 MOV DWORD PTR DS:[EBX+2],EAX ; 改 Patch2 处 为 CALL DWORD PTR DS:[1540050]
015401A8 90 NOP
015401A9 8BB5 80000000 MOV ESI,DWORD PTR SS:[EBP+80] ; 指向 CALL Address Struct
015401AF 8B46 04 MOV EAX,DWORD PTR DS:[ESI+4] ; CALL Address Struct 的结束地址
015401B2 05 00010000 ADD EAX,100
015401B7 25 00FFFFFF AND EAX,FFFFFF00
015401BC 8985 84000000 MOV DWORD PTR SS:[EBP+84],EAX ; 指向 API Address Struct
015401C2 90 NOP
015401C3 8D50 10 LEA EDX,DWORD PTR DS:[EAX+10]
015401C6 8910 MOV DWORD PTR DS:[EAX],EDX ; FirstAPIAddr
015401C8 8950 04 MOV DWORD PTR DS:[EAX+4],EDX ; CurrAPIAddr
015401CB 90 NOP
015401CC 8B85 80000000 MOV EAX,DWORD PTR SS:[EBP+80]
015401D2 8B30 MOV ESI,DWORD PTR DS:[EAX] ; ESI=CallAddrs[]
015401D4 8B85 84000000 MOV EAX,DWORD PTR SS:[EBP+84]
015401DA 8B38 MOV EDI,DWORD PTR DS:[EAX] ; EDI=APIAddrs[]
015401DC 90 NOP
015401DD AD LODS DWORD PTR DS:[ESI] ; CallAddr[i]
015401DE 83F8 00 CMP EAX,0
015401E1 74 3D JE SHORT 01540220 ; 所有的API 地址都得到了, 跳去IAT 修复
015401E3 FFE0 JMP EAX ; 跳到 CallAddr[i] 去执行 CALL 12000000
015401E5 90 NOP
015401E6 90 NOP
015401E7 90 NOP
015401E8 90 NOP
015401E9 90 NOP
015401EA 90 NOP
015401EB 90 NOP
015401EC 90 NOP
015401ED 90 NOP
015401EE 90 NOP
015401EF 90 NOP
015401F0 60 PUSHAD ; 015401F0 - 01540207 是 第 3 段CODE, 保存 EDX
015401F1 E8 00000000 CALL 015401F6
015401F6 5D POP EBP
015401F7 81E5 0000FFFF AND EBP,FFFF0000
015401FD 8955 54 MOV DWORD PTR SS:[EBP+54],EDX ; 保存 API Address 到 [EBP+54], 01540054
01540200 61 POPAD
01540201 FF15 18902C01 CALL DWORD PTR DS:[12C9018] ; 执行壳原来的代码
01540207 C3 RETN ; 回到壳中去
01540208 90 NOP
01540209 90 NOP
0154020A 90 NOP
0154020B 90 NOP
0154020C 90 NOP
0154020D 90 NOP
0154020E 90 NOP
0154020F 90 NOP
01540210 8B45 54 MOV EAX,DWORD PTR SS:[EBP+54] ; 由 Script 在 015100A3 JMP DWORD PTR SS:[ESP-4] 断点强行跳到这
01540213 AB STOS DWORD PTR ES:[EDI] ; API Address ==> APIAddrs[i]
01540214 ^ EB C7 JMP SHORT 015401DD
01540216 90 NOP
01540217 90 NOP
01540218 90 NOP
01540219 90 NOP
0154021A 90 NOP
0154021B 90 NOP
0154021C 90 NOP
0154021D 90 NOP
0154021E 90 NOP
0154021F 90 NOP
01540220 8B45 20 MOV EAX,DWORD PTR SS:[EBP+20] ; IAT address
01540223 8945 60 MOV DWORD PTR SS:[EBP+60],EAX ; IAT address
01540226 8B45 24 MOV EAX,DWORD PTR SS:[EBP+24] ; IAT size
01540229 8945 64 MOV DWORD PTR SS:[EBP+64],EAX ; IAT size
0154022C 90 NOP
0154022D 90 NOP
0154022E 90 NOP
0154022F 90 NOP
01540230 8B85 80000000 MOV EAX,DWORD PTR SS:[EBP+80]
01540236 8B30 MOV ESI,DWORD PTR DS:[EAX] ; ESI=CallAddrs[]
01540238 8B85 84000000 MOV EAX,DWORD PTR SS:[EBP+84]
0154023E 8B18 MOV EBX,DWORD PTR DS:[EAX] ; EBX=APIAddrs[]
01540240 90 NOP
01540241 AD LODS DWORD PTR DS:[ESI] ; EAX = Call Address
01540242 8B13 MOV EDX,DWORD PTR DS:[EBX] ; EDX = API Address
01540244 83F8 00 CMP EAX,0
01540247 74 6C JE SHORT 015402B5
01540249 50 PUSH EAX
0154024A 52 PUSH EDX
0154024B 90 NOP
0154024C 90 NOP
0154024D 90 NOP
0154024E 90 NOP
0154024F 8B7D 60 MOV EDI,DWORD PTR SS:[EBP+60] ; IAT
01540252 8B4D 64 MOV ECX,DWORD PTR SS:[EBP+64] ; IAT size
01540255 C1E9 02 SHR ECX,2
01540258 8BC2 MOV EAX,EDX
0154025A F2:AF REPNE SCAS DWORD PTR ES:[EDI] ; 在IAT中找 API Address
0154025C 90 NOP
0154025D 83F9 00 CMP ECX,0
01540260 75 3D JNZ SHORT 0154029F ; 找到就跳
01540262 90 NOP ; 没找到, 要加进去
01540263 A9 000000C0 TEST EAX,C0000000 ; 是系统API 地址吗?
01540268 74 17 JE SHORT 01540281 ; 不是的, 就把API Address 当 DLL 基址好了
0154026A 25 0000FFFF AND EAX,FFFF0000 ; 这5行是从ASPR抄过来的, 根据地址求 DLL 基址
0154026F 05 00000100 ADD EAX,10000 ;
01540274 2D 00000100 SUB EAX,10000 ;
01540279 66:8138 4D5A CMP WORD PTR DS:[EAX],5A4D
0154027E ^ 75 F4 JNZ SHORT 01540274
01540280 90 NOP
01540281 837D 58 00 CMP DWORD PTR SS:[EBP+58],0 ; 以前的 DLL 基址有值吗?
01540285 74 10 JE SHORT 01540297 ; 没有, 跳去保存 DLL 基址, 并加 IAT
01540287 3B45 58 CMP EAX,DWORD PTR SS:[EBP+58] ; 基址 == 以前的 DLL 基址 ?
0154028A 74 0B JE SHORT 01540297 ; 是, 相同的DLL
0154028C 90 NOP ; 不同的DLL
0154028D 8945 58 MOV DWORD PTR SS:[EBP+58],EAX ; 保存 DLL 基址
01540290 33C0 XOR EAX,EAX
01540292 AB STOS DWORD PTR ES:[EDI] ; 加 00000000 到 IAT, 表示上一个DLL的IAT结束
01540293 8345 64 04 ADD DWORD PTR SS:[EBP+64],4 ; size + = 4
01540297 8945 58 MOV DWORD PTR SS:[EBP+58],EAX ; 保存 DLL 基址
0154029A 8BC2 MOV EAX,EDX
0154029C AB STOS DWORD PTR ES:[EDI] ; 加 API 地址 到 IAT
0154029D 8345 64 04 ADD DWORD PTR SS:[EBP+64],4 ; size += 4
015402A1 90 NOP
015402A2 83EF 04 SUB EDI,4 ; EDI 指向 IAT 中的 API
015402A5 5A POP EDX
015402A6 58 POP EAX
015402A7 66:C700 FF25 MOV WORD PTR DS:[EAX],25FF ; 改 CALL 12000000 成 JMP DWORD PTR DS:[xxxx], (Delphi)
如果是C的话, 就应该是 MOV WORD PTR DS:[EAX],15FF, 即改成 CALL DWORD PTR DS:[xxxx]
015402AC 8978 02 MOV DWORD PTR DS:[EAX+2],EDI
015402AF 83C3 04 ADD EBX,4
015402B2 ^ EB 8D JMP SHORT 01540241 ; 循环
015402B4 90 NOP
015402B5 33C0 XOR EAX,EAX ; 最后再往IAT中加一个00000000
015402B7 83C7 04 ADD EDI,4
015402BA AB STOS DWORD PTR ES:[EDI]
015402BB 8345 58 04 ADD DWORD PTR SS:[EBP+58],4 ; 用ImportRec修复时, 用[EBP+58]做IAT size
015402BF 61 POPAD ;
015402C0 - EB FE JMP SHORT 015402C0 ; 死循环, 万一忘了在这设断点, 修复代码就跑飞了
015402C2 90 NOP ; 修复完毕, 回OEP DUMP & 修复
015402C3 90 NOP
015402C4 90 NOP
01540300 DD FirstCallAddr // 01540310
01540304 DD CurrCallAddr
01540310 DD CallAddrs[]
0154xx00 DD FirstAPIAddr
0154xx04 DD CurrAPIAddr
0154xx10 DD APIAddrs[]
Binary Code@1540100
60 55 90 E8 00 00 00 00 5D 8D 55 5A 81 E5 00 00 FF FF 90 83 BD 80 00 00 00 00 75 1B 8B 45 00 83
C0 05 89 45 40 8D 45 40 89 02 8D 95 00 03 00 00 89 95 80 00 00 00 90 8B 8D 80 00 00 00 83 79 04
00 75 09 8D 51 10 89 11 89 51 04 90 8B 79 04 90 5D 89 2F 83 C7 04 89 79 04 61 45 89 45 00 6A 0A
FF 25 40 00 54 01 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
60 E8 00 00 00 00 5D 8D 55 7D 90 90 90 81 E5 00 00 FF FF 90 8B 5D 10 8B 43 02 89 02 8D 42 ED 89
45 50 8D 45 50 89 43 02 90 8B B5 80 00 00 00 8B 46 04 05 00 01 00 00 25 00 FF FF FF 89 85 84 00
00 00 90 8D 50 10 89 10 89 50 04 90 8B 85 80 00 00 00 8B 30 8B 85 84 00 00 00 8B 38 90 AD 83 F8
00 74 3D FF E0 90 90 90 90 90 90 90 90 90 90 90 60 E8 00 00 00 00 5D 81 E5 00 00 FF FF 89 55 54
61 FF 15 18 90 2C 01 C3 90 90 90 90 90 90 90 90 8B 45 54 AB EB C7 90 90 90 90 90 90 90 90 90 90
8B 45 20 89 45 60 8B 45 24 89 45 64 90 90 90 90 8B 85 80 00 00 00 8B 30 8B 85 84 00 00 00 8B 18
90 AD 8B 13 83 F8 00 74 6C 50 52 90 90 90 90 8B 7D 60 8B 4D 64 C1 E9 02 8B C2 F2 AF 90 83 F9 00
75 3D 90 A9 00 00 00 C0 74 17 25 00 00 FF FF 05 00 00 01 00 2D 00 00 01 00 66 81 38 4D 5A 75 F4
90 83 7D 58 00 74 10 3B 45 58 74 0B 90 89 45 58 33 C0 AB 83 45 64 04 89 45 58 8B C2 AB 83 45 64
04 90 83 EF 04 5A 58 66 C7 00 FF 25 89 78 02 83 C3 04 EB 8D 90 33 C0 83 C7 04 AB 83 45 58 04 61
EB FE 90 90 90
3.2) 修复用的 Script
// filename: ospr.osc
var addr // 定义变量
start:
eob label_break // 中断发生时,跳到 label_break
run // == F9
label_break:
cmp eip,015100A3 // 每次运行这个值都要重新修改
jne label_exit
mov addr,esp
sub addr,4
mov [addr],1540210 // 回到CODE2中去
jmp start
label_exit:
ret // 退出 script
3.3) 例子下载:
http://www.crystaloffice.com/download.html
4.0 心得与感受
4.1) 感觉现在用 ASPR V2.X 加密的软件越来越多了.
4.2) 有些版本的 ASPR 会在INT 3 的 SEH 中 清 调试寄存器, 所以在INT 3 之前设的硬件断点会失效.
4.3) 有些版本的 ASPR 在 CALL 12000000 后, 并不改写 CALL 12000000, 并且出现API Address 的CODE 也与本文中的
Patch2 处的CODE 不一样; 但用 Ctrl+F11 跟到POPF 处, 在 Run Trace 中总有个地方 API Address 会出现在某
一寄存器中.
4.4) ASPR V2.X 真的好难脱, 即使没有 Stolen code
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
看原图
赞赏
雪币:
留言: