导语:虚拟机保护技术已经出现了10多年,目前已有较多、较为成熟的商业化虚拟机保护产品,如VMProtect、Themida等。这些产品实现的虚拟机过于复杂,而本文仅仅是逆向分析和编程实现小型虚拟机保护。

1.引言

虚拟机保护技术已经出现了10多年,目前已有较多、较为成熟的商业化虚拟机保护产品,如VMProtect、Themida等。这些产品实现的虚拟机过于复杂,而本文仅仅是逆向分析和编程实现小型虚拟机保护,这种小型虚拟机仅用于说明虚拟机保护这种技术,可以用于开发CrackMe,但与真正的虚拟机保护还有非常远的路要走。

2017年看雪CTF大赛正在火热征题中,防守组的各位参赛者随手写一个适用于自己CrackMe的虚拟机,相信可以如虎添翼。

GitHub确实是个好地方,写代码之前可以先去逛一逛。笔者原本打算自行写一个小型虚拟机,不过在GitHub找到了非常理想的代码,所以干脆把这份代码拿出来说一说。这份代码出自NWMonster大神的CrackMe仓库。

本文的后续按照如下方式组织:首先逆向分析一下这个CrackMe,进而分析一下源码并实现自己的小型虚拟机,最后是一个小小的总结。

2.逆向分析

在逆向分析之前,了解一点关于小型虚拟机的程序结构还是很有帮助的。一般来讲,一个小型的虚拟机保护结构图如下:

1.png

上图中,VM_code代表虚拟机可以解读的字节码,VM_register为虚拟机使用的寄存器,在一次VM_loop中,虚拟机读取VM_code,并根据读取的内容选择并执行相应的VM_handler。为了明确被虚拟机保护的代码所实现的功能,我们需要了解每个VM_handler的含义,从而理解被隐藏的代码。

需要注意的是,该流程图并不适用于商业虚拟机保护软件,商业的虚拟机保护往往更复杂一些。

通常情况下,可以使用OllyDbg (OD)的trace功能,找到虚拟机的VM_loop。但本次我们使用IDA完成分析,这主要因为IDA的图形界面更加清晰明了。为了方便调试,笔者将源码重新编译为32位windows的debug版本。后文会把所有的材料都上传到Github,需要的读者可自行下载。

用IDA打开CrackMe,不难发现其207字节的VM_code:

11.png

上图中的VM_code被赋值给[eax + 20h],如下图:

111.png

根据这些代码,可以推测VM_register的结构如下:

Structure VM_register{
+0x00     ??
+0x04     ??
+0x08     ??
+0x0C     ??
+0x10     ??
+0x14     ??
+0x18     ??
+0x1C     ??
+0x20    IP register
}

其中,eax指向VM_register,[eax + 20h]指向虚拟机的指令寄存器。紧接着就可以找到完整的VM_loop,如下图:

1111.png

数一下上图中的方块数量,可以推测这里有大约23个VM_handler。下面开始根据VM_code的顺序逐一分析这些VM_handler。VM_code的第一个字节为0xDE,该字节会使虚拟机执行VM_handler_defaultcase,如下图所示:

2.png

而VM_handler_defaultcase的详细内容如下:

22.png

上图中,[this + 0x24]即为指令寄存器,因为VM_register是VM类中的第一个元素,其伪代码如下:

class   VM{
+0x00   point to virtual table
+0x04   VM_register  + 0x20    IP register
…
}

所以,在0x20的基础上又增加了0x4字节的偏移。这段代码并无实际功能,只增加了指令寄存器,故可理解为NOP指令。VM_code中的第2,3,4字节同样会令虚拟机执行VM_handler_defaultcase分支,故可以如下代码重写VM_code:

1                0xDE                         NOP
2                0xAD                         NOP
3                0xC0                         NOP
4                0xDE                         NOP

VM_code的第5个字节为0x7C,该字节会使虚拟机执行VM_handler_22分支,该分支的具体内容如下:

222.png

该段代码的含义是:将后续0xF字节的VM_code与0x66异或。暂不清楚其异或的目的为何。可以用如下方式表示该指令:

5                0x7C          0x2E 0x27 … 0x23   0x32  XOR the following 0xF bytes with   0x66

VM_code的第21字节为0x70,与之相应的VM_handler_10的内容如下:

3.png

这个VM_handler的作用是明显是入栈操作,即将后续4字节的VM_code压入虚拟机的栈,其[this + 20h]为栈指针。由此,可以进一步推断出VM_register结构如下:

Structure VM_register{
+0x00     ??
+0x04     ??
+0x08     ??
+0x0C     ??
+0x10     ??
+0x14     ??
+0x18     ??
+0x1C   SP   register
+0x20   IP   register
}

该虚拟机指令可表示如下:

21             0x70          0x00 0x00 0x00 0x2F                         push 0x2F

到此为止,笔者已经分析了25字节的VM_code和3个VM_handler以及一部分VM_register,后续内容还是留给感兴趣的读者。此外,其被虚拟机保护的代码被还原为等价的C代码也上传到了Github,供读者参考。

3.编程实现小型虚拟机

根据已有的源代码,可以很方便的构造小型虚拟机。首先来看一下该源代码,虚拟机使用的VM_register结构如下图:

4.png

其中,cf为标志位,*db指向用户输入数据。虚拟机中的VM_loop是通过一个while-switch结构实现的:

44.png

上图中的r.ip指向虚拟机的VM_code,该字节码被定义为一个字节数组,如图:

444.png

而完整的虚拟机类,即VM类如下图:

4444.png

其中,结构体REG即为VM_register,为VM类的第一个成员。虚拟机中的各个VM_handler也可以在该类中找到。在每次VM_loop中,虚拟机读取 VM_code,并选择执行响应的VM_handler。

基于这些已有代码,构造一个定制的虚拟机变得非常简单。可以使用已有的VM_handler和VM_register,也可以增加更多的VM_handler,并适当地增加一些混淆。增加混淆的VM_handler会使虚拟机保护更加难以分析。

在已有的框架之上,定制虚拟机的问题是变为设计一段VM_code用来实现所需目的即可。比如说,如果我们需要检查用户输入是否为0x27字节的16进制数字,则可以使用如下VM_code:

       PUSHD,  0x00, 0x00, 0x00, 0x2f,
       POP,    0x30,
       MOVD,   0x00,     //loop begin
       XOR,    0x22,
       CMP,    0x02,
       JZ,     0x33,
       INCD,
       PUSHD,  0x00, 0x00, 0x00, 0x46,
       POP,    0x10,
       CMP,    0x01,
       JG,     0x27,     //error               user-input < ‘F’  ascII:0x46
       PUSHD,  0x00, 0x00, 0x00, 0x30,
       POP,    0x10,
       CMP,    0x01,
       JL,     0x16,     //error               user-input > ‘0’  ascII:0x30
       PUSHD,  0x00, 0x00, 0x00, 0x39,
       POP,    0x10,
       CMP,    0x01,
       JL,     0x0b,     //correct             user-input < ’9’  ascII:0x39
       PUSHD,  0x00, 0x00, 0x00, 0x41,
       POP,    0x01,
       CMP,    0x01,
       JL,     0x06,     //error               user-input < ‘A’  ascII:0x41
       XOR,    0x00,
       CMP,    0x00,
       JZ,     0x03,
       XOR,    0x00,     //error out  R0 =   0         
       END,
       LOOP,   0x3E,     //loop end
       XOR,    0x00,     //correct out  R0   = 1
       INC,    0x00,
       END,

这部分代码是取自源文件中的VM_code,并加以相应的修改。在以上列表中,令用户输入的每一位与‘F’, ‘0’, ’9’, ’A’进行比较,从而判断出是否为16进制数字。当全部满足条件时,虚拟机运行结束后,R0寄存机为1,否则为0。

最后,用这段VM_code替换原先的代码,调试运行确认没有bug之后,就可以正常使用了。

4.小结

本文主要讨论了小型虚拟机保护的相关内容,前一部分逆向分析了一个使用虚拟机保护的CrackMe,后一部分讨论了如果定制一些被虚拟机保护的代码。在分析虚拟机保护过程中,最主要的步骤是分析每一个VM_handler的作用,从而理解被保护的代码含义;而在定制小型虚拟机保护是,关键是设计一段VM_code使之能够顺利完成其目的。文章中提到的所有材料可以在此链接下载到:https://github.com/zzz66686/simple-virtual-machine

源链接

Hacking more

...