似乎分析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 ;定位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
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
[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!