首先说一说VMP1.8,当然大家认为壳很老了,直接跑脚本就可以了,但本人并不是这麽认为,本人可能比较执拗的人,一定要知其然,知其所以然(所以学得慢吧,也许,呵呵
。)
看了kissy的壳世界,对VMP有一定的认识了,也在网上找了一些有关VMP1.8的文章来看了。搜到UPK,一阵暗喜,UPK有一些关於VMP的文章,可惜,我注册后过几天就关了,令人唏嘘不已。
不说了,来正题,再者PYG论坛里好像没多少人写有关壳的文章,小弟不才,但对壳有兴趣,虽然破解能力一般,但本着学习的态度,把文章写出来供大家一起研究。
有错误请大家指正。
实验程序用DELPHI生成的一个默认窗体,在保护选项里只选Import protection,即只保护输入表.
经过研究和资料,VMP会先将DLL的导出表遍成进行分析,找到函数后把差值写到一个地址里,调用时再将值取出来,加上一个差值,然後就得到实际的API地址,这里VMP1.8的处理好厉害,会把e8型调用API的CALL
也用VM函数处理了,当然FF 25 型的也处理掉,所以在Nooby牛的脚本里把差值算出来,用DLL调用,运行时把差值读出来,达到跨平台的目的。(跟VM累的很,跳转多,我就不截图了,大家看看代码分析吧),
花指令比较多,有用的指令我注释出来了)
首先,来看看找导入表的地方(以Kernel32.dll为例)(代码较多,不太好看,请大家见谅):
004A17EE 8B70 3C MOV ESI, DWORD PTR DS:[EAX+0x3C]
; PE标识偏移
004A17F1 F6D6 NOT DH
004A17F3 66:0FADF2 SHRD DX, SI, CL
004A17F7 30DA XOR DL, BL
004A17F9 01C6 ADD ESI, EAX
; 基址+偏移定位到PE头VA
004A17FB 0FBED1 MOVSX EDX, CL
004A17FE D3E2 SHL EDX, CL
004A1800 8B56 78 MOV EDX, DWORD PTR DS:[ESI+0x78]
; 导出表RVA
004A1803 9C PUSHFD
004A1804 F6C4 B7 TEST AH, 0xB7
004A1807 84D1 TEST CL, DL
004A1809 66:0FBAE3 06 BT BX, 0x6
004A180E 85D2 TEST EDX, EDX
; 导出表RVA是否为0
004A1810 52 PUSH EDX
004A1811 8D6424 28 LEA ESP, DWORD PTR SS:[ESP+0x28]
004A1815 0F84 CB030000 JE delphi7?004A1BE6
; 为0则跳走
004A181B 66:F7D1 NOT CX
004A181E 30D1 XOR CL, DL
004A1820 F9 STC
004A1821 01C2 ADD EDX, EAX
; 导出表VA
004A1823 68 FFF78DF6 PUSH 0xF68DF7FF
004A1828 8B4E 7C MOV ECX, DWORD PTR DS:[ESI+0x7C]
; 导出表大小
004A182B F9 STC
004A182C 80FA 79 CMP DL, 0x79
004A182F E9 A8570000 JMP delphi7?004A6FDC
接着:
004A00BF 01D1 ADD ECX, EDX
; 导出表最后的位置
004A00C1 ^ E9 5A9DFFFF JMP delphi7?00499E20
00499E20 F9 STC
00499E21 894C24 0C MOV DWORD PTR SS:[ESP+0xC], ECX
; 导入表最后的位置
00499E25 66:0FA3F7 BT DI, SI
00499E29 F5 CMC
00499E2A 66:0FC9 BSWAP CX
00499E2D 8B4D 04 MOV ECX, DWORD PTR SS:[EBP+0x4]
; API字符串
00499E30 84E0 TEST AL, AH
00499E32 0FA3DE BT ESI, EBX
00499E35 81F9 FFFF0000 CMP ECX, 0xFFFF
00499E3B 9C PUSHFD
00499E3C 50 PUSH EAX
00499E3D 8D6424 0C LEA ESP, DWORD PTR SS:[ESP+0xC]
00499E41 0F86 CABF0000 JBE delphi7?004A5E11
00499E47 66:0FA4C1 07 SHLD CX, AX, 0x7
00499E4C 8B7A 24 MOV EDI, DWORD PTR DS:[EDX+0x24]
; AddreesOfNumberOrdinal的RVA
00499E4F D0DB RCR BL, 1
00499E51 0FBCDA BSF EBX, EDX
00499E54 66:87D9 XCHG CX, BX
00499E57 01C7 ADD EDI, EAX
; AddressOfNumberOrdinal的VA
00499E59 66:0FADF1 SHRD CX, SI, CL
00499E5D 8B5A 20 MOV EBX, DWORD PTR DS:[EDX+0x20]
; AddressOfNames的RVA
00499E60 0FC9 BSWAP ECX
00499E62 66:0FA4D1 0A SHLD CX, DX, 0xA
00499E67 D2C5 ROL CH, CL
00499E69 01C3 ADD EBX, EAX
; AddressOfNames的VA
00499E6B 18D5 SBB CH, DL
00499E6D 0F99C5 SETNS CH
00499E70 FEC1 INC CL
00499E72 C70424 00000000 MOV DWORD PTR SS:[ESP], 0x0
00499E79 66:D3C9 ROR CX, CL
00499E7C 8B4A 18 MOV ECX, DWORD PTR DS:[EDX+0x18]
; NumberOfNames,以名称导出函数数量
00499E7F ^ E9 11D5FFFF JMP delphi7?00497395
//这里说明一下,在遍历时这里是用二分查找法来查找的,提高查找速度。
00497395 /0F8F 0A3B0000 JG delphi7?0049AEA5
0049739B |66:0FBAE5 01 BT BP, 0x1
004973A0 |F8 CLC
004973A1 |85C9 TEST ECX, ECX
; 最大数量是否为0
004973A3 |9C PUSHFD
004973A4 |8D6424 04 LEA ESP, DWORD PTR SS:[ESP+0x4]
004973A8 |0F8A 76D70000 JPE delphi7?004A4B24
004973AE |9C PUSHFD
004973AF |8D6424 04 LEA ESP, DWORD PTR SS:[ESP+0x4]
004973B3 |0F84 2DA80000 JE delphi7?004A1BE6
; 为0则跳走
004973B9 |38FE CMP DH, BH
004973BB |60 PUSHAD
004973BC |9C PUSHFD
004973BD |83E9 01 SUB ECX, 0x1 ; 最大数量减1
004973C0 |9C PUSHFD
004973C1 |38F7 CMP BH, DH
004973C3 |FF7424 08 PUSH DWORD PTR SS:[ESP+0x8]
004973C7 |C64424 04 01 MOV BYTE PTR SS:[ESP+0x4], 0x1
004973CC |894C24 30 MOV DWORD PTR SS:[ESP+0x30], ECX
; 将新的最大数量存到栈
004973D0 |83C4 2C ADD ESP, 0x2C
004973D3 |66:0FBDCA BSR CX, DX
004973D7 |8B0C24 MOV ECX, DWORD PTR SS:[ESP]
; 二分结果
004973DA |F7C6 A9869ACD TEST ESI, 0xCD9A86A9
004973E0 |84D4 TEST AH, DL
004973E2 |F8 CLC
004973E3 |38C7 CMP BH, AL
004973E5 |3B4C24 04 CMP ECX, DWORD PTR SS:[ESP+0x4]
; 二分结果与最大数量比较,这个会随着计算改变
004973E9 |E9 B4790000 JMP delphi7?0049EDA2
004A2371 ^\0F87 6FF8FFFF JA delphi7?004A1BE6
; 大于则跳走
0049A081 034C24 08 ADD ECX, DWORD PTR SS:[ESP+0x8]
; 二分结果与这个最大数量相加
0049A085 83EC FC SUB ESP, -0x4
0049A088 0F8E 6E170000 JLE delphi7?0049B7FC
0049A08E 66:FFCE DEC SI
0049A091 66:D3D7 RCL DI, CL
0049A094 66:09DE OR SI, BX
0049A097 D1E9 SHR ECX, 1
; 再除以2,得到新的函数数组下标
0049A099 0FACEF 10 SHRD EDI, EBP, 0x10
0049A09D 21CE AND ESI, ECX
0049A09F 66:0FACC6 0B SHRD SI, AX, 0xB
0049A0A4 8B3C8B MOV EDI, DWORD PTR DS:[EBX+ECX*4]
; 第ecx个NumberOfNames的RVA
0049A0A7 68 B3CED963 PUSH 0x63D9CEB3
0049A0AC 01C7 ADD EDI, EAX
; 第ecx个NumberOfNames的VA
0049A0AE F8 CLC
0049A0AF C1E6 0C SHL ESI, 0xC
0049A0B2 8B75 04 MOV ESI, DWORD PTR SS:[EBP+0x4]
; API字符串
0049A0B5 83EC FC SUB ESP, -0x4
0049A0B8 0FBAE2 0F BT EDX, 0xF
0049A0BC 66:0FA3FE BT SI, DI
0049A0C0 66:0FA3CF BT DI, CX
0049A0C4 60 PUSHAD
0049A0C5 A6 CMPS BYTE PTR DS:[ESI], BYTE PTR ES:[EDI]
; 逐字节比较
0049A0C6 C64424 04 7E MOV BYTE PTR SS:[ESP+0x4], 0x7E
0049A0CB 8D6424 20 LEA ESP, DWORD PTR SS:[ESP+0x20]
0049A0CF ^ 0F87 15E3FFFF JA delphi7?004983EA
; 大于则找后面的
0049A0D5 55 PUSH EBP
; 小于等于则来到这里
0049A0D6 9C PUSHFD
0049A0D7 51 PUSH ECX
0049A0D8 E9 DCD60000 JMP delphi7?004A77B9
首先看看找到的API字符大於目的API字符的地方,大於的话,这个ecx会加1,然後和二分结果相加,再除以2找到新的中值位置。
004983EA F9 STC
004983EB 9C PUSHFD
004983EC 83C1 01 ADD ECX, 0x1
; 二分结果加1
004983EF E9 E66E0000 JMP delphi7?0049F2DA
0049F2DA 894C24 04 MOV DWORD PTR SS:[ESP+0x4], ECX ; 二分结果
0049F2DE 882424 MOV BYTE PTR SS:[ESP], AH
0049F2E1 60 PUSHAD
0049F2E2 60 PUSHAD
0049F2E3 8D6424 44 LEA ESP, DWORD PTR SS:[ESP+0x44]
0049F2E7 ^ E9 E780FFFF JMP delphi7?004973D3
004973D3 66:0FBDCA BSR CX, DX
004973D7 8B0C24 MOV ECX, DWORD PTR SS:[ESP]
; 二分结果
004973DA F7C6 A9869ACD TEST ESI, 0xCD9A86A9
004973E0 84D4 TEST AH, DL
004973E2 F8 CLC
004973E3 38C7 CMP BH, AL
004973E5 3B4C24 04 CMP ECX, DWORD PTR SS:[ESP+0x4]
; 二分结果与最大数量比较,这个会随着计算改变
004973E9 E9 B4790000 JMP delphi7?0049EDA2
004A2371 ^\0F87 6FF8FFFF JA delphi7?004A1BE6
; 大于则跳走
004A2377 66:09DF OR DI, BX
004A237A 66:87F7 XCHG DI, SI
004A237D D3DF RCR EDI, CL
004A237F E8 FD7CFFFF CALL delphi7?0049A081
0049A081 034C24 08 ADD ECX, DWORD PTR SS:[ESP+0x8]
; 二分结果与这个最大数量相加
0049A085 83EC FC SUB ESP, -0x4
0049A088 0F8E 6E170000 JLE delphi7?0049B7FC
0049A08E 66:FFCE DEC SI
0049A091 66:D3D7 RCL DI, CL
0049A094 66:09DE OR SI, BX
0049A097 D1E9 SHR ECX, 1
; 再除以2,得到新的函数数组下标
0049A099 0FACEF 10 SHRD EDI, EBP, 0x10
0049A09D 21CE AND ESI, ECX
0049A09F 66:0FACC6 0B SHRD SI, AX, 0xB
0049A0A4 8B3C8B MOV EDI, DWORD PTR DS:[EBX+ECX*4]
; 第ecx个NumberOfNames的RVA
0049A0A7 68 B3CED963 PUSH 0x63D9CEB3
0049A0AC 01C7 ADD EDI, EAX
; 第ecx个NumberOfNames的VA
0049A0AE F8 CLC
0049A0AF C1E6 0C SHL ESI, 0xC
0049A0B2 8B75 04 MOV ESI, DWORD PTR SS:[EBP+0x4]
; API字符串
0049A0B5 83EC FC SUB ESP, -0x4
0049A0B8 0FBAE2 0F BT EDX, 0xF
0049A0BC 66:0FA3FE BT SI, DI
0049A0C0 66:0FA3CF BT DI, CX
0049A0C4 60 PUSHAD
0049A0C5 A6 CMPS BYTE PTR DS:[ESI], BYTE PTR ES:[EDI]
; 逐字节比较
0049A0C6 C64424 04 7E MOV BYTE PTR SS:[ESP+0x4], 0x7E
0049A0CB 8D6424 20 LEA ESP, DWORD PTR SS:[ESP+0x20]
0049A0CF ^ 0F87 15E3FFFF JA delphi7?004983EA
; 大于则找后面的
小於来到这里:
004973BD 83E9 01 SUB ECX, 0x1
; 最大数量减1
004973C0 9C PUSHFD
004973C1 38F7 CMP BH, DH
004973C3 FF7424 08 PUSH DWORD PTR SS:[ESP+0x8]
004973C7 C64424 04 01 MOV BYTE PTR SS:[ESP+0x4], 0x1
004973CC 894C24 30 MOV DWORD PTR SS:[ESP+0x30], ECX
; 将新的最大数量存到栈
004973D0 83C4 2C ADD ESP, 0x2C
004973D3 66:0FBDCA BSR CX, DX
004973D7 8B0C24 MOV ECX, DWORD PTR SS:[ESP]
; 二分结果
004973DA F7C6 A9869ACD TEST ESI, 0xCD9A86A9
004973E0 84D4 TEST AH, DL
004973E2 F8 CLC
004973E3 38C7 CMP BH, AL
004973E5 3B4C24 04 CMP ECX, DWORD PTR SS:[ESP+0x4]
; 二分结果与最大数量比较,这个会随着计算改变
004973E9 E9 B4790000 JMP delphi7?0049EDA2
这里总结一下,首先,设要找到这个目的API下标为i,初值i=1,导出函数最大数量为t,则找法是从i=(i+t)/2开始,如果找到的字符串是大於(cmps比较),这个下标i,i=i+1,再重新算中值i=(i+t)/2,
如果是小於,则t=i-1,然後,i=(i+t)/2,再重新找,直到函数名完全一样。
然後,到获取API的地址的地方了:
004A2C9D 8B7A 24 MOV EDI, DWORD PTR DS:[EDX+0x24]
; 函数序号表的RVA
004A2CA0 52 PUSH EDX
004A2CA1 896424 04 MOV DWORD PTR SS:[ESP+0x4], ESP
004A2CA5 01C7 ADD EDI, EAX
; 函数序号表的VA
004A2CA7 F5 CMC
004A2CA8 FF7424 04 PUSH DWORD PTR SS:[ESP+0x4]
004A2CAC 66:0FBAE5 03 BT BP, 0x3
004A2CB1 81FD 90BA8BB0 CMP EBP, 0xB08BBA90
004A2CB7 0FB70C4F MOVZX ECX, WORD PTR DS:[EDI+ECX*2]
; AddressOfNameOrdinals[ecx],取出这个序号
004A2CBB 83EC F4 SUB ESP, -0xC
004A2CBE 66:0FA4C7 01 SHLD DI, AX, 0x1
004A2CC3 66:C1DF 02 RCR DI, 0x2
004A2CC7 66:19C7 SBB DI, AX
004A2CCA 0FBAFF 19 BTC EDI, 0x19
004A2CCE 8B7A 1C MOV EDI, DWORD PTR DS:[EDX+0x1C]
; AddressOfFunctions的RVA
004A2CD1 9C PUSHFD
004A2CD2 01C7 ADD EDI, EAX
; AddressOfFunctions的VA
004A2CD4 9C PUSHFD
004A2CD5 9C PUSHFD
004A2CD6 57 PUSH EDI
004A2CD7 ^ E9 0657FFFF JMP delphi7?004983E2
004983E2 8B3C8F MOV EDI, DWORD PTR DS:[EDI+ECX*4]
; AddressOfFunction[ecx],第ecx个函数的地址RVA
004983E5 E8 92890000 CALL delphi7?004A0D7C
004A0D7C F5 CMC
004A0D7D 85FF TEST EDI, EDI
; 得到API的RVA是否为0
004A0D7F 60 PUSHAD
004A0D80 8D6424 34 LEA ESP, DWORD PTR SS:[ESP+0x34]
004A0D84 0F84 5C0E0000 JE delphi7?004A1BE6
004A0D8A 66:85E9 TEST CX, BP
004A0D8D ^ 0F8E FF7FFFFF JLE delphi7?00498D92
004A0D93 84C7 TEST BH, AL
004A0D95 F5 CMC
004A0D96 01F8 ADD EAX, EDI
; API的VA,即真实函数地址
004A0D98 0FA3CE BT ESI, ECX
004A0D9B 39D0 CMP EAX, EDX ; 与输出表VA比较
004A0D9D E9 35190000 JMP delphi7?004A26D7
004A1BF2 87FA XCHG EDX, EDI ; API函数RVA与输出表互换
004A1BF4 66:0FBED1 MOVSX DX, CL
004A1BF8 5A POP EDX
004A1BF9 8D2CDD 45720347 LEA EBP, DWORD PTR DS:[EBX*8+0x47037245]
004A1C00 0F9FC2 SETG DL
004A1C03 88E2 MOV DL, AH
004A1C05 5A POP EDX
004A1C06 68 48AAD205 PUSH 0x5D2AA48
004A1C0B 68 D2A45EA1 PUSH 0xA15EA4D2
004A1C10 60 PUSHAD
004A1C11 F6D2 NOT DL
004A1C13 8B5424 28 MOV EDX, DWORD PTR SS:[ESP+0x28] ; ??
004A1C17 0F9EC2 SETLE DL
004A1C1A 9C PUSHFD
004A1C1B 9C PUSHFD
004A1C1C 0F9FC7 SETG BH
004A1C1F 8B5424 34 MOV EDX, DWORD PTR SS:[ESP+0x34] ; ??
004A1C23 87EB XCHG EBX, EBP
004A1C25 5E POP ESI
004A1C26 8A5C24 04 MOV BL, BYTE PTR SS:[ESP+0x4]
004A1C2A 8B5C24 34 MOV EBX, DWORD PTR SS:[ESP+0x34]
; dll基址
004A1C2E 9C PUSHFD
004A1C2F 8B7C24 3C MOV EDI, DWORD PTR SS:[ESP+0x3C]
; API名称
004A1C33 9C PUSHFD
004A1C34 0FB6F2 MOVZX ESI, DL
004A1C37 8B7424 44 MOV ESI, DWORD PTR SS:[ESP+0x44]
; dll名称
004A1C3B FF7424 08 PUSH DWORD PTR SS:[ESP+0x8]
004A1C3F 8B6C24 4C MOV EBP, DWORD PTR SS:[ESP+0x4C]
004A1C43 883424 MOV BYTE PTR SS:[ESP], DH
004A1C46 66:892C24 MOV WORD PTR SS:[ESP], BP
004A1C4A FF7424 50 PUSH DWORD PTR SS:[ESP+0x50]
004A1C4E C2 5C00 RETN 0x5C
; 返回API CHECK
返回这里:
0049C97A 68 84F15E4E PUSH 0x4E5EF184
; 获取API名称后来到这里,API check,这里会计算一个DWORD值
0049C97F E8 58B40000 CALL delphi7?004A7DDC
获取了API地址後就跳到再次进入虚拟机进行操作,後面是算那个差值,这里进入的虚拟机,我也不能完全看明白,只能找到一些较关键的地方:
首先说明一下我要找的地方
kernel32.FreeResource
DS:[00466C84]=9D732DB0
减法:76D9F879 –
D966CAC9
=9D732DB0
加法:76D9F879+
26993537
=9D732DB0(补码加法)
因为是先知道结果再来找过程,因此不知道他後面用加法还是减法去算,所以先列出来可能的值会出现在什麽位置,上面的值用红色标出
进入虚拟机的函数就不列出来了,跳转大多,发代码也不好说明,进这里一个主要操作是从EBP的内容和EDI这里的内容计算过後会复制来复制去。
当然後面有个最关键的地方:
1.从esi取出DWORD:
004A8DEC 8B06 MOV EAX, DWORD PTR DS:[ESI]
; 解密一个DWORD
004A8DEE 66:0FBAE0 01 BT AX, 0x1
004A8DF3 F5 CMC
004A8DF4 01D8 ADD EAX, EBX
004A8A14 05 00323AFF ADD EAX, 0xFF3A3200
004A8A19 60 PUSHAD
004A8A1A F7D0 NOT EAX
; 取反
004A8A1C 52 PUSH EDX
004A8A1D 66:C74424 10 2B>MOV WORD PTR SS:[ESP+0x10], 0xD02B
004A8A24 40 INC EAX ; 加1
004A8A25 E9 63080000 JMP delphi7?004A928D
004A9518 01C3 ADD EBX, EAX
004A951A 9C PUSHFD
004A951B ^ E9 77E7FFFF JMP delphi7?004A7C97
004A7CA5 8945 00 MOV DWORD PTR SS:[EBP], EAX
; 这个DWORD写进EBP里面如果此时EAX=26993537,正是FREERESOURCE的差值
///後面省略一些,具体记录找不到了,累。。
004A90D8 8B45 00 MOV EAX, DWORD PTR SS:[EBP]
; 这里会出现API地址76D9F879 kernel32.FreeResource
004A90DB F5 CMC
004A90DC 0145 04 ADD DWORD PTR SS:[EBP+0x4], EAX
; 这里[ebp+4]就是那个值26993537,加上API地址,就等於那个差值了
到此,这个结果出来了,几年前大牛已经将VM看透了,再次膜拜一下。