Pinguw
Articles10
Tags3
Categories2

Categories

Archive

浅谈VM虚拟机

浅谈VM虚拟机

浅谈VM虚拟机

1. 什么是虚拟机?

虚拟机:自己定义一套指令,在程序中能有一套函数和结构解释自己定义的指令并执行功能。

查一下维基百科

虚拟机(英语:virtual machine),在计算机科学中的体系结构里,是指一种特殊的软件,可以在计算机平台终端用户之间创建一种环境,而终端用户则是基于虚拟机这个软件所创建的环境来操作其它软件。虚拟机(VM)是计算机系统的仿真器,通过软件模拟具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统,能提供物理计算机的功能。

有不同种类的虚拟机,每种虚拟机具有不同的功能:

  • 系统虚拟机(也称为全虚拟化虚拟机)可代替物理计算机。它提供了运行整个操作系统所需的功能。虚拟机监视器(hypervisor)共享和管理硬件,从而允许有相互隔离但存在于同一物理机器上的多个环境。现代虚拟机监视器使用虚拟化专用硬件(主要是主机CPU)来进行硬件辅助虚拟化。
  • 程序虚拟机 被设计用来在与平台无关的环境中执行计算机程序。

而我们CTF中遇到的虚拟机一般是这种:

vm(虚拟机保护)是一种基于虚拟机的代码保护技术。他将基于x86汇编系统中的可执行代码转换为字节码指令系统的代码。来达到不被轻易篡改和逆向的目的。

Untitled.png

2. 虚拟机的运行原理

要搞清虚拟机的运行原理,最好的方法是手搓一个虚拟机出来:

一般虚拟机分为基于寄存器的虚拟机和基于栈的虚拟机,通过数据存储处理的方式区分,二者的区别可以看这里 栈式虚拟机和寄存器式虚拟机?

这里先写一个简单的寄存器虚拟机,

实现虚拟机时,要做好这么几个步骤:

  1. 寄存器虚拟机需要初始化好栈空间和寄存器空间
  2. 定义一套opcode
  3. 实现opcode功能的模块

定义opcode和寄存器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum regist
{
R1 = 0xe1,
R2 = 0xe2,
R3 = 0xe3,
};

enum opcodes
{
MOV = 0xf1,
XOR = 0xf2,
RET = 0xf4,
READ = 0xf5,
};

定义vm相关变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct
{
unsigned char opcode;
void (*handle)(void*); //handler执行器
}vm_opcode;

typedef struct vm_cpus
{
int r1;
int r2;
int r3;
unsigned char* eip; //指向opcode的指针
vm_opcode op_list[OPCODE_N]; //opcode列表,存储opcode和其对应的操作函数
}vm_cpu;

初始化vm各项数据,并使opcode关联handler功能函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void vm_init(vm_cpu* cpu)
{
cpu->r1 = 0;
cpu->r2 = 0;
cpu->r3 = 0;
cpu->eip = (unsigned char*)vm_code;

cpu->op_list[0].opcode = 0xf1;
cpu->op_list[0].handle = (void (*)(void*))mov; //0xf1对应mov

cpu->op_list[1].opcode = 0xf2;
cpu->op_list[1].handle = (void (*)(void*))xor1; //0xf2对应xor

cpu->op_list[2].opcode = 0xf5;
cpu->op_list[2].handle = (void (*)(void*))read1; //0xf1对应read

vm_stack = (char*)malloc(0x512);
memset(vm_stack, 0, 0x512); //开辟栈空间
}

vm启动函数:

1
2
3
4
5
6
void vm_start(vm_cpu* cpu)
{
cpu->eip = (unsigned char*)vm_code;
while((*cpu->eip) != RET) //持续执行opcode直到ret
vm_dispatcher(cpu);
}

执行器:

1
2
3
4
5
6
7
8
9
void vm_dispatcher(vm_cpu* cpu)
{
for(int i = 0; i < OPCODE_N; i++)
if(*cpu->eip == cpu->op_list[i].opcode)
{
cpu->op_list[i].handle(cpu);
break;
}
}

再写出opcode:

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
unsigned char vm_code[] = {
0xf5,
0xf1,0xe1,0x0,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x0,0x00,0x00,0x00,
0xf1,0xe1,0x1,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x1,0x00,0x00,0x00,
0xf1,0xe1,0x2,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x2,0x00,0x00,0x00,
0xf1,0xe1,0x3,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x3,0x00,0x00,0x00,
0xf1,0xe1,0x4,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x4,0x00,0x00,0x00,
0xf1,0xe1,0x5,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x5,0x00,0x00,0x00,
0xf1,0xe1,0x6,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x6,0x00,0x00,0x00,
0xf1,0xe1,0x7,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x7,0x00,0x00,0x00,
0xf1,0xe1,0x8,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x8,0x00,0x00,0x00,
0xf1,0xe1,0x9,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x9,0x00,0x00,0x00,
0xf1,0xe1,0xa,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0xa,0x00,0x00,0x00,
0xf1,0xe1,0xb,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0xb,0x00,0x00,0x00,
0xf1,0xe1,0xc,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0xc,0x00,0x00,0x00,
0xf1,0xe1,0xd,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0xd,0x00,0x00,0x00,
0xf1,0xe1,0xe,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0xe,0x00,0x00,0x00,
0xf1,0xe1,0xf,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0xf,0x00,0x00,0x00,
0xf1,0xe1,0x10,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x10,0x00,0x00,0x00,
0xf1,0xe1,0x11,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x11,0x00,0x00,0x00,
0xf1,0xe1,0x12,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x12,0x00,0x00,0x00,
0xf1,0xe1,0x13,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x13,0x00,0x00,0x00,
0xf1,0xe1,0x14,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x14,0x00,0x00,0x00,
0xf1,0xe1,0x15,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x15,0x00,0x00,0x00,
0xf1,0xe1,0x16,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x16,0x00,0x00,0x00,
0xf1,0xe1,0x17,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x17,0x00,0x00,0x00,
0xf1,0xe1,0x18,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x18,0x00,0x00,0x00,
0xf1,0xe1,0x19,0x00,0x00,0x00, 0xf2, 0xf1,0xe4,OFFEST + 0x19,0x00,0x00,0x00,
0xf4
};

寄存器虚拟机完成。

3. 给个实例,出个小题

上面的虚拟机大概实现的就是这个过程

1
2
3
4
5
6
7
call read1

MOV R1,flag[i]
XOR R1 0x12
MOV [OFFEST],R1; //循环len(flag)次

ret

也就是实现了

1
2
for(int i = 0; i < strlen(flag); i++)
flag[i] ^= 0x12;

的功能。

把源码放在这:

maybeVM

Untitled _1_.png

4. 如何破解VM虚拟机保护类题目

解题一般步骤:

分析VM结构->分析opcode->编写parser->re算法

VM结构常见类型:

基于栈、基于队列、基于信号量

opcode:

与VM数据结构对应的指令 :push pop

运算指令:add、sub、mul等

示例1链接:[NewStarCTF 2023 公开赛道]茶 用来简单了解VM原理 ——by me

示例2链接:[watevrCTF 2019]Repyc——WriteUp pyc+混淆+VM虚拟机 ——by me

Author:Pinguw
Link:https://pinguw.github.io/2024/03/26/Reverse/aboutvm/
看完了吗,再去看看博主的其他文章叭:)