http://hi.baidu.com/ximo2006/blog/item/144661313b85224cac4b5f60.html
VMP加壳的选项中,有个内存效验选项,默认是勾上的,于是,默认的加壳后的程序,只要修改1个字节,程序就会报错。比如,我把加了VMP2.06后的记事本程序,用Hexworkshop打开,把最后一个字节,修改成90,如图:
然后保存后,打开程序,就会报如下错:
下面就来分析下,如何过这个效验。
VMP中,进行效验,都是由handler VM_CRC来进行的,大致的过程可以描述如下:
if(VM_CRC(dwCheckStartAddr,dwLen)==Orien_hash)
{
//继续执行
}
else
{
MessageBox("File corrupted!");
}
于是,过这个效验的方法就是:
法1.把修改后文件的效验值patch成原始的,也就是未修改程序的值
法2,强行修改跳转
下面先来看下VM_CRC:
Address Thread Command ; Registers and comments
010520C7 Main setne dl ; EDX=00840101
010520CA Main bsf cx,di ; ECX=01050003
010520CE Main mov edx,dword ptr ss:[ebp] ; EDX=0084013C //取效验的开始地址
010520D1 Main pushfd
010520D2 Main push esp
010520D3 Main inc ch ; ECX=01050103
010520D5 Main cmp sp,bx
010520D8 Main add ebp,4 ; EBP=0006F794
010520DB Main ror ch,cl ; ECX=01052003
010520DD Main ror ch,cl ; ECX=01050403
010520DF Main rcl cx,3 ; ECX=01052018
010520E3 Main add ch,86 ; ECX=0105A618
010520E6 Main sub eax,eax ; EAX=00000000
010520E8 Main add esp,8
010520EB Main bt esp,0D
010520EF Main mov ecx,eax ; ECX=00000000
010520F1 Main push ecx
010520F2 Main bt cx,8
010520F7 Main jmp NOTEPAD_.010513F9
010513F9 Main shl eax,7
010513FC Main mov byte ptr ss:[esp],0AA
01051400 Main shr ecx,19
01051403 Main stc
01051404 Main call NOTEPAD_.01052238
01052238 Main stc
01052239 Main cmp bl,al
0105223B Main or eax,ecx
0105223D Main call NOTEPAD_.010524E9
010524E9 Main mov word ptr ss:[esp+4],si
010524EE Main stc
010524EF Main jmp NOTEPAD_.01050828
01050828 Main xor al,byte ptr ds:[edx] ; EAX=00000002 //计算
0105082A Main pushad
0105082B Main inc edx ; EDX=0084013D
0105082C Main jmp NOTEPAD_.010513F0
010513F0 Main mov byte ptr ss:[esp],23
010513F4 Main jmp NOTEPAD_.010519AB
010519AB Main pushfd
010519AC Main dec dword ptr ss:[ebp] //长度递减
010519AF Main jmp NOTEPAD_.0104FFB1
0104FFB1 Main mov byte ptr ss:[esp],0CE
0104FFB5 Main push dword ptr ss:[esp]
0104FFB8 Main call NOTEPAD_.01050DF8
01050DF8 Main lea esp,dword ptr ss:[esp+38]
01050DFC Main jnz NOTEPAD_.010520EB
......
01050E02 Main pushfd
01050E03 Main mov dword ptr ss:[ebp],eax //保存计算后的hash值
01050E06 Main pushfd
01050E07 Main push dword ptr ss:[esp+4]
01050E0B Main mov byte ptr ss:[esp+8],bl
01050E0F Main lea esp,dword ptr ss:[esp+C]
01050E13 Main jmp NOTEPAD_.01052455
01052455 Main jl NOTEPAD_.010512B1
从这条handler里,可以得到如下关键的信息:
1.效验的起始地址:CheckStartAddr
2.效验的长度:Len
3.计算后的效验值:CrcValue
说明:对于VM_CRC这条handler的获取,其实很简单,脱过VMP壳的应该都遇到过:
在VirtualProtectEx下好断,F9运行,一直到程序解码,然后在.text段下F2断点,F9运行,往往都会中断在xor al,byte ptr ds:[edx] 指令处,这就是VM_CRC了。接着就向上早这条handler的头就行了,小技巧就是:在OD中,右键---查找---常量,一步一步往上递归查找,就到handler的头了。
下面,写个脚本,来记录下VMP外壳到底在效验哪些地方,脚本如下:
var addr
var hash
var len
var logfile
var info
var end
var ref
var GetAddr
var GetLen
var GetHash
var stop
mov logfile,"log.txt"
bphwcall
bpmc
bc
///配置,根据程序自行修改
mov GetAddr,010520ce //获取效验开始地址
mov GetLen,010519ac //获取长度
mov GetHash,01050e02 //获取计算后的效验值
mov stop,0100739d //设置个脚本停止地址
bphws stop,"x"
bphws GetAddr,"x"
bphws GetHash,"x"
loop:
run
cmp eip,stop
je Exit
mov addr,[ebp]
bphws GetLen,"x"
run
mov len,[ebp]
bphwc GetLen
run
mov hash,eax
mov ref,dx
info:
mov end,addr
add end,len
eval "Check Addr:{addr}----{end} VM_CRC:{hash} ref:{ref}"
mov info,$RESULT
wrta logfile,info
jmp loop
Exit:
ret
记录后的效果如下:
Check Addr:84013C----840178 VM_CRC:6BE8626 ref:178
Check Addr:840180----8513B6 VM_CRC:4FAEC4C8 ref:13B6
Check Addr:8513E6----86EA00 VM_CRC:FCD461CC ref:EA00
Check Addr:840000----840138 VM_CRC:A2031BDE ref:138
Check Addr:104EB50----104EDB4 VM_CRC:9EEE9611 ref:EDB4
Check Addr:103B264----103B280 VM_CRC:44A10069 ref:B280
Check Addr:10523DE----105261F VM_CRC:9571C843 ref:261F
Check Addr:100013C----1000178 VM_CRC:6BE8626 ref:178
Check Addr:1036129----1036A63 VM_CRC:CBEF3424 ref:6A63
Check Addr:102C904----102D74C VM_CRC:12396730 ref:D74C
Check Addr:103B200----103B20C VM_CRC:94966AA1 ref:B20C
Check Addr:103B250----103B25C VM_CRC:98CB8F21 ref:B25C
Check Addr:103B283----104EB50 VM_CRC:5065D528 ref:EB50
Check Addr:103A2F6----103B194 VM_CRC:EFC06876 ref:B194
Check Addr:103B1D8----103B1E4 VM_CRC:92B333A1 ref:B1E4
Check Addr:103B23C----103B248 VM_CRC:97FB8F21 ref:B248
Check Addr:1000118----1000138 VM_CRC:49C882 ref:138
Check Addr:105138F----1051697 VM_CRC:D5374579 ref:1697
Check Addr:103B214----103B220 VM_CRC:159E05A1 ref:B220
Check Addr:1051FCB----10523C7 VM_CRC:F5C3B314 ref:23C7
Check Addr:1051036----105138B VM_CRC:AD5AAA1A ref:138B
Check Addr:102D750----102DDFF VM_CRC:3EAD397E ref:DDFF
Check Addr:10507A8----1050DA2 VM_CRC:FC7A3CF4 ref:DA2
Check Addr:103550E----1036125 VM_CRC:C1206AA9 ref:6125
Check Addr:104EF5A----1050790 VM_CRC:22A05033 ref:790
Check Addr:103B228----103B234 VM_CRC:16F1C6E2 ref:B234
Check Addr:103B1EC----103B1F8 VM_CRC:93BAC921 ref:B1F8
Check Addr:103A2B6----103A2F2 VM_CRC:56D8AA3B ref:A2F2
Check Addr:1050DA6----1051032 VM_CRC:D7A929AE ref:1032
Check Addr:103B1B0----103B1BC VM_CRC:914D2621 ref:B1BC
Check Addr:1050794----10507A4 VM_CRC:7AC87546 ref:7A4
Check Addr:105169B----1051FA5 VM_CRC:89B75DFD ref:1FA5
....下面略
下面来分析下这份日志:
由于我修改的文件是文件最后个字节,offset=2E9FF,转化成RVA=2E9FF,日志的前几行地址为84XXXX,显然,这个地址是内存中新申请的,下个bp MapViewOfFile看看,返回后可知,eax=00840000,于是,修改的地址,在内存映射中的地址=lpBase+RVA=00840000+2E9FF=0086E9FF
然后看日志的第三行:
Check Addr:8513E6----86EA00 VM_CRC:FCD461CC ref:EA00
发现,这行记录的信息正好是效验了我们修改的地方。于是,我们只要手动patch这个效验值,就能使程序正常的运行起来。但是如何要使保存后的程序也能正常运行呢?于是得找个地方,去放patch的代码。
最好的思路就是找个没在上面日志中的效验的范围内的handler内,但是这个值往往不好找,省事点,直接在CRC计算的出口处进行patch,这个地址:
01050E02 9C pushfd //这里patch
01050E03 8945 00 mov dword ptr ss:[ebp],eax
01050E06 9C pushfd
01050E07 FF7424 04 push dword ptr ss:[esp+4]
01050E0B 885C24 08 mov byte ptr ss:[esp+8],bl
01050E0F 8D6424 0C lea esp,dword ptr ss:[esp+C]
但是,这个地址也正好存在在效验的范围内,看日志:
Check Addr:1050DA6----1051032 VM_CRC:D7A929AE ref:1032
01050E02正好在这个效验的范围内,于是,这次效验值,我们也得进行patch。
当然,VMP并未对程序的空白区进行效验,于是,可以在程序最后的空数据区,随便patch。
如何patch呢?这个就自由发挥,我也不知道如何patch最佳,提供一种自己的挫patch代码:(说明下,脚本中记录的ref值,是为了进行patch对照用的,也就是一个参照数)
01050E02 /E9 A6180000 jmp 修改.010526AD //放个跳转去进行patch
01050E07 |FF7424 04 push dword ptr ss:[esp+4]
01050E0B |885C24 08 mov byte ptr ss:[esp+8],bl
01050E0F |8D6424 0C lea esp,dword ptr ss:[esp+C]
01050E13 |E9 3D160000 jmp 修改3.01052455
01050E18 |8B4C24 4C mov ecx,dword ptr ss:[esp+4C]
010526AD 66:81FA 00EA cmp dx,0EA00
010526B2 75 07 jnz short 修改.010526BB
010526B4 B8 CC61D4FC mov eax,FCD461CC
010526B9 EB 0C jmp short 修改.010526C7
010526BB 66:81FA 3210 cmp dx,1032
010526C0 75 05 jnz short 修改.010526C7
010526C2 B8 AE29A9D7 mov eax,D7A929AE
010526C7 9C pushfd
010526C8 8945 00 mov dword ptr ss:[ebp],eax
010526CB 9C pushfd
010526CC ^ E9 36E7FFFF jmp 修改.01050E07
如此patch后,保存文件,即可正常运行。
当然,你也可以修改跳转的地方,来到过效验的目的。方法就是HOOK VM_Add32,0和4互换,这个上次的文章中提过了,就不赘述了。
附件为试练品,patch后的,以及脚本跟日志