前言:
学过计算机组成原理的应该都知道,程序的运行是靠cpu解释可执行文件中操作码来实现的功能,vm逆向顾名思义就是自己定义了小型cpu并定义了指令集,将程序的代码转换自定义的操作码(opcode),然后在程序执行时再通过解释这些操作码,选择对应的函数执行,从而实现程序原有的功能。
vm逆向基本原理:
vm_init:
虚拟机的入口函数,对虚拟机环境进行初始化,初始化一般包括
寄存器初始化(eax,ebx,ecx,edx,eip)
把handle函数和操作码连接在一起
给虚拟机的栈空间vm_stack分配内存
vm_run:
虚拟机开始运行的地方
vm_dispatcher:
调度器,解释opcode,并选择对应的handle函数执行,当handle执行完后会跳回这里,形成一个循环。
vm_handle:
处理器,当rip走到对应操作码时调用对应操作函数,并接受操作数。
opcode :
程序可执行代码转换成的操作码
在这种情况下,如果要逆向程序,就需要对整个emulator结构进行逆向,理解程序功能,还需要结合opcode进行分析,整个程序逆向工程将会十分繁琐。这是一个一般虚拟机结构:
vm_lables:
有的虚拟机涉及lable的创造和调用,起到跳转目的
分析方法
在比赛中,虚拟机题目常常有两种考法:
给可执行程序和opcode,逆向emulator,结合opcode文件,推出flag
只给可执行程序,逆向emulator,构造opcode,读取flag
拿到一个虚拟机之后,一般有以下几个逆向过程:
分析虚拟机入口,搞清虚拟机的输入,或者opcode位置
理清虚拟机结构,包括Dispatcher和各个Handler
逆向各个Handler,分析opcode的意义
根据opcode运行时打印出对应汇编代码,根据汇编代码逻辑进行逆向
一个简单vm虚拟机实例:
vm_cpu结构体
1 | typedef struct |
vm_opcode结构体
1 | typedef struct |
vm_init()
1 |
|
handles(示例)
1 | void mov(vm_cpu *cpu); |
vm_code
1 | unsigned char vm_code[] = { |
vm_stack
一般是一个全局数组,用于存放虚拟机的栈
1 | vm_stack = malloc(0x512); |
vm_run
1 | void vm_run(vm_cpu *cpu) |
vm_dispatcher
1 | void vm_dispatcher(vm_cpu *cpu) |
做题流程:
先根据上面的特征静态分析判断函数是vm中的哪一部分,如果静态分析困难,那就动调一下
找到对应部分后创建上述结构体来帮助分析
自己把vm还原在vscode里,并在dispatcher部分加上打印这条指令的代码,有的时候也可以打印stack,和当前执行完指令的内存状态(各个变量的值)
根据打印出的汇编指令,手动逆向
具体使用方法还需在实战中不断练习,具体做题流程可看我的vm题单真题