西湖论剑 EasyVT 复现

前置知识

通过查资料了解一些 VT 知识点,以及相关退出指令以及常量即可

其实整个题和 VT 关系不是很大,只是借助了 VT 来将对应加密的流程分为了 4 个部分来进行检测

学习参考链接

VT虚拟化技术笔记(part 4)- smallzhong_

zzhouhe/VT_Learn (github.com)

VT技术入门 - _周壑

逆向分析

拿到题目后我们可以发现有两个东西,一个exe文件和一个sys文件,将exe拖入IDA进行分析可以看到其明显的VM标识,同时观察到输入32字节,分四组,每组 8 字节。从 Vmxon 开始到 Vmoff 结束,每条 vm 指令都会发起一次 VmExit 进入 Host 进行对应的处理。若四次均校验通过则为 flag

image-20230308232215911

结合题目信息我们可以很容易想到VT相关知识点,同时能猜测出sys文件为对应的驱动,我们将sys拖入到IDA进行分析

image-20230308232356431

可以确定其驱动的属性,在整个驱动中其实大部分东西都没什么用,就是最最简单基本的VT配置,在 SetupVMCS 函数(sub_402240)中主要关注 HOST_RIP 的设置。这个域设置 VMMEntryPoint(sub_401C10),也就是当 VmExit 发生时,会先进入到这个位置,做状态的保存恢复和 VM 处理函数的 Dispatch 分发。

对于VM事件的核心分发函数在 sub_401C90 中,并根据宏可以还原出符号

image-20230308232648139

对于宏常量定义可以参考vtsystem.h

之后我们便需要根据ring3下的程序的汇编来观察对应的执行过程,可以看出每组输入经过的Host处理流程都是一样的,Vmxon -> Vmclear -> Vmptrld -> Vmwrite -> Vmlaunch -> Vmread -> Vmcall -> Vmptrst -> Vmresume -> Vmoff,总共10步。然后再去分析驱动程序,可以知道vmxon~vmread 在读取配置数据,vmcall~vmresume在处理加密,vmxoff判断运算结果。

整理出的逻辑大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
array_list = [0x00102030,0x40506070,0x8090A0B0,0xC0D0E0F0]

--- step 1 ---

delta = 0xAB95D3CC;
qmemcpy(key, "55a8ca7d24f178ad", 16);
v0 = _esi;
v1 = _edi;
sum = 0x10000000;
tea_key_0 = array_list[3];
tea_key_1 = array_list[2];
tea_key_2 = array_list[0];
tea_key_3 = array_list[1];
*a2 = _esi;
*&a2[4] = _edi;

--- step 2 ---

delta = 0xAB95D3CC;
v0 = _esi;
v1 = _edi;
sum = 0x20000000;
*a2 = _edi;
*&a2[4] = _esi;
qmemcpy(key, "14a57d1bd2023c0f", 16);
tea_key_0 = array_list[1];
tea_key_1 = array_list[2];
tea_key_2 = array_list[3];
tea_key_3 = array_list[0];

--- step 3 ---

*a2 = _esi;
*&a2[4] = _edi;
qmemcpy(key, "35a78da3d61198dc", 16);
tea_key_0 = array_list[2];
tea_key_1 = array_list[1];
tea_key_2 = array_list[3];
tea_key_3 = array_list[0];
delta = 0xC95D6ABF;
v0 = _edi;
v1 = _esi;

--- step 4 ---

v0 = _edi;
v1 = _esi;
*a2 = _edi;
*&a2[4] = _esi;
tea_key_0 = array_list[1];
tea_key_1 = array_list[3];
tea_key_2 = array_list[2];
tea_key_3 = array_list[0];
qmemcpy(key, "97abdc154f7c82ae", 16);

--- step 5 ---

key = "04e52c7e31022b0b"

--- step 6 ---

--- step 7 ---

key = "04e52c7e31022b0b"
rc4_init
rc4_crypt


--- step 8 ---

edi esi -> esi edi // 加密顺序发生了变换,rc4加密按照EDI ESI顺序,Tea加密是按照ESI EDI的顺序

--- step 9 ---

delta = 0xAB95D3CC
sum = 0x20000000;
tea_key_0 = array_list[1];
tea_key_1 = array_list[3];
tea_key_2 = array_list[2];
tea_key_3 = array_list[0];
tea_enc // 魔改
for ( i = 0; i < 32; ++i )
{
v0 -= (tea_key_1 + (v1 >> 5)) ^ (sum + v1) ^ (tea_key_0 + 16 * v1);
v1 += (tea_key_3 + (v0 >> 5)) ^ (sum + v0) ^ (tea_key_2 + 16 * v0);
sum -= delta;
}

--- step 10 ---

check_enc
unsigned int res[8] = {
0x5C073994, 0x0D805CB3, 0x87DDA586, 0x0317FB8E, 0x6520EF29, 0x5A4987AF, 0xEB2DC2A4, 0x38CF470E
};

对于每次的调用过程我们可以发现会对 RC4 以及 TEA 加密的 Key 以及 Delta 进行重新赋值,我们只需要保留最后变换即可,其余的可以忽略。

脚本解密

对于上述过程先执行的 RC4 再执行的 TEA ,我们解密时先对 TEA 进行解密,之后在对 RC4 进行解密即可,需要注意的是在 step 8 处交换了数据顺序,在将 TEA 解密出来的数据丢到 RC4 解密脚本前需要对其进行两两交换

TEA 解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdint.h>

//加密函数
void encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0x20000000, i; /* set up */
uint32_t delta=0xAB95D3CC; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for ( i = 0; i < 32; ++i ) {
v0 -= (k1 + (v1 >> 5)) ^ (sum + v1) ^ (k0 + 16 * v1);
v1 += (k3 + (v0 >> 5)) ^ (sum + v0) ^ (k2 + 16 * v0);
sum -= delta;
} /* end cycle */
v[0]=v0;
v[1]=v1;
}
//解密函数
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0x20000000-0xC95D6ABF*32, i; /* set up */
uint32_t delta=0xC95D6ABF; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) {
sum += delta; /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);

} /* end cycle */
v[0]=v0;
v[1]=v1;
}

int main() {
uint32_t v[]= {0x5C073994, 0x0D805CB3, 0x87DDA586, 0x0317FB8E, 0x6520EF29, 0x5A4987AF, 0xEB2DC2A4, 0x38CF470E};
uint32_t array_list[] = {0x00102030, 0x40506070, 0x8090A0B0, 0xC0D0E0F0};
uint32_t key[4];

key[0] = array_list[1];
key[1] = array_list[3];
key[2] = array_list[2];
key[3] = array_list[0];

decrypt(v, key);
decrypt(v+2, key);
decrypt(v+4, key);
decrypt(v+6, key);

for(int i=0; i < 8; i++) { // 需要注意字节顺序
printf("0x%x,",v[i]&0xff);
printf("0x%x,",v[i]>>8&0xff);
printf("0x%x,",v[i]>>16&0xff);
printf("0x%x,",v[i]>>24&0xff);
}
return 0;
}

RC4 解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include<iostream>
#include<cstring>
using namespace std;

/*初始化函数*/
void rc4_init(unsigned char*s,unsigned char*key, unsigned long Len) {
int i=0,j=0;
char k[256]= {0};
unsigned char tmp=0;
for(i=0; i<256; i++) {
s[i]=i;
k[i]=key[i%Len];
}
for(i=0; i<256; i++) {
j=(j+s[i]+k[i])%256;
tmp=s[i];
s[i]=s[j];//交换s[i]和s[j]
s[j]=tmp;
}
}

/*加解密*/
void rc4_crypt(unsigned char*s,unsigned char*Data,unsigned long Len) {
int i=0,j=0,t=0;
unsigned long k=0;
unsigned char tmp;
for(k=0; k<Len; k++) {
i=(i+1)%256;
j=(j+s[i])%256;
tmp=s[i];
s[i]=s[j];//交换s[x]和s[y]
s[j]=tmp;
t=(s[i]+s[j])%256;
Data[k]^=s[t];
}
}


int main() {

unsigned char s[256] = { 0 }, s2[256] = { 0 };//S-box
char key[] = {"04e52c7e31022b0b"};//密钥
//加密后的数据
char pData[]= {0xd5,0x12,0x9c,0xb8,0x2c,0x7a,0x7e,0xb1,0xd1,0x42,0x98,0xbf,0x21,0x73,0x25,0xe6,0xd0,0x45,0xcd,0xed,0x21,0x29,0x26,0xb2,0xdc,0x49,0x9b,0xb9,0x2c,0x2d,0x72,0xba};
//加密后数据长度
unsigned long len = strlen(pData);
//unsigned long len = ;
int i;

for(int i=0; i<len; i+=8) {
rc4_init(s, (unsigned char*)key, 16); //初始化
rc4_crypt(s, (unsigned char*)&pData[i], 8);//解密
}


for(int i=0; i<len; i++) {
cout<<(char)((int)pData[i]&0xff);
}

cout << endl;

for(int i = 0; i < 4; i++) {
for(int j = 1; j >= 0; j--) {
for(int k = 0; k<4; ++k) {
printf("%c", pData[i*8 + j*4 + k]); // flag
}
}
}


return 0;
}
// 0c378192437058be54bb5ba198468f59 还需要倒一下顺序
// 81920c3758be43705ba154bb8f599846 // flag

Flag

1
DASCTF{81920c3758be43705ba154bb8f599846}

西湖论剑 EasyVT 复现
https://equinox-shame.github.io/2023/03/08/西湖论剑 EasyVT 复现/
作者
梓曰
发布于
2023年3月8日
许可协议