AntiCrack Protector 1.0x -> RISCO Software Inc脱壳详解
本帖最后由 镇北看雪 于 2020-8-26 16:57 编辑# 写在前面
***
ACProtect 1.x的壳,此壳所用的防脱手段有Anti Debug,Anti Dump,stolen bytes以及检测检测断点和IAT加密等。在脱此壳的时候可以利用OD脚本辅助完成一些操作,下面我们就一起来看看这个壳。
# 分析工具
***
* OD
* ImportRE
* LordPE
*运行环境虚拟机 Windows XP
# 去除外壳
***
脱壳一般分三个步骤,寻找OEP,dump程序,重建输入表。
## 寻找OEP
我们用OD加载程序后直接运行,结果程序直接结束运行。
所以我们怀疑应该是有反调试存在,我们利用插件HideOD配置好反反调试选项,然后运行程序发现程序没有结束运行。
接着我们发现程序有多处异常,所以我们利用最后一次异常法来寻找OEP。我们多次Shift+F9后使程序运行起来后打开日志记录窗口,发现最后一次异常发生的地址是0x47108E。
我们重新用OD加载程序,然后多次Shift+F9后来到最后一次异常处。
然后我们对主模块的text代码段设置内存访问断点。然后我们Shift+F9运行程序,程序停在了OEP处。但是我们发现OEP处的代码并不是正常的入口点代码,所以壳应该是采用了stolen bytes手段,此OEP为假的OEP。
我们需要找到被窃取的代码并将其恢复,进一步获取程序真正的OEP。
### 解决stolen bytes
我们重新加载程序来到最后一次异常处,在异常处理程序下断点并来到异常处理程序中。单步往下跟我们发现其调用ZwContext()函数的context参数的eip为0x471090,刚好为刚刚发生异常时的下一条指令的地址。
我们在此地址处下断点,运行程序来到此处。我们设置跟踪暂停条件为`push ebp 和 popad`即遇到这两条指令就停止跟踪。
设置好之后我们Ctrl+F11跟踪步入,结果我们发现指令太对很长时间之后程序还是没有停止。我们需要换一种思路。因为我们是为了找到被窃取的代码,而在壳代码空间中执行被窃取的代码之前一定会进行popad恢复寄存器环境,所以我们利用ESP定律来寻找被窃取的代码。首先我们重新加载程序查看push后栈指针的值,我们发现执行完push后esp为0x12FFA4。
我们再次运行程序来到最后一次异常发生后的下一条指令处,我们对0x12FFA4下硬件访问断点。然后我们运行程序,发现程序在执行了一条popad指令后停了下来。
而且我们发现在执行完popad指令程序停下来后,下一条即将执行的指令为push ebp,很像程序真正的入口点代码。我们向下运行验证此处是否为被窃取的代码,F9运行数次后其到了假的OEP跳转处,所以说明此处就是被窃取的代码。
我们分析执行完第一次popad后的被窃取的入口点代码,我们发现在执行完`push ebpmov ebp,esp push -0x1`后会继续pushad保存寄存器环境等到要跳到假的OEP时在popad。`push ebpmov ebp,esp push -0x1`此代码也就是被窃取的代码。对应的机器码为:
```
55 8B EC 6A FF
```
```
00485AF2 61 popad
00485AF3 55 push ebp
00485AF4 8BEC mov ebp,esp
00485AF6 6A FF push -0x1
00485AF8 90 nop
00485AF9 60 pushad
00485AFA 60 pushad
00485AFB E8 00000000 call 00485B00 ; UnPackMe.00485B00
00485B00 5E pop esi
00485B01 83EE 06 sub esi,0x6
00485B04 B9 35000000 mov ecx,0x35
00485B09 29CE sub esi,ecx
00485B0B BA FDD5D6E3 mov edx,0xE3D6D5FD
00485B10 C1E9 02 shr ecx,0x2
00485B13 83E9 02 sub ecx,0x2
```
有以上代码我们还可以知道:
1.在从外壳的EP到原程序的OEP过程中可能不止一次pushad,popad 只要保证堆栈平衡就行。
2.stolen bytes的入口点代码不一定要紧挨着假的OEP,有可能中间会执行一些垃圾指令。
我们接着运行程序来到假的OEP处,将被窃取的指令的机器码粘贴到假的OEP上方。最后得到真正的OEP的RVA为:0x271B0
## DUMP程序
利用LordPE工具对正在调试的程序进行完整转存。
## 重建输入表
我们首先查找程序的IAT在哪里。我们在入口点并没有直接看到API的调用指令,但是我们发现可以指令。
```
004271D6 FF15 DC0A4600 call Xdword ptr ds:
```
我们在运行来到0x460ADC处,然后发现此处实际是进行API调用。我们得出程序的IAT被加密,IAT地址被替换为壳代码自己的地址,然后让壳代码来调用API。
我们这里可以利用OD脚本将地址写回到原处,还可以逆向壳代码的IAT重定向逻辑,通过修改关键跳转来达到跳过IAT重定向的目的。
如果采用OD脚本,则脚本代码如下:
```
start:
mov dwAddress1,0460974
word1:
mov dwAddress2,,4
mov dwNum1,,4
mov dwNum2,,4
xor dwNum1,dwNum2
mov ,dwNum1
add dwAddress1,4
cmp dwAddress1,0460BA0
jbe word1
mov dwAddress1,0460BFC
word2:
mov dwAddress2,,4
mov dwNum1,,4
mov dwNum2,,4
xor dwNum1,dwNum2
mov ,dwNum1
add dwAddress1,4
cmp dwAddress1,0460E84
jbe word2
```
如果采用逆向IAT重定向逻辑的方法,我们需要重新加载程序。因为由上面分析可得IAT地址0x460ADC的内容会被重定向,所以我们对此处下内存写入断点。运行程序我们可以来到壳代码的IAT加密处,代码主要逻辑如下。
```
004742A7 8985 20404000 mov dword ptr ss:,eax
004742AD C785 24404000 00000000 mov dword ptr ss:,0x0
004742B7 8B95 28404000 mov edx,dword ptr ss: ; edx = 0x00400000
004742BD 8B06 mov eax,dword ptr ds: ; eax = IID的RVA( INT )
004742BF 0BC0 or eax,eax
004742C1 75 07 jnz X004742CA ; if INT不为空
004742C3 90 nop
004742C4 90 nop
004742C5 90 nop
004742C6 90 nop
004742C7 8B46 10 mov eax,dword ptr ds:
004742CA 03C2 add eax,edx ; eax = INT RVA + 0x00400000
004742CC 0385 24404000 add eax,dword ptr ss: ; eax = eax + 4
004742D2 8B18 mov ebx,dword ptr ds:
004742D4 8B7E 10 mov edi,dword ptr ds: ; edi = IID的FirstThunk字段de RVA
004742D7 03FA add edi,edx ; edi = edi + 0x00400000
004742D9 03BD 24404000 add edi,dword ptr ss: ; edi = edi + 4
004742DF 85DB test ebx,ebx
004742E1 0F84 FA000000 je 004743E1
004742E7 F7C3 00000080 test ebx,0x80000000
004742ED 75 1D jnz X0047430C
004742EF 90 nop
004742F0 90 nop
004742F1 90 nop
004742F2 90 nop
004742F3 03DA add ebx,edx ; ebx = ebx + 0x00400000
004742F5 83C3 02 add ebx,0x2 ; ebx = ebx + 2
004742F8 56 push esi
004742F9 57 push edi
004742FA 50 push eax
004742FB 8BF3 mov esi,ebx ; 得到API的名称字符串
004742FD 8BFB mov edi,ebx
004742FF AC lods byte ptr ds:
00474300 C0C0 03 rol al,0x3
00474303 AA stos byte ptr es:
00474304 803F 00 cmp byte ptr ds:,0x0
00474307^ 75 F6 jnz X004742FF
00474309 58 pop eax
0047430A 5F pop edi
0047430B 5E pop esi
0047430C 81E3 FFFFFF0F and ebx,0xFFFFFFF
00474312 53 push ebx
00474313 FFB5 20404000 push dword ptr ss:
00474319 FF95 68C24100 call Xdword ptr ss: ; GetProcAddress( )
0047431F 3B9D 28404000 cmp ebx,dword ptr ss:
00474325 7C 0F jl X00474336
00474327 90 nop
00474328 90 nop
00474329 90 nop
0047432A 90 nop
0047432B 60 pushad
0047432C 2BC0 sub eax,eax
0047432E 8803 mov byte ptr ds:,al ; 清除,破坏INT。使其为空
00474330 43 inc ebx
00474331 3803 cmp byte ptr ds:,al
00474333^ 75 F9 jnz X0047432E
00474335 61 popad
00474336 0BC0 or eax,eax
00474338^ 0F84 2EFFFFFF je 0047426C ; 如果GetProcAddress( )获得地址失败则跳转
0047433E 3B85 78C24100 cmp eax,dword ptr ss:
00474344 75 0A jnz X00474350
00474346 90 nop
00474347 90 nop
00474348 90 nop
00474349 90 nop
0047434A 8D85 CB454000 lea eax,dword ptr ss:
00474350 56 push esi
00474351 FFB5 20404000 push dword ptr ss: ; 判断DLL的基地址是否为0x7C800000
00474357 5E pop esi ; 0x7C800000地址为kernel32.dll的基地址
00474358 39B5 E9204000 cmp dword ptr ss:,esi ; if (esi == 0x7C800000) 就跳转
0047435E 74 15 je X00474375
00474360 90 nop
00474361 90 nop
00474362 90 nop
00474363 90 nop ; 判断DLL的基地址是否为0x77D10000
00474364 39B5 ED204000 cmp dword ptr ss:,esi ; 0x77D10000地址为USER32.dll的基地址
0047436A 74 09 je X00474375 ; if (esi == 0x77D10000) 就跳转
0047436C 90 nop
0047436D 90 nop
0047436E 90 nop
0047436F 90 nop
00474370 EB 60 jmp X004743D2
00474372 90 nop
00474373 90 nop
00474374 90 nop
00474375 80BD 87A34000 00 cmp byte ptr ss:,0x0 ; if ( ss: == 0)就跳转
0047437C 74 54 je X004743D2
0047437E 90 nop
0047437F 90 nop
00474380 90 nop
00474381 90 nop
00474382 EB 07 jmp X0047438B
00474384 90 nop
00474385 90 nop
00474386 90 nop
00474387 0100 add dword ptr ds:,eax
00474389 0000 add byte ptr ds:,al
0047438B 8BB5 ED404000 mov esi,dword ptr ss:
00474391 83C6 0D add esi,0xD
00474394 81EE C71F4000 sub esi,0x401FC7
0047439A 2BF5 sub esi,ebp
0047439C 83FE 00 cmp esi,0x0
0047439F 7F 31 jg X004743D2 ; UnPackMe.004743D2
004743A1 90 nop
004743A2 90 nop
004743A3 90 nop
004743A4 90 nop
004743A5 8BB5 ED404000 mov esi,dword ptr ss:
004743AB 53 push ebx
004743AC 50 push eax
004743AD 0F31 rdtsc
004743AF 8BD8 mov ebx,eax
004743B1 58 pop eax
004743B2 33C3 xor eax,ebx
004743B4 C606 68 mov byte ptr ds:,0x68
004743B7 8946 01 mov dword ptr ds:,eax
004743BA C746 05 81342400 mov dword ptr ds:,0x243481
004743C1 895E 08 mov dword ptr ds:,ebx
004743C4 C646 0C C3 mov byte ptr ds:,0xC3
004743C8 5B pop ebx
004743C9 8BC6 mov eax,esi
004743CB 8385 ED404000 0D add dword ptr ss:,0xD
004743D2 5E pop esi
004743D3 8907 mov dword ptr ds:,eax ; 修正输入地址表IAT的值
004743D5 8385 24404000 04 add dword ptr ss:,0x4 ; 指向下一个IAT地址项
004743DC^ E9 D6FEFFFF jmp 004742B7
```
由上述壳代码的IAT加密逻辑可得,修改1的je跳转为jmp,或者修改2的je跳转为jmp。从而跳过IAT的重定向
需要注意的是MessageBoxA( )函数被特殊对待,其IAT地址被篡改为了0x46E5C8,我们需要手动帮其更改。
```
0047433E 3B85 78C24100 cmp eax,dword ptr ss: ; if (eax == MessageBoxA)
00474344 75 0A jnz X00474350 ; 不跳转
00474346 90 nop
00474347 90 nop
00474348 90 nop
00474349 90 nop
0047434A 8D85 CB454000 lea eax,dword ptr ss: ; eax = 0x46E5C8
00474350 56 push esi
```
我们在OD中Ctrl + G搜索MessageBoxA得到其地址为0x77D507EA,找到USER32.dll对应的被篡改的IAT地址,将其更改为0x77D507EA。
然后我们运行程序来到假的OEP处,我们可以看到IAT已经修复完成。
然后我们用工具ImportRE来重建输入表,输入OEP的RVA为0x271B0,IAT的RVA为0x60818.获取输入表并截切掉无效的数据,最终得到IAT正确的数据。
我们修复前面dump的文件后,运行修复后的文件结果程序崩溃了。我们用OD打开程序单步来到崩溃处,发现崩溃在一处跳转处,此跳转调到0x17XXXX地址处。
而程序并无0x17XXXX区块,说明此地址应该是壳动态申请的,而将壳脱去后此动态地址的区段没有被dump。以此达到反dump的目的。
### 解决Anti dump
指令字节数相同。所以我们可以用这几个需要执行的指令的自己码替代jmp跳转。(如果这里待执行指令字节数大于jmp指令,我们就需要另行自己寻找空间来存放这些指令)
这里我们用OD脚本来完成这项操作,脚本代码如下非常简单。
```
start:
mov s,0167d58
mov d,046c0f5
mov c,016862d
word:
mov ss,,1
mov ,ss,1
inc d
inc s
cmp s,c
jbe word
ret
```
运行脚本之后jmp指令就会被替换为待执行指令然后我们在dump,之后在修复dump。
最后我们运行修复的程序,运行成功。
# 总结
***
此壳主要还是在解决Anti dump和stolen bytes上。其中还要注意对壳代码MessageBox的特殊对待,如果没有去逆向壳的IAT加密逻辑而直接将函数地址粘贴到IAT处就会忽略MessageBox的修复。 米粒米粒 发表于 2020-8-26 03:02
是这个论坛的爱盘吗
就是那个论坛的爱盘,实在没有也可以直接百度 请问楼主图中的工具在哪里下载 米粒米粒 发表于 2020-8-26 02:58
请问楼主图中的工具在哪里下载
爱盘,看雪工具箱都可以 镇北看雪 发表于 2020-8-26 03:00
爱盘,看雪工具箱都可以
是这个论坛的爱盘吗 感谢分享! I just want to know WTF 脱壳是我感觉最难的破解手段,太难跟了 是这个论坛的爱盘吗
727426900 发表于 2020-8-26 11:00
是这个论坛的爱盘吗
就是论坛的爱盘
页:
[1]
2