似乎分析cv的东西不多,还好找到了Ryosuke兄弟的一个详细的分析,非常强大的。
http://bbs.pediy.com/showthread.php?t=62447&highlight=Virtualizer
1.3.8.0这版和Ryosuke分析的那版没有太多变化,vm_context中多了一个字段,增加到了0x48,其他的变化都不大,个别dispatch函数有些变化。选择不同的加密强度测试看,似乎在代码混淆方面并没明显加强,也许测试代码少的原因,分析的不到位。当时看的时候有些地方还没看懂,还是动手自己琢磨一下,顺便记录下的笔记,这个分析只是婴儿奶粉,不能补钙,高手略过吧。
先把Code Virtualizer的虚拟机简单说一下,这里把我们自己的程序运行空间叫做宿主吧。cv虚拟机的所有运算的操作都在宿主的堆栈中完成。
cv自己的存放宿主程序的寄存器及解析例程的结构叫vm_context,所有操作的都是由一个叫vm_data数据块来驱动的。这里面的数据会指示cv的虚拟机如何取指令计算出新的数据。
程序运行进入cv虚拟机后,edi的值始终不变,一直指向vm_context(在Ryosuke的里面叫VM_BLOCK都是一个意思)的首地址。
从偏移0 ~ 0x1c 存放的是宿主的寄存器,但每次都会变,这个是按某种顺序的变化的。
esi指向vm_data结构,每执行一个dispatch,esi不断的增加。
eax的用处就是从esi中读取数据,进行运算,获得解析例程的dispatch号码。或者是vm_context的某个相对偏移地址(cv里面为了操作某个结构)
ebx就是参与计算用的,和vm_data中的数据运算,用来获得有效的eax
edx是个临时的变量,cv在宿主堆栈的运算中,常用edx做临时变量来存放数据
ebp,esp 基本没有太大用处,参与一些混淆代码的计算。
struct vm_context
{
DWORD vm_off_00; +0
DWORD vm_off_04; +4
DWORD vm_off_08; +8
DWORD vm_off_0c; +0c
DWORD vm_off_10; +10
DWORD vm_off_14; +14
DWORD vm_off_18; +18
DWORD vm_off_1c; +1c
DWORD vm_off_20;
DWORD VM_off_24;
DWORD VM_ecx_value;
DWORD VM_off_2c;
DWORD VM_busy_flag;
DWORD VM_off_34;
DWORD VM_off_38;
DWORD VM_off_3c;
DWORD VM_off_40;
DWORD VM_off_44;
DWORD VM_off_48;
DWORD vm_context_xxx1;
DWORD VM_dispatch_xxx2;
DWORD VM_dispatch_xxx3;
...
}
下面就测试这么一个例子:
.data
t_buff db 100 dup(090h)
t_txt db "test",0
t_title db "codevirtualizer",0
.code
START:
mov eax,offset t_buff
call VirtualizerStart
mov dword ptr[eax],01234567h ; 既然小窥,先只测试这一条语句吧,对cv的分析可以在里加入不同的语句一点一点分析
call VirtualizerEnd
invoke MessageBox,0,offset t_txt,offset t_title,0
invoke ExitProcess,0
end START
分别在Protection Options中选择这两种个加一次
Virtual Opcodes Obfuscation :Low ,Highest
Virtual Machine Complexity :Low ,Highest
Opcodes Mutation :Low ,Highest
分析后可以发现再混淆方面差别并不是太大。
进入VM之前先保存程序最初的环境
EAX 00403000
ECX 0013FFB0
EDX 7C92E514
EBX 7FFDE000
ESP 0013FFC0
EBP 0013FFF0
ESI 00000068
EDI 0013B790
EIP 004034EC CV_TES~3.004034EC
EFL 00000246 (NO,NB,E,BE,NS,PE,GE,LE)
软后先压入VM_DATA
00407083 68 1A704000 push CV_TES~3.0040701A -----VM_DATA
00407088 ^ E9 5FC4FFFF jmp CV_TES~3.004034EC
VM_DATA 内容每次加壳后都变化
0040701A 3F 1E E1 D0 F8 5F A0 8F B7 A4 5B 4A 72 E4 1B 0A ?嵝鴂爮筏[Jr?.
0040702A 32 2A D5 C4 5F D9 C0 9C 63 52 7A DE 21 10 38 21 2*漳_倮渃Rz?8!
0040703A DE CD F5 65 9A 89 11 74 22 9F 40 20 7A DD 43 EF 尥鮡殙t"烜 z軨
0040704A 45 05 C3 7B DD 74 54 43 A9 25 6B BD 85 1A EF 01 E脅輙TC?k絽?
0040705A 00 00 00 FB 59 04 FB 59 C7 93 6C CA 38 21 DE 3C ...鸜鸜菗l?!?
0040706A AA AE 51 AF 1D 3F C0 1E 8C D0 2F 8D FB 5B A4 02 Q???屝/嶜[?
0040707A 70 EB 14 72 E0 77 88 E6 B8 68 1A 70 40 00 E9 5F p?r鄔堟竓p@.開
0040708A C4 FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 ?.............
通过重定位获得自身数据及处理例程的偏移,设置重定位标志,如果是dll还要重新定位。
004034EF E8 00000000 call CV_TES~3.004034F4 ; 重定位自身
004034F4 5F pop edi
004034F5 81EF F4344000 sub edi,CV_TES~3.004034F4
004034FB 8BC7 mov eax,edi
004034FD 81C7 00324000 add edi,offset <CV_TES~3.vm_dispatch> ;定位edi为vm_context
00403503 3B47 2C cmp eax,dword ptr ds:[edi+2C]
00403556 AC lods byte ptr ds:[esi] ;此处进入虚拟机,知道遇到vm_exit,才退出。
00403557 04 81 add al,81
00403559 04 90 add al,90
0040355B 00D8 add al,bl
0040355D 2C 90 sub al,90
0040355F E9 080A0000 jmp CV_TES~3.00403F6C
1.3.8版的vm_dispatch
00403200 >00000000
00403204 00000000
00403208 00000000
0040320C 00000000
00403210 00000000
00403214 00000000
00403218 00000000
0040321C 00000000 ----->eflag 标志
00403220 00000000
00403224 00000000
00403228 00000000
0040322C 00000000
00403230 00000000 ----->是否是忙的标志
00403234 00000000
00403238 00000000
0040323C 00000000
00403240 00000000
00403244 00000000
00403248 00000000 ------->相比前版本增加的字段
0040324C 0040469C CV_TES~3.0040469C --- > 解析例程
00403250 00403843 CV_TES~3.00403843 --- > 解析例程
00403254 00403B82 CV_TES~3.00403B82 --- > 解析例程
00403258 00403FE9 CV_TES~3.00403FE9 --- > 解析例程
0040325C 00403608 CV_TES~3.00403608 --- > 解析例程
00403260 004037E2 CV_TES~3.004037E2 --- > 解析例程
00403264 00403723 CV_TES~3.00403723 --- > 解析例程
00403268 004069F1 CV_TES~3.004069F1 --- > 解析例程
0040326C 00406241 CV_TES~3.00406241 --- > 解析例程
00403270 004064F3 CV_TES~3.004064F3 --- > 解析例程
00403274 00405C54 CV_TES~3.00405C54 --- > 解析例程
...
先开始分析进入虚拟机情况第一次情况:
EAX 0000003F
ECX 00000001
EDX 7C92E514 ntdll.KiFastSystemCallRet
EBX 0040701A CV_TES~3.0040701A -----> vm_data[0] 数据
ESP 0013FF9C
EBP 0013FFF0
ESI 0040701B CV_TES~3.0040701B -----> vm_data[n+1] 以后esi依次递增
EDI 00403200 offset <CV_TES~3.vm_context>
EIP 00403557 CV_TES~3.00403557
EFL 00000246 (NO,NB,E,BE,NS,PE,GE,LE)
//进入运算前,就从VM_DATA中读取的al->0x3f
//其实运算的内容都是为了混淆变乱而已,为了做到自动的去除这些指令,还得分析清楚这些内容
//跟踪的时候,盯住al寄存器,ebx 及堆栈数据的的变化就可以。
//al - 计算得知最后的解析例程的索引值
//ebx - 参与中间计算的用途,用来计算al时,所需要的值,每次都变化
//下面的代码是为了计算一个dispatch的索引,跟踪一遍明白原来后,其他的情况都类似
00403556 AC lods byte ptr ds:[esi] --- 读取VM_DATA 中的数据,此时al->0x3f
00403557 04 81 add al,81
00403559 04 90 add al,90
0040355B 00D8 add al,bl
0040355D 2C 90 sub al,90
0040355F /E9 080A0000 jmp CV_TES~3.00403F6C
eax --> 0xDA
//--------------------------------------
// 00403F6C
00403F6C 53 push ebx ; CV_TES~3.0040701A
00403F6D B7 57 mov bh,57
00403F6F 80EF D6 sub bh,0D6
00403F72 28F8 sub al,bh
00403F74 E9 4A230000 jmp CV_TES~3.004062C3
eax -> 0x59
//--------------------------------------
//004062C3
004062C3 5B pop ebx ; CV_TES~3.0040701A
004062C4 68 C5250000 push 25C5
004062C9 891C24 mov dword ptr ss:[esp],ebx ; 这里注意堆栈中记录了vm_data位置,也就是ebx的值
004062CC B7 D2 mov bh,0D2 ; 修改了ebx值
004062CE ^ E9 97E8FFFF jmp CV_TES~3.00404B6A
eax-> 0x59
esp-4 -> 0x40701A (vm_data offset)
// 继续跟踪
00404B6A 30F8 xor al,bh
00404B6C 8B1C24 mov ebx,dword ptr ss:[esp] ; --> 又把ebx恢复了
00404B6F 81C4 04000000 add esp,4 ; 恢复堆栈了,不再用[esp]保留,ebx值了。
00404B75 52 push edx ; 又把edx压入堆栈保存起来,开始折腾edx值了
00404B76 ^ E9 06FDFFFF jmp CV_TES~3.00404881
eax-> 0x8B
edx-> 7C92E514
//--------------------------------------
//00404881
00404881 B6 30 mov dh,30
00404883 80EE 70 sub dh,70
00404886 80C6 34 add dh,34
00404889 80EE 98 sub dh,98
0040488C F6D6 not dh
0040488E ^ E9 E5F2FFFF jmp CV_TES~3.00403B78
// eax 不变,
// edx -> 7C92A314
//--------------------------------------
//00403B78
00403B78 80F6 52 xor dh,52
00403B7B 2C 34 sub al,34
00403B7D E9 E5130000 jmp CV_TES~3.00404F67
// eax->0x57
// edx->7C92F114
//--------------------------------------
//00404F67
00404F67 28F0 sub al,dh
00404F69 51 push ecx ; ecx 又压入栈中折腾去了
00404F6A E9 390D0000 jmp CV_TES~3.00405CA8
// eax->66
// edx 不变
// ecx->0x01
//--------------------------------------
00405CA8 B1 12 mov cl,12
00405CAA C0E1 05 shl cl,5
00405CAD E9 210B0000 jmp CV_TES~3.004067D3
// eax 不变
// edx 不变
// ecx->40
//--------------------------------------
//004067D3
004067D3 80C1 F4 add cl,0F4
004067D6 00C8 add al,cl
004067D8 59 pop ecx
004067D9 5A pop edx
004067DA ^ E9 B8FFFFFF jmp CV_TES~3.00406797
// 堆栈已经被恢复
// eax -> 0x9A
// edx -> 变为最初值 0x7C92E514
// ecx -> 变为最初值 0x00000001
//--------------------------------------
// 修改ebx值
//00406797
00406797 80C3 4C add bl,4C
0040679A 80C3 BE add bl,0BE
0040679D 00C3 add bl,al
0040679F 80EB BE sub bl,0BE
004067A2 ^ E9 E8FFFFFF jmp CV_TES~3.0040678F
// eax -> 0x9A
// ebx -> 00407000
//--------------------------------------
// 0040678F
0040678F 51 push ecx
00406790 B5 CC mov ch,0CC
00406792 ^ E9 F2DEFFFF jmp CV_TES~3.00404689
// eax -> 0x9A
// ebx -> 00407000
// ecx -> 0000CC01
//--------------------------------------
00404689
00404689 D0ED shr ch,1
0040468B 80E5 95 and ch,95
0040468E 80F5 48 xor ch,48
00404691 28EB sub bl,ch
00404693 59 pop ecx ; 恢复了ecx为0x00000001
00404694 0FB6C0 movzx eax,al ; eax -> 0x0000009A
00404697 ^\FF2487 jmp dword ptr ds:[edi+eax*4] ; 调向了真正的解析例程中
// ebx -> 0x004070b4
dword ptr ds:[edi+eax*4] 相当于
ds:[00403468]=00403FFE (CV_TES~3.00403FFE)
403468 是vm_dispatch的数据范围,里面存放的时候00403FFE解析例程
到此时我们已经知道,这一路跟踪下来的代码仅为了计算一个eax的值,用来定位解析例程的索引。
但al从0x3F如何变成0x9A 的?只要查看这一路跟下了起变化的过程即可。
al = vm_data[0 ~ n] + 0x81 + 0x90 + bl - 0x90 - 0x81 ^ 0xD2 - 0x34 - 0xF1 + 0x34
al = vm_data[0 ~ n] + bl ^ 0xD2 - 0xF1
al = (0x3f + 0x1a ) ^ 0xD2 - 0xF1
al = 0x9A
所以固定的计算就是
index = (al + bl ) ^ 0xD2 - 0xF1 ;
同时更新ebx的值,
bl = bl + al
还是用这个程序加壳生成另外一个时,会发现和前面过程中执行的指令一摸一样,仅是利用大量的jmp打乱原有的指令
而已,也就是说指令的运算及执行条数是固定的,然后用jmp来打乱。只有设置不同的加密等级时才变得不一样。
x0: x0:
xxx1 xxx1
xxx2 xxx2
xxx3 混乱为 jmp x1
jmp x1 --------------->
x1: x1:
xxx4 xxx3
xxx5 jmp x2
xxx6
jmp x2 x2:
... xxx4
xxx5
xxx6
jmp x3
...
// 继续看处理例程,还是那套路子,不过这次计算的al是用来获得vm_context中要操作的一个寄存器的索引标志
// 简化后 al = al - bl - 0x2b - 0x38
00403FFE AC lods byte ptr ds:[esi] ;取vm_data 下一个数据
00403FFF 2C 36 sub al,36
00404001 E9 F81A0000 jmp CV_TES~3.00405AFE
00405AFE 28D8 sub al,bl
00405B00 52 push edx
00405B01 66:53 push bx
00405B03 ^ E9 71F9FFFF jmp CV_TES~3.00405479
00405479 B7 AB mov bh,0AB
0040547B 88FE mov dh,bh
0040547D 66:5B pop bx
0040547F C0EE 06 shr dh,6
00405482 FEC6 inc dh
00405484 80F6 35 xor dh,35
00405487 00F0 add al,dh
00405489 E9 0B190000 jmp CV_TES~3.00406D99
00406D99 5A pop edx
00406D9A ^ E9 9CD0FFFF jmp CV_TES~3.00403E3B
00403E3B 51 push ecx
00403E3C B5 C2 mov ch,0C2
00403E3E F6DD neg ch
00403E40 FECD dec ch
00403E42 F6D5 not ch
00403E44 E9 8C0E0000 jmp CV_TES~3.00404CD5
00404CD5 80E5 6A and ch,6A
00404CD8 ^ E9 47F0FFFF jmp CV_TES~3.00403D24
00403D24 F6D5 not ch
00403D26 80C5 6E add ch,6E
00403D29 28E8 sub al,ch
00403D2B 8B0C24 mov ecx,dword ptr ss:[esp]
00403D2E 83C4 04 add esp,4
00403D31 53 push ebx
00403D32 B7 A7 mov bh,0A7
00403D34 50 push eax
00403D35 B4 B3 mov ah,0B3
00403D37 80C4 26 add ah,26
00403D3A 80EC 91 sub ah,91
00403D3D E9 3F1F0000 jmp CV_TES~3.00405C81
00405C81 80C7 D1 add bh,0D1
00405C84 28E7 sub bh,ah
00405C86 ^ E9 27E4FFFF jmp CV_TES~3.004040B2
004040B2 80EF D1 sub bh,0D1
004040B5 58 pop eax
004040B6 F6D7 not bh
004040B8 E9 6D2E0000 jmp CV_TES~3.00406F2A
00406F2A C0EF 05 shr bh,5
00406F2D F6DF neg bh
00406F2F C0EF 05 shr bh,5
00406F32 80C7 31 add bh,31
00406F35 28F8 sub al,bh
00406F37 5B pop ebx
00406F38 51 push ecx
00406F39 B1 F7 mov cl,0F7
00406F3B ^ E9 26D9FFFF jmp CV_TES~3.00404866
00404866 D0E1 shl cl,1
00404868 ^ E9 5BFAFFFF jmp CV_TES~3.004042C8
004042C8 80C1 4F add cl,4F
004042CB 80E1 E7 and cl,0E7
004042CE E9 40140000 jmp CV_TES~3.00405713
00405713 80E1 BD and cl,0BD
00405716 80E9 2D sub cl,2D
00405719 00CB add bl,cl
0040571B 59 pop ecx
0040571C 80EB 68 sub bl,68
0040571F ^ E9 F2E2FFFF jmp CV_TES~3.00403A16
00403A16 00C3 add bl,al
00403A18 E9 56000000 jmp CV_TES~3.00403A73
00403A73 80C3 68 add bl,68
00403A76 80EB F8 sub bl,0F8
00403A79 0FB6C0 movzx eax,al
// eax == 7
00403A7C 8D0487 lea eax,dword ptr ds:[edi+eax*4]
// 上面执行完eax == 0040321C,是vm_context 结构中的偏移1c的位置,经过分析知道是eflag标志
00403A7F 68 7E320000 push 327E ; 压入一个临时数字,后面要改写这个数字
00403A84 E9 270D0000 jmp CV_TES~3.004047B0
004047B0 890424 mov dword ptr ss:[esp],eax ; 修改这个数字
// 这相当于把虚拟机的eflag标志的地址进栈
// ebx - > 004070BB
// esi - > 0040701C -- 此时指向vm_data+3处
004047B3 ^ E9 9EEDFFFF jmp CV_TES~3.00403556 ;再次跳回vm起始位置
相当于执行:push eflag
// 至此我们已经知道刚才的那个00403FFE例程是cv虚拟机压入eflag标志进栈操作,但该例程并不是就单纯完成push eflag
// 因为eflag的偏移是根据vm_data中的数据计算而来的,所以这个例程应该是push imm32 功能,imm32是根据传入的参数
// 来确定的。
// 继续看下面的例程,al = 0xe1 ,计算后得到的例程地址为5D,[edi + eax * 4] = 0x403f8f
// 分析可知0x403f8f的解析例程目的是执行pop edx,执行过程如下,*表示垃圾指令
00403F8F 8B1424 mov edx,dword ptr ss:[esp] ; CV_TES~3.0040321C
00403F92 55 push ebp *
00406EC7 89E5 mov ebp,esp *
00406EC9 81C5 04000000 add ebp,4 *
00406C51 51 push ecx *
00406C52 B9 04000000 mov ecx,4 *
00406BEA 01CD add ebp,ecx *
00406BEC 59 pop ecx *
00406BED 872C24 xchg dword ptr ss:[esp],ebp *
00406BF0 5C pop esp *
执行;pop edx
大家可以在 00404697 jmp dword ptr ds:[edi+eax*4] 这个地方大家可以下断点,因为每一个cv的dispatch执
行完毕后,都要调向这里去执行下一个dispatch
// 继续获得解析例程地址为ds:[00403324] = 0x00405F70
00405F70 8F02 pop dword ptr ds:[edx]
00405F72 ^ E9 DFD5FFFF jmp CV_TES~3.00403556 --- 跳回vm_start
// 也就是0x00405F70例程
执行 :pop [edx]
// 下一条解析指令 ds:[00403468]= 0x00403FFE ,这是已知的,上面分析过
执行 :push vm_off_04
// 下一条解析指令 ds:[00403374]=00403F8F ,这是已知的,上面分析过
执行 : pop edx
// 下一条解析指令 ds:[00403324]=00405F70 ,这是已知的,上面分析过
执行: pop [edx]
此时堆栈弹出来的值原EDI的值,所以可以推断出vm_off_04是cv虚拟机的存放edi的地方
//--------------------------------------
// 下一条解析指令 ds:[00403468]=00403FFE ,这是已知的,上面分析过
执行:push vm_off_14
// 下一条解析指令 ds:[00403374]=00403F8F ,这是已知的,上面分析过
执行 : pop edx
// 下一条解析指令 ds:[00403324]=00405F70 ,这是已知的,上面分析过
执行: pop [edx]
此时堆栈弹出来的值原ESI的值,所以可以推断出vm_off_14是cv虚拟机的存放esi的地方
//--------------------------------------
// 下一条解析指令 ds:[00403468]= 0x00403FFE ,这是已知的,上面分析过
执行 :push vm_off_00
// 下一条解析指令 ds:[00403374]=00403F8F ,这是已知的,上面分析过
执行 : pop edx
// 下一条解析指令 ds:[00403324]=00405F70 ,这是已知的,上面分析过
执行: pop [edx]
此时堆栈弹出来的值原ebp的值,所以可以推断出vm_off_00是cv虚拟机的存放ebp的地方
//--------------------------------------
// 下一条解析指令 ds:[00403468]= 0x00403FFE ,这是已知的,上面分析过
执行 :push vm_off_18
// 下一条解析指令 ds:[00403374]=00403F8F ,这是已知的,上面分析过
执行 : pop edx
// 下一条解析指令 ds:[00403324]=00405F70 ,这是已知的,上面分析过
执行: pop [edx]
此时堆栈弹出来的值原Esp的值,所以可以推断出vm_off_18是cv虚拟机的存放esp的地方
//--------------------------------------
// 下一条解析指令 ds:[00403468]= 0x00403FFE ,这是已知的,上面分析过
执行 :push vm_off_18
// 下一条解析指令 ds:[00403374]=00403F8F ,这是已知的,上面分析过
执行 : pop edx
// 下一条解析指令 ds:[00403324]=00405F70 ,这是已知的,上面分析过
执行: pop [edx]
此时堆栈弹出来的值原Esp的值,所以可以推断出vm_off_18是cv虚拟机的存放esp的地方
//--------------------------------------
接着对 vm_off_28 赋值0x03 ,在vm_context中索引是0x03的寄存器是ecx
//紧接着又给vm_off_18赋值ebx值
//--------------------------------------
// 下一条解析指令 ds:[00403324]=00405F70 ,这是已知的,上面分析过
执行: pop [edx]
此时堆栈弹出来的值原EDX的值,所以可以推断出vm_off_08是cv虚拟机的存放eDX的地方
//--------------------------------------
// 下一条解析指令 ds:[00403468]= 0x00403FFE ,这是已知的,上面分析过
执行 :push vm_off_08
// 下一条解析指令 ds:[00403374]=00403F8F ,这是已知的,上面分析过
执行 : pop edx
// 下一条解析指令 ds:[00403324]=00405F70 ,这是已知的,上面分析过
执行: pop [edx]
此时堆栈弹出来的值原EDX的值,所以可以推断出vm_off_08是cv虚拟机的存放eDX的地方
//--------------------------------------
// 下一条解析指令 ds:[00403468]= 0x00403FFE ,这是已知的,上面分析过
执行 :push vm_off_0c
// 下一条解析指令 ds:[00403374]=00403F8F ,这是已知的,上面分析过
执行 : pop edx
// 下一条解析指令 ds:[00403324]=00405F70 ,这是已知的,上面分析过
执行: pop [edx]
此时堆栈弹出来的值原EcX的值,所以可以推断出vm_off_0c是cv虚拟机的存放ecx的地方
/--------------------------------------
// 下一条解析指令 ds:[00403468]= 0x00403FFE ,这是已知的,上面分析过
执行 :push vm_off_10
// 下一条解析指令 ds:[00403374]=00403F8F ,这是已知的,上面分析过
执行 : pop edx
// 下一条解析指令 ds:[00403324]=00405F70 ,这是已知的,上面分析过
执行: pop [edx]
此时堆栈弹出来的值原EAX的值,所以可以推断出vm_off_10是cv虚拟机的存放eax的地方
//--------------------------------------
此时可以整理的vm_data结构是
struct vm_data
{
DWORD vm_ebp; +0
DWORD vm_edi; +4
DWORD vm_edx; +8
DWORD vm_ecx; +0c
DWORD vm_eax; +10
DWORD vm_esi; +14
DWORD vm_ebx; +18
DWORD vm_eflag; +1c
DWORD VM_off_20;
DWORD VM_off_24;
DWORD VM_ecx_value;
DWORD VM_off_2c;
DWORD VM_busy_flag;
DWORD VM_off_34;
DWORD VM_off_38;
DWORD VM_off_3c;
DWORD VM_off_40;
DWORD VM_off_44;
DWORD VM_off_48;
DWORD VM_dispatch_xxx1;
DWORD VM_dispatch_xxx2;
DWORD VM_dispatch_xxx3;
...
}
cv虚拟机把宿主的寄存器值分别赋值到自身的vm_context结构的寄存器中
// 下一条解析指令 ds:[004033E8]=0040640A
对比一下执行前后寄存器:
前:
EDX 00403210 CV_TES~3.00403210
EBX 00407022 CV_TES~3.00407022
后:
EDX 0013FFC0
EBX 00407075 CV_TES~3.00407075
可知该解析例程是完成 mov edx,esp
// 下一条解析指令 ds:[0040334C]=0040584D
对寄存器无变化,堆栈中压入了edx值
可知该解析例程是完成 push edx
// 下一条解析指令 ds:[00403350]=00406AB1
对寄存器无变化,堆栈中压入了0x00000004值
可知该解析例程是完成 push 00000004 ,但这个数字是依赖vm_data计算出来的,所以该例程
完成的是 push imm32 功能
// 下一条解析指令 ds:[004033FC]=00403E01
寄存器和堆栈都有变化。
这个例程有两个操作,pop eax, add[esp],eax
// 下一条解析指令 ds:[00403390]=00405B41
寄存器和堆栈都有变化
这个例程完成的指令是 mov esp,[esp]
// 下一条解析指令 ds:[00403350]=00406AB1,这条上面分析过,是push imm32 ,imm32 是计算出来的。
该指令完成 push 0x01234567
// 下一条解析指令 ds:[004034BC]=004063C3 ,该例程不是vm_dispatch中的解析指令
该指令完成压入403000 进栈,这是cv虚拟机进入是保持的eax值,所以该功能完成 push vm_eax
也就是t_buff的值
// 下一条解析指令 ds:[00403374]=00403F8F,前面已经分析过
该指令完成 pop edx ,把t_buff值给edx
// 下一条解析指令 ds:[00403324]=00405F70 ,前面分析过这
该指令完成 pop [edx] ,将0x1234567 写入到403000地址中
此时我们加载虚拟机里面的指令,
mov dword ptr[eax],01234567h ,已经被处理完毕了。下面是恢复虚拟机堆栈
// 下一条解析指令 ds:[00403350]=00406AB1
push 00401015 ; 401015地址对应的是原push 0,也就是加密的语句的下体指令的地址
所以该解析完成的压入一个退出cv虚拟机时的返回地址
// 下一条解析指令 ds:[00403490]=004056D9
add ebx,imm32 (不是完全肯定) nop ,感觉混入的一条无效指令, ebx = ebx + 运算结果中的eax 所以这条指令不是很肯定
// 下一条解析指令 ds:[00403284]=00404610
add esi,6 (不完全肯定) nop ,同样刚进是混入的无效指令
// 注意下面的指令是恢复vm_context中的寄存器到实际运行环境的堆栈中,因为原程序中要加密的指令已经执行完毕了。
现在要恢复环境了。
// 恢复处理vm_eflag
// 下一条解析指令 ds:[00403468]=00403FFE (CV_TES~3.00403FFE)
该指令完成 push vm_eflag
// 下一条解析指令 ds:[00403374]=00403F8F
pop edx
// 下一条解析指令 ds:[00403450]=004040BD
该指令完成 push [edx]
// 恢复处理vm_eax
// 下一条解析指令 ds:[00403468]=00403FFE
push vm_eax_addr (由此看出403FFE 是根据eax参数信息,获得vm_data中的索引)
// 下一条解析指令 ds:[00403374]=00403F8F
pop edx ,此时edx里面存放的是vm_context中vm_eax的地址
// 下一条解析指令 ds:[00403450]=004040BD (dis_no -> 0x94)
push [edx]
// 恢复处理vm_ecx
// 下一条解析指令 ds:[00403468]=00403FFE
push vm_ecx_addr
// 下一条解析指令 ds:[00403374]=00403F8F
pop edx ,此时edx里面存放的是vm_context中vm_ecx的地址
// 下一条解析指令 ds:[00403450]=004040BD (dis_no -> 0x94)
push [edx] , 将vm_context 中的vm_ecx 压入栈
// 恢复处理vm_edx
// 下一条解析指令 ds:[00403468]=00403FFE (dis_no -> 0x9a)
push vm_edx_addr
// 下一条解析指令 ds:[00403374]=00403F8F
pop edx ,此时edx里面存放的是vm_context中vm_edx的地址
// 下一条解析指令 ds:[00403450]=004040BD (dis_no -> 0x94)
push [edx] ,将vm_context 中的vm_edx 压入栈
// 恢复处理vm_bex
// 下一条解析指令 ds:[00403468]=00403FFE
push vm_ebx_addr
// 下一条解析指令 ds:[00403374]=00403F8F
pop edx ,此时edx里面存放的是vm_context中vm_ebx的地址
// 下一条解析指令 ds:[00403450]=004040BD
push [edx] ,将vm_context 中的vm_ebx 压入栈
又压入了一遍vm_ebx
// 下一条解析指令 ds:[00403468]=00403FFE
push vm_ebx_addr
// 下一条解析指令 ds:[00403374]=00403F8F
pop edx ,此时edx里面存放的是vm_context中vm_ebx的地址
// 下一条解析指令 ds:[00403450]=004040BD
push [edx] ,将vm_context 中的vm_ebx 压入栈
// 恢复处理vm_ebp
// 下一条解析指令 ds:[00403468]=00403FFE
push vm_ebp_addr
// 下一条解析指令 ds:[00403374]=00403F8F
pop edx ,此时edx里面存放的是vm_context中vm_ebp的地址
// 下一条解析指令 ds:[00403450]=004040BD
push [edx] ,将vm_context 中的vm_ebp 压入栈
// 恢复处理vm_esi
// 下一条解析指令 ds:[00403468]=00403FFE
push vm_esi_addr
// 下一条解析指令 ds:[00403374]=00403F8F
pop edx ,此时edx里面存放的是vm_context中vm_esi的地址
// 下一条解析指令 ds:[00403450]=004040BD
push [edx] ,将vm_context 中的vm_esi 压入栈
// 恢复处理vm_edi
// 下一条解析指令 ds:[00403468]=00403FFE
push vm_edi_addr
// 下一条解析指令 ds:[00403374]=00403F8F
pop edx ,此时edx里面存放的是vm_context中vm_edi的地址
// 下一条解析指令 ds:[00403450]=004040BD
push [edx] ,将vm_context 中的vm_edi 压入栈
至此原来的宿主寄存器已经全部恢复完毕。此时cv的虚拟机要退出。
// 下一条解析指令 ds:[004033F8]=004066AC ,我们详细分析一下
004066AC FF77 38 push dword ptr ds:[edi+38] ;vm_context.busyflag 是否忙的标志
004066AF FF3424 push dword ptr ss:[esp]
004066B2 59 pop ecx ;该标志给busyflag
004066B3 81C4 04000000 add esp,4
004066B9 ^\E9 CDF5FFFF jmp CV_TES~3.00405C8B
// 混淆的垃圾指令
00405C8B 57 push edi
00405C8C 56 push esi
00405C8D BE 0113AB7B mov esi,7BAB1301
00405C92 017424 04 add dword ptr ss:[esp+4],esi
00405C96 5E pop esi
00405C97 8B1424 mov edx,dword ptr ss:[esp]
00405C9A E9 74030000 jmp CV_TES~3.00406013
// 混淆的垃圾指令
00406013 81C4 04000000 add esp,4
00406019 81EA 0113AB7B sub edx,7BAB1301
0040601F ^ E9 96E1FFFF jmp CV_TES~3.004041BA
004041BA 09C9 or ecx,ecx
004041BC E9 7E050000 jmp CV_TES~3.0040473F
0040473F ^\0F84 FBF7FFFF je CV_TES~3.00403F40
00403F40 50 push eax ;把eax 保存起来,继续折腾
00403F41 B8 30000000 mov eax,30
00403F46 01D0 add eax,edx
00403F48 56 push esi
00403F49 BE 53496F70 mov esi,706F4953
00403F4E F7D6 not esi
00403F50 E9 2E2D0000 jmp CV_TES~3.00406C83
00406C83 81EE ACB6908F sub esi,8F90B6AC
00406C89 8930 mov dword ptr ds:[eax],esi
00406C8B 5E pop esi
00406C8C 8B0424 mov eax,dword ptr ss:[esp]
00406C8F 83C4 04 add esp,4
00406C92 61 popad ;恢复宿主的寄存器
00406C93 9D popfd
00406C94 C3 retn ;这里从虚拟机返回到宿主
至此cv虚拟机将控制权交给宿主程序,加密执行的过程完毕。
最后说一下关于欢迎的一点看法,首先一定要解析出每一个dispatch的含义。静态的分析倒也可以,但可能操作起来更麻烦。
不如用虚拟机对付虚拟机,大致看cv的加密方式后,更多的是指令的混淆和单纯的执行,这样我们实现一个小的堆栈虚拟机来仿真执行一下即可,在dispatch入口处断下,每一个dispatch含义我们都知道这样直接还原为我们知道的x86指令反而更好,当然这只是一个想法不知是否可行,毕竟分析vm这东西很是麻烦的,未知的东西太多。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课