Reference

系统学习vm虚拟机逆向_vm_dispatcher-CSDN博客

[原创]VM逆向,一篇就够了(上)-CTF对抗-看雪-安全社区|安全招聘|kanxue.com网鼎杯 signal 基础vm逆向及vm逆向分析思路 - nigacat - 博客园 (cnblogs.com)

[虚拟机逆向]UNCTF - 2019 EasyVm-CSDN博客

逆向之虚拟机保护 - 先知社区 (aliyun.com)

学习vm的笔记

vm正向

原理

程序运行时通过解释操作码(opcode)选择对应的函数(handle)执行

下图是虚拟机的基本结构

705

  • vm-init

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

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

  • opcode

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

因此在这种情况下如果要逆向该程序就需要对该程序的虚拟硬件结构进行逆向

分析方法

  • 给定可执行程序与opcode,逆向emulator,结合opcode,获得flag
  • 只给可执行程序,逆向emulator,构造opcode,读取flag

给定一个虚拟机程序,一般的解题步骤是这样的:

  1. 找到虚拟机入口即vm-init函数,寻找虚拟机的输入以及opcode位置
  2. 逆向emulator结构,dispatcher以及handler,分析清楚opcode的意义
  3. 写一个opcode的解释器
  4. 逆向算法

​ 调试过程中,在汇编层面调试当然是最基本最直接的方法,但是由于虚拟机Handler可能比较多,调试十分繁琐。
​ 若虚拟机内部没有很复杂的代码混淆,可以考虑使用IDA进行源码级调试,这对于快速整理emulator意义很有帮助。
​ 再进一步,可以结合IDA反编译伪代码,加上一些宏定义,加入输出,重新编译,可以十分快速的逆向整个emulator执行过程。

实现自己的小型虚拟机

结构体

vm_cpu

typedef struct vm_cpus
{
    int r1; 虚拟寄存器r1
    int r2; 虚拟寄存器r2
    int r3; 虚拟寄存器r3
    unsigned char *eip; 指向正在解释的opcode的地址
    vm_opcode op_list[OPCODE_N];    opcode列表,存放了所有的opcode及其对应的处理函数
}vm_cpu;

vm_opcode

typedef struct
{
    unsigned char opcode;
    void (*handle)(void *);

}vm_opcode;

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

然后开始实现解释器,对opcode进行解析,选择相应的handle函数,并且将相应的参数传递给handle函数,有handle函数模拟解释执行每一条指令

handles

void mov_(vm_cpu *cpu);      //change flag position
void xor_(vm_cpu *cpu);      //xor flag , 0x1-0x9
void read_(vm_cpu *cpu);    //call read ,read the flag
void vm_init(vm_cpu *cpu);
void vm_start(vm_cpu *cpu);
void vm_dispatcher(vm_cpu *cpu);
void check();
//handles具体实现
void mov_(vm_cpu *cpu)
{
    /*//mov指令的参数都隐藏在字节码中,指令表示后的一个字节是寄存器标识,
     * 第二到第五是要mov的数据在vm_stack上的偏移
    //我这里只是实现了从vm_stack上取数据和存数据到vm_stack上*/
    unsigned char *res=cpu->eip+1; //寄存器标识,即指令的第一个字节
    int *offset=(int *)(cpu->eip+2); //数据在vm_stack上的偏移,即指令的第二道第5字节
    char *dest=0;
    dest=vm_stack;

    switch (*res) {
        case 0xe1:
            cpu->r1=*(dest+*offset);// 从vm_stack上取数据,存储到cpu结构体的r1寄存器中
            break;
        case 0xe2:
            cpu->r2=*(dest+*offset);// 从vm_stack上取数据,存储到cpu结构体的r2寄存器中
            break;
        case 0xe3:
            cpu->r3=*(dest+*offset);  // 从vm_stack上取数据,存储到cpu结构体的r3寄存器中
            break;
        case 0xe4:
        {
            int x=cpu->r1; // 从cpu结构体的r1寄存器中取数据
            *(dest+*offset)=x; // 将数据存储到vm_stack的指定偏移位置上
            break;
        }
    }


    cpu->eip+=6; //mov指令占6个字节,eip向后偏移6位
}

void xor_(vm_cpu *cpu)
{
    int temp;
    temp=cpu->r1^cpu->r2;
    temp ^=0x12;
    cpu->r1=temp;
    cpu->eip+=1;  //xor指令占一个字节
}

void read_(vm_cpu *cpu)
{
    char *dest=vm_stack;
    read(0,dest,12); //用于往虚拟栈中读入数据
    cpu->eip+=1;
}

实现解释器

vm_init

//vm_init函数
void vm_init(vm_cpu *cpu)
{
    cpu->r1=0;
    cpu->r2=0;
    cpu->r3=0;
    cpu->eip=(unsigned char *)vm_code; //将eip指向opcode的地址

    cpu->op_list[0].opcode=0xf1;
    cpu->op_list[0].handle=(void (*)(void *))mov_;
   /*   mov:这是一个函数名,表示函数的地址。
        (*):这表示一个指针,指向一个函数。
        (void *):这表 示函数接受一个 void* 类型的参数。
         void:这表示函数不返回任何值。*/
   //这样就可以将0xF1的操作码与对应的handle函数关联在一起了

   //以下同理
   cpu->op_list[1].opcode=0xf2;
   cpu->op_list[1].handle=(void (*)(void *))xor_;

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

   vm_stack=(char *)malloc(0x512);
   memset(vm_stack,0,0x512);
}

vm_start

void vm_start(vm_cpu *cpu)
{
    cpu->eip=(unsigned char*)vm_code;
    while((*cpu->eip)!=RET)
    {
        vm_dispatcher(cpu);  //执行对应函数
    }
}

vm_dispatcher

//分发器
void vm_dispatcher(vm_cpu *cpu)
{
    int i;
    for(i=0;i<OPCODE_N;i++)
    {
        if(*cpu->eip==cpu->op_list[i].opcode)
        {
            cpu->op_list[i].handle(cpu);
            break;
        }
    }
}

checkflag

void check()
{
    int i;
    char *target=vm_stack;
    for(i=0;i<F_LEN;i++)
    {
        int offset=i+0x20;
        if((char)target[offset]!=enc_flag[i])
        {
            puts("error");
        }
        else{
            continue;
        }
    }
     puts("right");
}

将伪代码转为opcode

/*
    call read_
    MOV R1,flag[0]
    XOR
    MOV R1,0x20;    //这是将R1的值送到vm_stack+0x20的位置,后面的同上
    MOV R1,flag[1]
    XOR
    MOV R1,0x21;
    MOV R1,flag[2]
    XOR
    MOV R1,0x22
    MOV R1,flag[3]
    XOR
    MOV R1,0x23;
    MOV R1,flag[4]
    XOR
    MOV R1,0x24;
    MOV R1,flag[5]
    XOR
    MOV R1,0x25;
    MOV R1,flag[6]
    XOR
    MOV R1,0x26;
    MOV R1,flag[7]
    XOR
    MOV R1,0x26
    MOV R1,flag[7]
    XOR
    MOV R1,0X27
    MOV R1,flag[7]
    XOR
    MOV R1,0x28
    MOV R1,flag[7]
    XOR
    MOV R1,0X29 
    MOV R1,flag[7]
    XOR
    MOV R1,0x2A
    MOV R1,flag[7]
    XOR   
    MOV R1,0x2b
*/

然后将对应的伪代码转为自定义的opcode

unsigned char vm_code[] = {
    0xf5,
    0xf1,0xe1,0x0,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x20,0x00,0x00,0x00,
    0xf1,0xe1,0x1,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x21,0x00,0x00,0x00,
    0xf1,0xe1,0x2,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x22,0x00,0x00,0x00,
    0xf1,0xe1,0x3,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x23,0x00,0x00,0x00,
    0xf1,0xe1,0x4,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x24,0x00,0x00,0x00,
    0xf1,0xe1,0x5,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x25,0x00,0x00,0x00,
    0xf1,0xe1,0x6,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x26,0x00,0x00,0x00,
    0xf1,0xe1,0x7,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x27,0x00,0x00,0x00,
    0xf1,0xe1,0x8,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x28,0x00,0x00,0x00,
    0xf1,0xe1,0x9,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x29,0x00,0x00,0x00,
    0xf1,0xe1,0xa,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x2a,0x00,0x00,0x00,
    0xf1,0xe1,0xb,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x2b,0x00,0x00,0x00,
    0xf1,0xe1,0xc,0x00,0x00,0x00,0xf2,0xf1,0xe4,0x2c,0x00,0x00,0x00,
    0xf4
};

简易vm程序运行成功截图

-------未完待续----------

vm逆向

beginctf2024-babyvm

给了memory.vm 与opcode.vm

从opcode.vm里读取opcode,memory.vm里读取数据

写一个脚本读取数据

#include<string>

using namespace std;
int main()
{
    FILE *fp,*v5;
    int n=0,c;
    unsigned char a,b;
    fp=fopen("./memory.vm","rb");
    //fp=fopen("./opcode.vm","rb");
    while (feof(fp)==0)
    {
        n++;
        a=fgetc(fp);
        b=a;
        printf("0x%x,",b);
    }
    fclose(fp);
    return 0;
}

然后照着反编译的C语言把vm虚拟机要干什么打印出来

#include<iostream>
#include<stdint.h>;
using namespace std;
unsigned char opcode[]={...};
unsigned char memory[]={...};
int add(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    c=*((uint32_t*)&opcode[ep+8]);
    printf("Memory[%x] = Memory[%x] + Memory[%x]",a*4,b*4,c*4);
    return 3;
}

int sub(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    c=*((uint32_t*)&opcode[ep+8]);
    printf("Memory[%x] = Memory[%x] - Memory[%x]",a*4,b*4,c*4);
    return 3;
}


int mul(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    c=*((uint32_t*)&opcode[ep+8]);
    printf("Memory[%x] = Memory[%x] * Memory[%x]",a*4,b*4,c*4);
    return 3;
}

int divide(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    c=*((uint32_t*)&opcode[ep+8]);
    printf("Memory[%x] = Memory[%x] / Memory[%x]",a*4,b*4,c*4);
    return 3;
}

int rst_in(int ep)
{
    printf("in_pointer = 0"); 
    return 0;
}

int rst_out(int ep)
{
    printf("out_pointer = 0");
    return 0;
}

int pop_in(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    for(int i=0;i<b;i++)
    {
        printf("Memory[%x] = input[%x]\n",(a+i)*4,i);
        printf("in_pointer++\n");
    }
    return 2;
}

int pop_out(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    for(int i=0;i<b;i++)
    {
        printf("output[%x] = Memory[%x]\n",i,(a+i)*4);
        printf("in_pointer++\n");
    }
    return 2;
}
int read_buf(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    printf("input: %d",a);
    return 1;
}

int write_buf(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    printf("output: output[]");
    return 1;
}

int jmp(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    //if(b&&)
    printf("if(%d) && !Memory[%x] jmp %d\n",b,b*4,ep/4 + 2) ;
    printf("else jmp %d",ep/4 + a);
    return 2;
}

int njmp(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    //if(b&&)
    printf("if(%d) && !Memory[%x] jmp %d\n",b,b*4,ep/4 + 2) ;
    printf("else jmp %d",ep/4 + a);
    return 2;
}

int len(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    //if(b&&)
    printf("Memory[%x] = strlen(input)",b);
    return 2;
}
int main()
{
    int v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,buf;
    int in_pointer;
    int ep,op=0;
    while(1)
    {
        ep=opcode[op];
        if(ep==0xff)break;
        printf("%04d: ",op/4);
        switch ( ep )
    {
      case 1:
        v1 = add(op + 4);
        op += (v1 + 1)*4;
        break;
      case 2:
        v2 = sub(op + 4);
        op += (v2 + 1)*4;
        break;
      case 3:
        v3 = mul(op + 4);
        op += (v3 + 1)*4;
        break;
      case 4:
        v4 = divide(op + 4);
        op += (v4 + 1)*4;
        break;
      case 5:
        v5 = rst_in(op + 4);
        op += (v5 + 1)*4;
        break;
      case 6:
        v6 = rst_out(op + 4);
        op += (v6 + 1)*4;
        break;
      case 7:
        v7 = pop_in(op + 4);
        op += (v7 + 1)*4;
        break;
      case 8:
        v8 = pop_out(op + 4);
        op += (v8 + 1)*4;
        break;
      case 9:
        buf = read_buf(op + 4);
        op += (buf + 1)*4;
        break;
      case 0xA:
        v10 = write_buf(op + 4);
        op += (v10 + 1)*4;
        break;
      case 0xB:
        v11 = jmp(op + 4);
        op += (v11 + 1)*4;
        break;
      case 0xC:
        v12 = njmp(op + 4);
        op += (v12 + 1)*4;
        break;
      case 0xD:
        v13 = len(op + 4);
        op += (v13 + 1)*4;
        break;
      default:
          op += 4; 
        //exit(1);
        break;
    }
    cout<<endl;
    }
}

分析可得,这是一个线性方程

#include<iostream>
#include<stdint.h>;
using namespace std;
unsigned char opcode[]={...};
unsigned char memory[]={...};
int add(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    c=*((uint32_t*)&opcode[ep+8]);
    //printf("Memory[%x] = v%d + %d",a*4,b*4,memory[c*4]);
    return 3;
}

int sub(int ep)
{
    uint32_t a,b,c,d;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    c=*((uint32_t*)&opcode[ep+8]);
    d=*((uint32_t *)&memory[c*4]);
    printf(" == %d )\n",d);

    return 3;
}


int mul(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    c=*((uint32_t*)&opcode[ep+8]);
    if(b-600==0)printf("s.add(");
    printf("(v%d * %d) ",b-600,memory[c*4]);
    if(b-600<19) printf("+");
    //if(b-600==19)printf("\n");
    return 3;
}

int divide(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    c=*((uint32_t*)&opcode[ep+8]);
    //printf("Memory[%x] = Memory[%x] / Memory[%x]",a*4,b*4,c*4);
    return 3;
}

int rst_in(int ep)
{
    //printf("in_pointer = 0"); 
    return 0;
}

int rst_out(int ep)
{
    //printf("out_pointer = 0");
    return 0;
}

int pop_in(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    //for(int i=0;i<b;i++)
    {
    //    printf("Memory[%x] = input[%x]\n",(a+i)*4,i);
    //    printf("in_pointer++\n");
    }
    return 2;
}

int pop_out(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
//    for(int i=0;i<b;i++)
    {
    //    printf("output[%x] = Memory[%x]\n",i,(a+i)*4);
//        printf("in_pointer++\n");
    }
    return 2;
}
int read_buf(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
//    printf("input: %d",a);
    return 1;
}

int write_buf(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
//    printf("output: output[]");
    return 1;
}

int jmp(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    //if(b&&)
//    printf("if(%d) && !Memory[%x] jmp %d\n",b,b*4,ep/4 + 2) ;
//    printf("else jmp %d",ep/4 + a);
    return 2;
}

int njmp(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    //if(b&&)
//    printf("if(%d) && !Memory[%x] jmp %d\n",b,b*4,ep/4 + 2) ;
//    printf("else jmp %d",ep/4 + a);
    return 2;
}

int len(int ep)
{
    uint32_t a,b,c;
    a=*((uint32_t*)&opcode[ep]);
    b=*((uint32_t*)&opcode[ep+4]);
    //if(b&&)
//    printf("Memory[%x] = strlen(input)",b);
    return 2;
}
int main()
{
    int v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,buf;
    int in_pointer;
    int ep,op=0;
    while(1)
    {
        ep=opcode[op];
        if(ep==0xff)break;
        //printf("%04d: ",op/4);
        switch ( ep )
    {
      case 1:
        v1 = add(op + 4);
        op += (v1 + 1)*4;
        break;
      case 2:
        v2 = sub(op + 4);
        op += (v2 + 1)*4;
        break;
      case 3:
        v3 = mul(op + 4);
        op += (v3 + 1)*4;
        break;
      case 4:
        v4 = divide(op + 4);
        op += (v4 + 1)*4;
        break;
      case 5:
        v5 = rst_in(op + 4);
        op += (v5 + 1)*4;
        break;
      case 6:
        v6 = rst_out(op + 4);
        op += (v6 + 1)*4;
        break;
      case 7:
        v7 = pop_in(op + 4);
        op += (v7 + 1)*4;
        break;
      case 8:
        v8 = pop_out(op + 4);
        op += (v8 + 1)*4;
        break;
      case 9:
        buf = read_buf(op + 4);
        op += (buf + 1)*4;
        break;
      case 0xA:
        v10 = write_buf(op + 4);
        op += (v10 + 1)*4;
        break;
      case 0xB:
        v11 = jmp(op + 4);
        op += (v11 + 1)*4;
        break;
      case 0xC:
        v12 = njmp(op + 4);
        op += (v12 + 1)*4;
        break;
      case 0xD:
        v13 = len(op + 4);
        op += (v13 + 1)*4;
        break;
      default:
          op += 4; 
        //exit(1);
        break;
    }
   // cout<<endl;
    }
}

输入以后用z3解