首页
论坛
专栏
课程

[虚拟机保护] Themida虚拟机简单介绍

2006-5-23 22:46 26417

[虚拟机保护] Themida虚拟机简单介绍

2006-5-23 22:46
26417
Themida虚拟机简单介绍

之所以写这篇东西,是我打算放弃跟v1.5.0.0的VM代码了:-(,另外,有些东西已经忘了,
所以下面的描述可能有错,也不完整。

1. Themida v1.1.1.0虚拟机代码的简单介绍

   v1.1.1.0的VM代码由多个动态分配的内存块组成,除代码外,有一个全局的
   数据结构,可称之为VM_CONTEXT,所有的虚拟机代码都要使用这个结构,v1.5.0.0
   的数据结构没有改变.

   结构中最重要的是+34处的dword,代表VM的register。

   代码通过push/jmp指令进入VM,格式为:

   push xxxxxxx
   jmp  xxxxxxx
   push xxxxxxx
   jmp  xxxxxxx
   ...
   push xxxxxxx
   jmp  xxxxxxx
   dd   xxxxxxx
   dd   xxxxxxx
   ...
   dd   FFFFFFFF

   最后的jmp下面为VM相关数据,第1个dword加delta offset为pcode数据起始地址,
   第2个dword为数据size。下面每3个dword为1组,对应一块独立的pcode数据,也对应
   上面的1个push/jmp指令对。以FFFFFFFF结束。

   原始代码按call分块,即原来代码内的call指令为 push/jmp指令对数 - 1。估计是
   因为call需要脱离VM,执行完后重新进入VM。

   
   每句PCODE包含如下数据(解码用到4个key):

   flag_zero_key;        // 是否清零4个key
   flag_to_init_key1;        // 取值0 - 3,决定如何初始变换key1
   num_of_rotate;        // vm_context内保存有进入VM时的寄存器(执行过程中会改变),
                           这些寄存器视为环状,会随机顺序滚动,这样同样地址的数据
                           可能代表不同的reg32。这个值决定滚动多少字节

   opcode;                // opcode类型,用key1解码
   opcode_flag;                // opcode的附加信息,用key1解码
   argument;                // pcode的dword参数,用key1解码
   next_pcode;                // 下一条pcode数据地址,用key3解码
   index1;                // 用key4解码
   index2;                // 用key2解码,这2个index合起来用于查表,搜索下一条pcode
                           解释函数地址

   解释函数的动作包括:初始化各key,解码上述数据,如果需要滚动context内寄存器,解释
   执行pcode。计算下一条pcode地址(保存在全局数据结构+0处)及其handler地址,然后
   jmp esi执行下一句pcode。

   以类似下面的指令退出VM:

   push dword ptr ds:[edi+94]
   push dword ptr ds:[edi+9C]
   mov dword ptr ds:[edi+28],0
   popad
   popfd
   retn

   用解码程序可以得到类似下面的pcode,其中第1列为pcode地址,第2列为解释函数地址,
   register为vm_context内的register,即VM自己的register。VM使用自己的栈,push都
   是压入vm栈。

   00A186CE:011D80CA        mov        register,addr_of_context.ebp  
   00A186EA:011D0C5D        push        dword ptr [register]   
   00A18706:011DA3DB        mov        register,context.esp   
   00A18722:011F0000        sub        register,00000004   
   00A186F8:011D3F6E        rep        movsb [register],[esp] (04 bytes)   
   00A186DC:011D18AF        push        register   
   00A18714:011D744B        rep        movsb context.esp,[esp] (04 bytes)   
   00A18730:0123157F        mov        context.ebp,context.esp   
   00A1873E:01240B8E        mov        register,0D70178C                   ; nop
   00A1874C:012352F6        add        context.esp,FFFFFFE0

   等价的真实代码:
   push ebp
   mov  ebp,esp
   add  esp,FFFFFFE0

   当然,也可以考虑自己定义一套pcode,把得到的vm pcode转换为自己的pcode(或直接跟
   Themida自己的编码),然后把上面的结果转换为真正的汇编码。我是自己读出来的;-)

   加壳时,对VM代码做了随机化处理,pcode数据的各个field摆放的顺序是变化的,对应的
   handler执行的操作顺序也在变化,虽然干的事情相同,但处理先后顺序可变。

   这样,修复VM保护代码时,难以写出通用的解码程序,即使是用同一版本加壳。必须逐个
   分析handler。从跟的结果看,很多handler是相同的,区别只是处理顺序。

2. Themida v1.5.0.0虚拟机代码的变化

   首先一个变化是混淆代码增强了,也许更早的版本就已经这样了。这是分析VM的主要障
   碍。对1110,可以把清理混淆代码后的VM贴回dumped_.exe直接运行,现在就难以做到了。
   基本的指令序列是类似的,但指令序列不连续,分隔指令序列的垃圾代码及其数量也在
   变化,难以识别或用NOP替代。

   我最后是这样做的: 跳着判断指令序列,如果匹配上,将中间的分隔指令全部作为junk
   清除。理由是,既然符合混淆指令序列,即由原来的正常指令expand而来,除匹配指令外,
   其余的应该是插入的垃圾。这样做的结果似乎还凑合,主要的问题是模式之间存在嵌套,
   对1个模式的处理破坏了别的代码。估计最好能判断出嵌套的模式,然后从内向外处理。
   总之费了不少劲,但结果不好。

   VM这次在1个连续的内存块内。本身的结构并无多大变化,pcode数据各field的含义,各
   key的使用都和原来一样。主要的区别有2处:

   pcode数据各field的offset固定了,这个不是很肯定,但看了4个handler都相同。各field
   在pcode数据内的偏移为:

   0x0,                // 是否清零keys,这个byte还参与key1的初始化
   0xC,                // 初始化key1
   0xC,                // 本轮rotate bytes,与上面使用1 byte不同位
   0x1,                // opcode
   0x4,                // opcode附加信息
   0x5,                // dword参数
   0xB,                // 下1条pcode地址
   0x9,                // index1
   0xA,                // index2

   这是好事。坏消息则"相当"肯定。每个handler在解码时使用的常量都不同。如下面一段
   代码,是解码v1.1.1.0使用的:

   void InitKeys(DWORD curr_pcode_data,int nIndex)
   {
        // 初始化4个解码key

        BYTE zero_key        = 0;
        BYTE init_key1        = 0;
       
        ReadProcessData(curr_pcode_data + data[nIndex].offset_zero_key,        &zero_key, 1);
        if(zero_key & 0x80)
        {
                key_1 = 0;       
                key_2 = 0;
                key_3 = 0;
                key_4 = 0;
        }

        ReadProcessData(curr_pcode_data + data[nIndex].offset_how_to_init_key1, &init_key1, 1);

        switch(init_key1 & 0x3)        // 对v1.5.0.0,使用的常数不同
        {
        case 0:
               
                key_1 ^= 0xD7;               
                break;

        case 1:
                key_1 += 0x89;               
                break;

        case 2:
                key_1 ^= zero_key;
                break;

        default:
                key_1 += zero_key;
        }
   }

   对于v1.1.1.0,这个函数是固定的,对1.5.0.0,下面case 0,case 1内的0xD7,0x89是变化的,
   每个handler都不同。不只是key的初始化,opcode,opcode_flag,dword参数,下句pcode地址
   及handler地址,2个index,所有数据的解码,不同handler都使用了不同的常数。看到这个我
   彻底失去了耐心。

   v1.5.0.0主程序用vm保护的代码有40余处。留给有坚强意志的朋友完成吧。

[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (45)
linhanshi 2006-5-23 22:49
2
0
学习.
csjwaman 24 2006-5-23 22:52
3
0
学不了。只有顶的份。
KuNgBiM 66 2006-5-23 22:52
4
0
学习了,我也只能看....实际操作不来
fly 85 2006-5-23 22:53
5
0
不懂  
学习
baby2008 28 2006-5-23 23:03
6
0
不懂,收藏
xuruifengc 2006-5-24 01:06
7
0
学习不了
支持一下
q3 watcher 2006-5-24 07:21
8
0
完全不懂,学习!
kanxue 8 2006-5-24 08:11
9
0
谢谢softworm与大家分享这知识,现在对Themida虚拟机有些概念了。
linex 7 2006-5-24 08:25
10
0
牛人!学习
shoooo 16 2006-5-24 08:54
11
0
softworm  
heXer 3 2006-5-24 09:00
12
0
一个好消息,一个坏消息
winndy 17 2006-5-24 09:32
13
0

谢谢分享。
lnn1123 13 2006-5-24 10:23
14
0
完全不懂,只能膜拜!
鹅蛋壳 2006-5-24 11:59
15
0
softworm 名副其实
hnhuqiong 10 2006-5-24 16:43
16
0
学习,有没有人跟上呀?
heXer 3 2006-5-24 20:02
17
0
最初由 鹅蛋壳 发布
softworm 名副其实


难道是传说中的 软体蛀虫 ?
softworm 30 2006-5-24 20:52
18
0
我刚入行的时候,倒是看过俞煌男和软蛀的文章
zhaoocn 7 2006-5-24 23:23
19
0
跟着凑热闹,不懂
CZCracker 2006-5-25 09:54
20
0
老大,偶还没到那水平,看不懂啊,还要继续努力
coolxiao 2006-5-25 15:52
21
0
学着人家顶
pendan2001 4 2006-5-25 19:32
22
0
谢谢分享。
softworm 30 2006-5-28 17:03
23
0
非常抱歉,1楼的好消息是假的,看过约20解释函数后发现各数据field的
offset仍然是变化的。

v1.1.1.0的pcode指令条数大约为被保护代码的几倍,应该不会超出10倍。
即,若用VM保护的汇编指令若为10行(从oep到call 401644就是10行),pcode
行数应该不会超出100。

v1.5.0.0有变化,如果在401644下断,在dump处trace into,断下后用
ultraedit统计run trace内的jmp esi个数为767。我把原来的程序大
概改了一下,先不考虑pcode的解释,仅跟踪pcode执行路径,还没有到
处理完,已经近千条了。

大致看看,下面的列表,没有显示pcode的行是还没有做。为了清晰一点,
稍微分行了一下。

0001  00BB3EDD:01501392        nop

0002  00BB3EEB:0148771E        mov        register,addr_of_context.ebp

0003  00BB3EF9:0150C5DD        push        dword ptr [register]

0004  00BB3F07:0148771E        mov        register,context.esp

0005  00BB3F15:014C7A77        sub        register,00000004

0006  00BB3F23:014A1AEA        rep        movsb [register],[esp] (04 bytes)

0007  00BB3F31:0150C5DD        push        register

0008  00BB3F3F:014A1AEA        rep        movsb context.esp,[esp] (04 bytes)        ; push ebp

0009  00BB3F4D:01450073        mov        register,0DD6C7B9

0010  00BB3F5B:0148771E        mov        register,addr_of_context.edi

0011  00BB3F69:0150C5DD        push        dword ptr [register]

0012  00BB3F77:0148771E        mov        register,addr_of_context.edx

0013  00BB3F85:0150C5DD        push        dword ptr [register]

0014  00BB3F93:014D5EF3        test        [esp+4],[esp]                                ; test edi,edx

0015  00BB3FA1:01450073        mov        register,01FDCEA1

0016  00BB3FAF:0148771E        mov        register,addr_of_context.eax

0017  00BB3FBD:0150C5DD        push        dword ptr [register]

0018  00BB3FCB:0148771E        mov        register,context.esp

0019  00BB3FD9:014C7A77        sub        register,00000004

0020  00BB3FE7:014A1AEA        rep        movsb [register],[esp] (04 bytes)

0021  00BB3FF5:0150C5DD        push        register

0022  00BB4003:014A1AEA        rep        movsb context.esp,[esp] (04 bytes)        ; push eax

0023  00BB4011:01450073        mov        register,0E111969

0024  00BB401F:0148771E        mov        register,addr_of_context.edx

0025  00BB402D:0150C5DD        push        dword ptr [register]

0026  00BB403B:0150C5DD        push        FFFFFFBA

0027  00BB4049:014D5EF3        test        [esp+4],[esp](字节操作)                        ; test dl,BA

0028  00BB4057:01450073        mov        register,05C35893

0029  00BB4065:0148771E        mov        register,addr_of_context.ebp

0030  00BB4073:0150C5DD        push        dword ptr [register]

0031  00BB4081:0150C5DD        push        00000005

0032  00BB408F:01446674        shr        [esp+4],[esp](双字操作)

0033  00BB409D:014A1AEA        rep        movsb [register],[esp] (04 bytes)        ; shr ebp,5

0034  00BB40AB:01450073        mov        register,0A9C82A3

0035  00BB40B9:0148771E        mov        register,addr_of_context.eax

0036  00BB40C7:0150C5DD        push        dword ptr [register]

0037  00BB40D5:0148771E        mov        register,addr_of_context.edi

0038  00BB40E3:0150C5DD        push        dword ptr [register]

0039  00BB40F1:014FF38F                                                 ; 未实现

0040  00BB40FF:0148771E        mov        register,addr_of_context.eax

0041  00BB410D:014A1AEA        rep        movsb [register],[esp] (04 bytes)

0042  00BB411B:01450073        mov        register,03CDA03D

0043  00BB4129:0148771E        mov        register,addr_of_context.ecx

0044  00BB4137:0150C5DD        push        dword ptr [register]

0045  00BB4145:0148771E        mov        register,addr_of_context.eax

0046  00BB4153:014A1AEA        rep        movsb [register],[esp] (01 bytes)        ; mov eax,ecx

0047  00BB4161:0148771E        mov        register,context.esp

0048  00BB416F:0150C5DD        push        dword ptr [register]

0049  00BB417D:0148771E        mov        register,addr_of_context.eax

0050  00BB418B:014A1AEA        rep        movsb [register],[esp] (04 bytes)        ; mov eax,[esp]

0051  00BB4199:0148771E        mov        register,context.esp

0052  00BB41A7:0152C47F                                                        ; 未实现,可以猜测是add esp,4

0053  00BB41B5:0150C5DD        push        register               

0054  00BB41C3:014A1AEA        rep        movsb context.esp,[esp] (04 bytes)               

0055  00BB41D1:01450073        mov        register,0BA18350

0056  00BB41DF:0148771E        mov        register,addr_of_context.ebp

0057  00BB41ED:0150C5DD        push        dword ptr [register]

0058  00BB41FB:0150C5DD        push        00000005

0059  00BB4209:014F7A55                                                        ; 未实现

0060  00BB4217:014A1AEA        rep        movsb [register],[esp] (04 bytes)

0061  00BB4225:01461D1F

0062  00BB4233:0148771E        mov        register,context.esp

0063  00BB4241:0150C5DD        push        dword ptr [register]

0064  00BB424F:0148771E        mov        register,addr_of_context.ebp

0065  00BB425D:014A1AEA        rep        movsb [register],[esp] (04 bytes)

0066  00BB426B:0148771E        mov        register,context.esp                        ; 可以看到mov ebp,esp在这里

结论是,被转换为pcode方式运行的代码,事先被混淆过了。再用这种手工式
的修复方法,已经不可行了。

1行真正的汇编指令,大约对应10行pcode代码,每条pcode执行时,对应的汇编
指令一般超出2000行,真正有点疯狂呵呵。
Saver 2006-5-28 18:20
24
0
除了觉得在云里.还是在云里啊...
forgot 26 2006-5-29 12:34
25
0
都是坏消息了
疯狂的random+expand=xtreme?
现在直接dump混淆代码为什么会出错,有检测么?
softworm 30 2006-5-29 12:44
26
0
应该是有校验,在主窗口创建事件(记不清了,以前看到过)内,别的地方可能也有。这部分代码是被vm保护的。大概就是所谓的Virtual Machine anti dumper。

dump出的主程序,oep是正常执行的。splash也可显示。
lidou 1 2006-5-30 17:02
27
0
偶的观点,看不懂的东西都是好东西。正如楼主这篇。。。
hongluobo 2006-5-31 12:39
28
0
完全不懂,只能膜拜!
Kernel64 2006-6-3 15:18
29
0
顶! 学习!
leagon 2006-6-3 16:29
30
0
完全不懂,学习
闲云 2006-6-4 20:26
31
0
牛!不懂,不过在学习!!
hhxx26 2006-6-8 22:58
32
0
努力理解
killes 2006-6-9 17:16
33
0
好复杂,一点都不懂,学习。
xiaowei 2006-6-9 23:20
34
0
不懂  不过终于知道谁是softworm了
ccitt 2006-6-10 04:23
35
0
顶,实在太深了。完全没理解。
柳三公子 2006-6-18 20:43
36
0
看不太懂,慢慢学习
FlyingSnow 2006-6-23 10:59
37
0
膜拜下,不懂
花尽尘香 2006-7-1 18:25
38
0
现在这种东西对我来说是太难了!
只有帮你顶一下了!
goodcode 2 2006-7-2 12:50
39
0
单单混淆就够难分析了
破浪 2006-7-3 09:10
40
0
不错,学习学习
dllish 2006-7-3 10:25
41
0
  太深奥了,天书!  膜拜!
cheat 2006-7-19 23:05
42
0
真的搞不懂啊
laojiang 2006-7-24 09:42
43
0
不错,看来下了不少功夫,顶
coolxiao 2006-7-24 15:33
44
0
最初由 lidou 发布
偶的观点,看不懂的东西都是好东西。正如楼主这篇。。。


sjcries 2006-7-30 02:03
45
0
上帝啊 看完了 不懂... 学习学习!
bloodt 2006-9-30 00:04
46
0
完全不懂 只能先顶了
游客
登录 | 注册 方可回帖
返回