吾爱破解 - LCG - LSG |安卓破解|病毒分析|破解软件|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

搜索
查看: 2527|回复: 7

[转贴] 逆向之虚拟机保护

[复制链接]
默小白 发表于 2019-1-29 15:50
本帖最后由 默小白 于 2019-1-29 16:13 编辑

转自:https://xz.aliyun.com/t/3851
考试周终于过去了,是时候又要开始学习了。所以就研究下逆向中的虚拟机保护技术,下面记录下学习的过程以及一些收获。


基础概念

逆向中的虚拟机保护是一种基于虚拟机的代码保护技术。它将基于x86汇编系统中的可执行代码转换为字节码指令系统的代码,来达到不被轻易逆向和篡改的目的。简单点说就是将程序的代码转换自定义的操作码(opcode),然后在程序执行时再通过解释这些操作码,选择对应的函数执行,从而实现程序原有的功能。

vm_start:

虚拟机的入口函数,对虚拟机环境进行初始化

vm_dispatcher:

调度器,解释opcode,并选择对应的handle函数执行,当handle执行完后会跳回这里,形成一个循环。

opcode :



序可执行代码转换成的操作码

虚拟机执行的基本流程


实现一个小型的虚拟机


这里我通过实现一个简化版的小型虚拟机来加深对虚拟机的认识,语言用的是C语言。要想实现虚拟机的话需要完成两个目标:

  1.定义一套opcode

  2.实现opcode的解释器

opcode只是一个标识,可以随便定义,这里我定义了4条指令,每条指令分别对应着一个字节的字节码。而opcode的解释器是用来对opcode进行解释,从而选择对应的handle函数执行。

定义opcode
  1. enum opcodes
  2. {
  3.     MOV = 0xf1,
  4.     XOR = 0xf2,
  5.     RET = 0xf4,
  6.     READ = 0xf5,
  7. };
复制代码
因为我只是为了理解,所以就只定义了几个常用指令。这里我用了共用体来定义opcode,比较方便。

实现解释器

opcode定义完后,就可以开始实现解释opcode的解释器了。解释器我们需要实现一个虚拟环境以及各个opcode对应的handle函数。虚拟环境则是真实物理机的一个虚拟,是自己定义的字节码运行的环境。

一些关键的结构体

vm_cpu

  1. typedef struct vm_cpus
  2. {
  3.     int r1; 虚拟寄存器r1
  4.     int r2; 虚拟寄存器r2
  5.     int r3; 虚拟寄存器r3
  6.     unsigned char *eip; 指向正在解释的opcode的地址
  7.     vm_opcode op_list[OPCODE_N];    opcode列表,存放了所有的opcode及其对应的处理函数
  8. }vm_cpu;</font>
复制代码

vm_opcode

  1. typedef struct
  2. {
  3.     unsigned char opcode;
  4.     void (*handle)(void *);

  5. }vm_opcode;
复制代码
其中 r1-r3是我定义的通用寄存器,用来传参或者是存放返回值,eip指向正在解释的opcode的地址,op_list则存放了所有opcode及其对应的handle函数。

实现了虚拟环境后就可以开始实现解释器了。解释器的功能就是对opcode解析,选择相应的handle函数,并且将相应的参数传递给handle函数。由handle函数来解释执行一条指令

关键函数


vm_init
  1. void vm_init(vm_cpu *cpu)   //初始化虚拟机环境
  2. {
  3.     cpu->r1 = 0;
  4.     cpu->r2 = 0;
  5.     cpu->r3 = 0;
  6.     cpu->eip = (unsigned char *)vm_code;    //将eip指向opcode的地址

  7.     cpu->op_list[0].opcode = 0xf1;
  8.     cpu->op_list[0].handle = (void (*)(void *))mov; //将操作字节码与对应的handle函数关联在一起

  9.     cpu->op_list[1].opcode = 0xf2;
  10.     cpu->op_list[1].handle = (void (*)(void *))xor;

  11.     cpu->op_list[2].opcode = 0xf5;
  12.     cpu->op_list[2].handle = (void (*)(void *))read_;

  13.     vm_stack = malloc(0x512);
  14.     memset(vm_stack,0,0x512);
  15. }
复制代码


vm_start

  1. void vm_start(vm_cpu *cpu)
  2. {
  3.     /*
  4.     进入虚拟机
  5.     eip指向要被解释的opcode地址
  6.     */
  7.     cpu->eip = (unsigned char*)opcodes;
  8.     while((*cpu->eip)!= RET)//如果opcode不为RET,就调用vm_dispatcher来解释执行
  9.     {
  10.         vm_dispatcher(*cpu->eip);
  11.     }
  12. }
复制代码
vm_dispatcher
  1. void vm_dispatcher(vm_cpu *cpu)
  2. {
  3.     int i;
  4.     for(i=0 ; i < OPCODE_N ; i++)
  5.     {
  6.         if(*cpu->eip == cpu->op_list[i].opcode)
  7.         {
  8.             cpu->op_list[i].handle(cpu);
  9.             break;
  10.         }
  11.     }

  12. }
复制代码

handles
  1. void mov(vm_cpu *cpu);      
  2. void xor(vm_cpu *cpu);      //xor flag
  3. void read_(vm_cpu *cpu);    //call read ,read the flag

  4. void xor(vm_cpu *cpu)
  5. {  
  6.     int temp;
  7.     temp = cpu->r1 ^ cpu->r2;
  8.     temp ^= 0x12;
  9.     cpu->r1 = temp;
  10.     cpu->eip +=1;                //xor指令占一个字节            
  11. }

  12. void read_(vm_cpu *cpu)
  13. {

  14.     char *dest = vm_stack;
  15.     read(0,dest,12);           //用于往虚拟机的栈上读入数据
  16.     cpu->eip += 1;            //read_指令占一个字节  
  17. }

  18. void mov(vm_cpu *cpu)
  19. {
  20.     //mov指令的参数都隐藏在字节码中,指令表示后的一个字节是寄存器标识,第二到第五是要mov的数据在vm_stack上的偏移
  21.     //我这里只是实现了从vm_stack上取数据和存数据到vm_stack上
  22.     unsigned char *res = cpu->eip + 1;  //寄存器标识
  23.     int *offset = (int *) (cpu->eip + 2);    //数据在vm_stack上的偏移
  24.     char *dest = 0;
  25.     dest = vm_stack;


  26.     switch (*res) {
  27.         case 0xe1:
  28.             cpu->r1 = *(dest + *offset);
  29.             break;   

  30.         case 0xe2:
  31.             cpu->r2 = *(dest + *offset);
  32.             break;   

  33.         case 0xe3:
  34.             cpu->r3 = *(dest + *offset);
  35.             break;   
  36.         case 0xe4:
  37.         {
  38.             int x = cpu->r1;
  39.             *(dest + *offset) = x;
  40.             break;

  41.         }
  42.     }   

  43.     cpu->eip += 6;
  44.     //mov指令占六个字节,所以eip要向后移6位
  45. }
复制代码

要执行的伪代码

解释器到这就实现完了。接下来是要将想要实现功能的伪代码转成自定义的opcode,伪代码的功能是从标准输入中读取12个字节的字符串,然后将读入的字符串每个字符与0x0还有0x12进行异或,并且将结果存储在虚拟机的栈上。写出来大致就是下面这样子
  1. /*
  2.     call read_
  3.     MOV R1,flag[0]
  4.     XOR
  5.     MOV R1,0x20;    //这是将R1的值送到vm_stack+0x20的位置,后面的同上
  6.     MOV R1,flag[1]
  7.     XOR
  8.     MOV R1,0x21;
  9.     MOV R1,flag[2]
  10.     XOR
  11.     MOV R1,0x22
  12.     MOV R1,flag[3]
  13.     XOR
  14.     MOV R1,0x23;
  15.     MOV R1,flag[4]
  16.     XOR
  17.     MOV R1,0x24;
  18.     MOV R1,flag[5]
  19.     XOR
  20.     MOV R1,0x25;
  21.     MOV R1,flag[6]
  22.     XOR
  23.     MOV R1,0x26;
  24.     MOV R1,flag[7]
  25.     XOR
  26.     MOV R1,0x26
  27.     MOV R1,flag[7]
  28.     XOR
  29.     MOV R1,0X27
  30.     MOV R1,flag[7]
  31.     XOR
  32.     MOV R1,0x28
  33.     MOV R1,flag[7]
  34.     XOR
  35.     MOV R1,0X29
  36.     MOV R1,flag[7]
  37.     XOR
  38.     MOV R1,0x2A
  39.     MOV R1,flag[7]
  40.     XOR   
  41.     MOV R1,0x2b
  42. */
复制代码

将它转换成对应的字节码,然后用解释器去解释执行就可以实现伪代码的功能。


  1. unsigned char vm_code[] = {
  2.    0xf5,
  3.    0xf1,0xe1,0x0,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x20,0x00,0x00,0x00,
  4.    0xf1,0xe1,0x1,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x21,0x00,0x00,0x00,
  5.    0xf1,0xe1,0x2,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x22,0x00,0x00,0x00,
  6.    0xf1,0xe1,0x3,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x23,0x00,0x00,0x00,
  7.    0xf1,0xe1,0x4,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x24,0x00,0x00,0x00,
  8.    0xf1,0xe1,0x5,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x25,0x00,0x00,0x00,
  9.    0xf1,0xe1,0x6,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x26,0x00,0x00,0x00,
  10.    0xf1,0xe1,0x7,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x27,0x00,0x00,0x00,
  11.    0xf1,0xe1,0x8,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x28,0x00,0x00,0x00,
  12.    0xf1,0xe1,0x9,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x29,0x00,0x00,0x00,
  13.    0xf1,0xe1,0xa,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x2a,0x00,0x00,0x00,
  14.    0xf1,0xe1,0xb,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x2b,0x00,0x00,0x00,
  15.    0xf1,0xe1,0xc,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x2c,0x00,0x00,0x00,
  16.    0xf4
  17. };
复制代码


至此,一个简化版的小型虚拟机就实现完了。我在虚拟机中实现了对输入字符串简单的异或加密,并将加密后的值存储到指定位置。我觉得这个过程是十分有意义的,让我加深了对虚拟机保护的了解。因为能力有限,所以就只实现了一个很简单的小型虚拟机。虚拟机相关的题目还没有做,等有时间再去做一下。

最后将代码放在附件上,有需要的可以下载。


20190114150609-de8edad6-17ca-1.zip (1.67 KB, 下载次数: 12)

免费评分

参与人数 6吾爱币 +7 热心值 +5 收起 理由
solly + 1 + 1 我很赞同!
PureT + 1 + 1 感谢分享!
自强 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
whm630987633 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
su.tim + 1 鼓励转贴优秀软件安全工具和文档!
黑的思想 + 2 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

logo_tu 发表于 2019-1-29 17:22
好厉害的样子哦
zhangtianwang 发表于 2019-1-29 17:26
FYX 发表于 2019-1-29 17:31
hzyh 发表于 2019-1-29 22:03

好厉害的样子哦,赞
埖落 发表于 2019-1-30 08:47
学习了,来看看 分享
fangchang819 发表于 2019-1-30 11:34
感谢楼主分享
CNSingle_net 发表于 2019-4-22 14:42
解释执行只写了部分的实现核心,首当其冲的堆栈维护问题需要楼主细致研究一下,感谢分享,吾爱有你更精彩!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:禁止回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2019-12-9 08:58

Powered by Discuz!

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表