首页
社区
课程
招聘
Themida虚拟机简单介绍
发表于: 2006-5-23 22:46 30185

Themida虚拟机简单介绍

2006-5-23 22:46
30185

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余处。留给有坚强意志的朋友完成吧。


[注意]APP应用上架合规检测服务,协助应用顺利上架!

收藏
免费 7
支持
分享
最新回复 (45)
雪    币: 98847
活跃值: (201074)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
2
学习.
2006-5-23 22:49
0
雪    币: 319
活跃值: (2459)
能力值: ( LV12,RANK:980 )
在线值:
发帖
回帖
粉丝
3
学不了。只有顶的份。
2006-5-23 22:52
0
雪    币: 817
活跃值: (1927)
能力值: ( LV12,RANK:2670 )
在线值:
发帖
回帖
粉丝
4
学习了,我也只能看....实际操作不来
2006-5-23 22:52
0
雪    币: 898
活跃值: (4039)
能力值: ( LV9,RANK:3410 )
在线值:
发帖
回帖
粉丝
5
不懂  
学习
2006-5-23 22:53
0
雪    币: 442
活跃值: (1241)
能力值: ( LV12,RANK:1130 )
在线值:
发帖
回帖
粉丝
6
不懂,收藏
2006-5-23 23:03
0
雪    币: 213
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
学习不了
支持一下
2006-5-24 01:06
0
雪    币: 215
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
完全不懂,学习!
2006-5-24 07:21
0
雪    币: 50161
活跃值: (20670)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
9
谢谢softworm与大家分享这知识,现在对Themida虚拟机有些概念了。
2006-5-24 08:11
0
雪    币: 279
活跃值: (145)
能力值: ( LV9,RANK:290 )
在线值:
发帖
回帖
粉丝
10
牛人!学习
2006-5-24 08:25
0
雪    币: 398
活跃值: (343)
能力值: (RANK:650 )
在线值:
发帖
回帖
粉丝
11
softworm  
2006-5-24 08:54
0
雪    币: 254
活跃值: (126)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
12
一个好消息,一个坏消息
2006-5-24 09:00
0
雪    币: 440
活跃值: (832)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
13

谢谢分享。
2006-5-24 09:32
0
雪    币: 234
活跃值: (370)
能力值: ( LV9,RANK:530 )
在线值:
发帖
回帖
粉丝
14
完全不懂,只能膜拜!
2006-5-24 10:23
0
雪    币: 212
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
softworm 名副其实
2006-5-24 11:59
0
雪    币: 184
活跃值: (108)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝
16
学习,有没有人跟上呀?
2006-5-24 16:43
0
雪    币: 254
活跃值: (126)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
17
最初由 鹅蛋壳 发布
softworm 名副其实


难道是传说中的 软体蛀虫 ?
2006-5-24 20:02
0
雪    币: 494
活跃值: (629)
能力值: ( LV9,RANK:1210 )
在线值:
发帖
回帖
粉丝
18
我刚入行的时候,倒是看过俞煌男和软蛀的文章
2006-5-24 20:52
0
雪    币: 50
活跃值: (145)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
19
跟着凑热闹,不懂
2006-5-24 23:23
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
老大,偶还没到那水平,看不懂啊,还要继续努力
2006-5-25 09:54
0
雪    币: 205
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
学着人家顶
2006-5-25 15:52
0
雪    币: 61
活跃值: (160)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
22
谢谢分享。
2006-5-25 19:32
0
雪    币: 494
活跃值: (629)
能力值: ( LV9,RANK:1210 )
在线值:
发帖
回帖
粉丝
23
非常抱歉,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行,真正有点疯狂呵呵。
2006-5-28 17:03
0
雪    币: 538
活跃值: (32)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
除了觉得在云里.还是在云里啊...
2006-5-28 18:20
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
25
都是坏消息了
疯狂的random+expand=xtreme?
现在直接dump混淆代码为什么会出错,有检测么?
2006-5-29 12:34
0
游客
登录 | 注册 方可回帖
返回
//