hgame2023 vm

news/2024/10/13 6:18:14

vm逆向

hgame2023 vm

简单翻阅一下发现,sub_140001000里面是vm_init,sub_1400010B0是主要的vm部分

查看vm_init部分,发现只知道cpu结构的大小以及初始值和24-32字节的结构,前24字节的结构未知,暂时还无法构建cpu的结构,需要更多的信息。

分析下面两图的函数可以得知,opcode总共有8个,byte_140005360的取值很多是0-7,故byte_140005360为opcode数组。看result类型可知opcode为4字节。(unsigned int *)(a1+24)在操作这个这个数组,因此a1+24为4字节的eip。sub_1400010B0中返回a1+32这个一字节的数据,他应该是某种标志位,某个标志寄存器。在sub_140001940中result为8字节,因此handle为8字节。在分析中,要确定cpu数组a1中各个成员的大小,以便创建cpu结构。

取v2 = opcode[a1[6] + 1]; 印证了a1[6]即a1+24为eip,此处为eip+1

此处出现了a1[7],而且a1[7]先自增再存入内容

下一个指令是a1[7]--,而且是先减后用,搭配上数组140005D40仅有上下两图中的指令操作,得出a1[7]应为esp,操作的数组为栈空间。

定义各种运算

此处涉及到a1+32的功能,根据a1和a1+4处的内容为a1+32号位置赋值,相等为0不等为1。联想到cmp指令操作的寄存器,猜测a1+32应该是ZF标志位。

至此,可以猜测cpu的结构。前24字节为6个通用寄存器,每个4字节。还有4字节eip,4字节esp和1字节ZF。创建结构体

typedef struct
{unsigned int R[6];      unsigned int eip;unsigned int esp;char          zf; vm_opcode op_list[OPCODE_N];    
}vm_cpu;
typedef struct
{unsigned int opcode;void (*handle)(void*);
}vm_opcode;

修改类型让其更易读

可看到指令对应的函数已经被修复

修复后的指令功能识别

其他指令分析同理。写脚本打印一下执行流程。

#include<stdio.h>
#include <stdlib.h>int main(){unsigned char opcode[]={0, 3, 2, 0, 3, 0, 2, 3, 0, 0, 0, 0, 0, 2, 1, 0, 0, 3, 2, 0x32, 3, 0, 2, 3, 0, 0, 0, 0,3, 0, 1, 0, 0, 3, 2, 0x64, 3, 0, 2, 3, 0, 0, 0, 0, 3, 3, 1, 0, 0, 3, 0, 8, 0, 2, 2, 1, 3, 4, 1, 0, 3, 5, 2, 0, 3, 0, 1, 2, 0, 2, 0, 1, 1, 0, 0, 3, 0, 1, 3, 0, 3, 0, 0, 2, 0, 3, 0, 3, 1, 0x28, 4, 6, 0x5f, 5, 0, 0, 3, 3, 0, 2, 1, 0, 3, 2, 0x96, 3, 0, 2, 3, 0, 0,0, 0, 4, 7, 0x88, 0, 3, 0, 1, 3, 0, 3, 0, 0, 2, 0, 3, 0, 3, 1, 0x28, 4, 7, 0x63, 0xff, 0xff};unsigned int r[6]={0, 0, 0, 0, 0, 0};int zf=0;unsigned int esp=0,eip=0;unsigned int input_and_mem[]={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x9B,0x0A8,0x2, 0xBC,0x0AC,0x9C,0x0CE,0x0FA,0x2, 0xB9,0x0FF,0x3A,0x74,0x48,0x19,0x69,0x0E8,0x3, 0xCB,0x0C9,0x0FF,0x0FC,0x80,0x0D6,0x8D,0x0D7,0x72,0x0, 0xA7,0x1D,0x3D,0x99,0x88,0x99,0x0BF,0x0E8,0x96,0x2E,0x5D,0x57,0x0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0xC9,0x0A9,0x0BD,0x8B,0x17,0x0C2,0x6E,0x0F8,0x0F5,0x6E,0x63,0x63,0x0D5,0x46,0x5D,0x16,0x98,0x38,0x30,0x73,0x38,0x0C1,0x5E,0x0ED,0x0B0,0x29,0x5A,0x18,0x40,0x0A7,0x0FD,0x0A,0x1E,0x78,0x8B,0x62,0x0DB,0x0F,0x8F,0x9C,0x0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x4800,0x0F100,0x4000,0x2100,0x3501,0x6400,0x7801,0x0F900,0x1801,0x5200,0x2500,0x5D01,0x4700,0x0FD00,0x6901,0x5C00,0x0AF01,0x0B200,0x0EC01,0x5201,0x4F01,0x1A01,0x5000,0x8501,0x0CD00,0x2300,0x0F800,0x0C00,0x0CF00,0x3D01,0x4501,0x8200,0x0D201,0x2901,0x0D501,0x601,0x0A201,0x0DE00,0x0A601,0x0CA01,0x0, 0, 0, 0, 0, 0, 0, 0, 0, 0};unsigned int stack[80]={0};int v2;FILE *fp = fopen("log.txt", "a+");while(opcode[eip]!=0xff){switch (opcode[eip]){case 0:v2=opcode[eip+1];if(v2){switch (v2){case 1:input_and_mem[r[2]]=r[0];fprintf(fp,"input_and_mem[%d]=%d\n",r[2],r[0]);break;case 2:*(r+opcode[eip+2])=*(r+opcode[eip+3]);fprintf(fp,"r[%d]=r[%d]\n",opcode[eip+2],opcode[eip+3]);break;case 3:*(r+opcode[eip+2])=opcode[eip+3];fprintf(fp,"r[%d]=%d\n",opcode[eip+2],opcode[eip+3]);break;default:break;}}else{r[0]=input_and_mem[r[2]];fprintf(fp,"r[0]=input_and_mem[%d] %d\n",r[2],r[0]);}eip=eip+4;break;case 1:v2=opcode[eip+1];if(v2){switch (v2){case 1:stack[++esp]=r[0];fprintf(fp,"stack[%d]=r[0] %d\n",esp,r[0]);break;case 2:stack[++esp]=r[2];fprintf(fp,"stack[%d]=r[2] %d\n",esp ,r[2]);break;case 3:stack[++esp]=r[3];fprintf(fp,"stack[%d]=r[3] %d\n",esp ,r[3]);break;}}else{stack[++esp]=r[0];fprintf(fp,"stack[%d]=r[0] %d\n",esp ,r[0]  );}eip=eip+2;break;case 2:v2=opcode[eip+1];if(v2){switch (v2){case 1:r[1]=stack[esp--];fprintf(fp,"r[1]=stack[%d] %d\n",esp+1,r[1]);break;case 2:r[2]=stack[esp--];fprintf(fp,"r[2]=stack[%d] %d\n",esp+1,r[2]);break;case 3:r[3]=stack[esp--];fprintf(fp,"r[3]=stack[%d] %d\n",esp+1 ,r[3]);break;}}else{r[0]=stack[esp--];fprintf(fp,"r[0]=stack[%d] %d\n",esp+1 ,r[0]);}eip=eip+2;break;case 3:switch (opcode[eip+1]){case 0:*(r+opcode[eip+2])+=*(r+opcode[eip+3]);fprintf(fp,"r[%d]=r[%d]+r[%d] %d\n",opcode[eip+2],opcode[eip+2],opcode[eip+3],*(r+opcode[eip+2]));break;case 1:*(r+opcode[eip+2])-=*(r+opcode[eip+3]);fprintf(fp,"r[%d]=r[%d]-r[%d] %d\n",opcode[eip+2],opcode[eip+2],opcode[eip+3] ,*(r+opcode[eip+2]));break;case 2:*(r+opcode[eip+2])*=*(r+opcode[eip+3]);fprintf(fp,"r[%d]=r[%d]*r[%d] %d\n",opcode[eip+2],opcode[eip+2],opcode[eip+3] ,*(r+opcode[eip+2]));break;case 3:*(r+opcode[eip+2])^=*(r+opcode[eip+3]);fprintf(fp,"r[%d]=r[%d]^r[%d] %d\n",opcode[eip+2],opcode[eip+2],opcode[eip+3] ,*(r+opcode[eip+2]) );break;case 4:*(r+opcode[eip+2])<<=*(r+opcode[eip+3]);fprintf(fp,"r[%d]=r[%d]<<r[%d] %d\n",opcode[eip+2],opcode[eip+2],opcode[eip+3],*(r+opcode[eip+2]));*(r+opcode[eip+2])&=0xff00u;fprintf(fp,"r[%d]=r[%d]&0xff00u %d\n",opcode[eip+2],opcode[eip+2],*(r+opcode[eip+2]));break;case 5:*(r+opcode[eip+2])=*(r+opcode[eip+2]) >> *(r+opcode[eip+3]);fprintf(fp,"r[%d]=r[%d]>>r[%d] %d\n",opcode[eip+2],opcode[eip+2],opcode[eip+3],*(r+opcode[eip+2]));break;default:break;}eip=eip+4;break;case 4:if(r[0]==r[1]){zf=0;fprintf(fp,"cmp r0 r1 r0==r1 zf=0\n");}else{zf=1;fprintf(fp,"cmp r0 r1 r0!=r1 zf=1\n");}eip=eip+1;break;    case 5:eip=opcode[eip+1];fprintf(fp,"jmp %d\n",eip);break;case 6:if(zf){eip=eip+2;fprintf(fp,"jnz %d\n",eip);}else{eip=opcode[eip+1];fprintf(fp,"jnz %d\n",eip);}break;case 7:if(zf){eip=opcode[eip+1];fprintf(fp,"jz %d\n",eip);}else{eip=eip+2;fprintf(fp,"jz %d\n",eip);}break;default:break;}}return 0;
}

编写代码时一定仔细检查

log:r[2]=0
r[2]=r[2]+r[3] 0
r[0]=input_and_mem[0] 0
r[1]=r[0]
r[2]=50
r[2]=r[2]+r[3] 50
r[0]=input_and_mem[50] 155
r[1]=r[1]+r[0] 155
r[2]=100
r[2]=r[2]+r[3] 100
r[0]=input_and_mem[100] 201
r[1]=r[1]^r[0] 82
r[0]=8
r[2]=r[1]
r[1]=r[1]<<r[0] 20992
r[1]=r[1]&0xff00u 20992
r[2]=r[2]>>r[0] 0
r[1]=r[1]+r[2] 20992
r[0]=r[1]
stack[1]=r[0] 20992
r[0]=1
r[3]=r[3]+r[0] 1
r[0]=r[3]
r[1]=40
cmp r0 r1 r0!=r1 zf=1
jnz 93
jmp 0r[2]=0
r[2]=r[2]+r[3] 1
r[0]=input_and_mem[1] 0
r[1]=r[0]
r[2]=50
r[2]=r[2]+r[3] 51
r[0]=input_and_mem[51] 168
r[1]=r[1]+r[0] 168
r[2]=100
r[2]=r[2]+r[3] 101
r[0]=input_and_mem[101] 169
r[1]=r[1]^r[0] 1
r[0]=8
r[2]=r[1]
r[1]=r[1]<<r[0] 256
r[1]=r[1]&0xff00u 256
r[2]=r[2]>>r[0] 0
r[1]=r[1]+r[2] 256
r[0]=r[1]
stack[2]=r[0] 256
r[0]=1
r[3]=r[3]+r[0] 2
r[0]=r[3]
r[1]=40
cmp r0 r1 r0!=r1 zf=1
jnz 93
jmp 0....r[2]=0
r[2]=r[2]+r[3] 37
r[0]=input_and_mem[37] 0
r[1]=r[0]
r[2]=50
r[2]=r[2]+r[3] 87
r[0]=input_and_mem[87] 46
r[1]=r[1]+r[0] 46
r[2]=100
r[2]=r[2]+r[3] 137
r[0]=input_and_mem[137] 15
r[1]=r[1]^r[0] 33
r[0]=8
r[2]=r[1]
r[1]=r[1]<<r[0] 8448
r[1]=r[1]&0xff00u 8448
r[2]=r[2]>>r[0] 0
r[1]=r[1]+r[2] 8448
r[0]=r[1]
stack[38]=r[0] 8448
r[0]=1
r[3]=r[3]+r[0] 38
r[0]=r[3]
r[1]=40
cmp r0 r1 r0!=r1 zf=1
jnz 93
jmp 0r[2]=0
r[2]=r[2]+r[3] 38
r[0]=input_and_mem[38] 0
r[1]=r[0]
r[2]=50
r[2]=r[2]+r[3] 88
r[0]=input_and_mem[88] 93
r[1]=r[1]+r[0] 93
r[2]=100
r[2]=r[2]+r[3] 138
r[0]=input_and_mem[138] 143
r[1]=r[1]^r[0] 210
r[0]=8
r[2]=r[1]
r[1]=r[1]<<r[0] 53760
r[1]=r[1]&0xff00u 53760
r[2]=r[2]>>r[0] 0
r[1]=r[1]+r[2] 53760
r[0]=r[1]
stack[39]=r[0] 53760
r[0]=1
r[3]=r[3]+r[0] 39
r[0]=r[3]
r[1]=40
cmp r0 r1 r0!=r1 zf=1
jnz 93
jmp 0r[2]=0
r[2]=r[2]+r[3] 39
r[0]=input_and_mem[39] 0
r[1]=r[0]
r[2]=50
r[2]=r[2]+r[3] 89
r[0]=input_and_mem[89] 87
r[1]=r[1]+r[0] 87
r[2]=100
r[2]=r[2]+r[3] 139
r[0]=input_and_mem[139] 156
r[1]=r[1]^r[0] 203
r[0]=8
r[2]=r[1]
r[1]=r[1]<<r[0] 51968
r[1]=r[1]&0xff00u 51968
r[2]=r[2]>>r[0] 0
r[1]=r[1]+r[2] 51968
r[0]=r[1]
stack[40]=r[0] 51968
r[0]=1
r[3]=r[3]+r[0] 40
r[0]=r[3]
r[1]=40
cmp r0 r1 r0==r1 zf=0
jnz 95r[3]=0
r[1]=stack[40] 51968
r[2]=150
r[2]=r[2]+r[3] 150
r[0]=input_and_mem[150] 18432
cmp r0 r1 r0!=r1 zf=1
jz 136

分析可以发现,虽然步骤很多,但是通过jmp 0 指令分隔可分成40个部分,也就是40轮循环,每轮循环都类似。以第一轮为例

r[2]=0
r[2]=r[2]+r[3] 0
r[0]=input_and_mem[0] 0	
r[1]=r[0]					//r1=input_and_mem[0]
r[2]=50
r[2]=r[2]+r[3] 50
r[0]=input_and_mem[50] 155	//
r[1]=r[1]+r[0] 155			//r1=r1+input_and_mem[50]
r[2]=100
r[2]=r[2]+r[3] 100
r[0]=input_and_mem[100] 201
r[1]=r[1]^r[0] 82			//r1=r1^input_and_mem[100]
r[0]=8
r[2]=r[1]					//r2=r1
r[1]=r[1]<<r[0] 20992		//r1=r1<<8
r[1]=r[1]&0xff00u 20992		//r1=r1&0xff
r[2]=r[2]>>r[0] 0			//r2=r2>>8
r[1]=r[1]+r[2] 20992		//r1=r1+r2  也就是交换高低字节
r[0]=r[1]					//r0=r1
stack[1]=r[0] 20992			//push r0
r[0]=1
r[3]=r[3]+r[0] 1
r[0]=r[3]
r[1]=40
cmp r0 r1 r0!=r1 zf=1
jnz 93
jmp 0

也就是input_and_mem里前40位是输入,50-90位是加运算的数据,100-140位是异或的数据。在最后一次跳转中使用pop弹出了栈顶的数据,和input_and_mem里的150号数据进行比较,可以猜测150-190为加密后的数据,栈中的数据为加密后的数据,依次弹出和密文比较。还可推断是逐个比较,只要不正确即终止运行,因此此处只比较了一次。代码示意和脚本如下。

push (((input_and_mem[i]+input_and_mem[i+50])^input_and_mem[i+100])<<8)&0xff00u+(((input_and_mem[i]+input_and_mem[i+50])^input_and_mem[i+100])>>8)
#include<stdio.h>
#include <stdlib.h>
int main(){unsigned int cipher[40]={0x4800,0x0F100,0x4000,0x2100,0x3501,0x6400,0x7801,0x0F900,0x1801,0x5200,0x2500,0x5D01,0x4700,0x0FD00,0x6901,0x5C00,0x0AF01,0x0B200,0x0EC01,0x5201,0x4F01,0x1A01,0x5000,0x8501,0x0CD00,0x2300,0x0F800,0x0C00,0x0CF00,0x3D01,0x4501,0x8200,0x0D201,0x2901,0x0D501,0x601,0x0A201,0x0DE00,0x0A601,0x0CA01};unsigned int key_add[40]={0x9B,0x0A8,0x2, 0xBC,0x0AC,0x9C,0x0CE,0x0FA,0x2, 0xB9,0x0FF,0x3A,0x74,0x48,0x19,0x69,0x0E8,0x3, 0xCB,0x0C9,0x0FF,0x0FC,0x80,0x0D6,0x8D,0x0D7,0x72,0x0, 0xA7,0x1D,0x3D,0x99,0x88,0x99,0x0BF,0x0E8,0x96,0x2E,0x5D,0x57};unsigned int key_xor[40]={0xC9,0x0A9,0x0BD,0x8B,0x17,0x0C2,0x6E,0x0F8,0x0F5,0x6E,0x63,0x63,0x0D5,0x46,0x5D,0x16,0x98,0x38,0x30,0x73,0x38,0x0C1,0x5E,0x0ED,0x0B0,0x29,0x5A,0x18,0x40,0x0A7,0x0FD,0x0A,0x1E,0x78,0x8B,0x62,0x0DB,0x0F,0x8F,0x9C};unsigned int temp;for(int i=0;i<40;i++){temp=cipher[39-i];temp=((temp<<8)&0xff00u)+(temp>>8);temp=temp^key_xor[i];temp=temp-key_add[i];printf("%c",temp);}return 0;
}
//hgame{y0ur_rever5e_sk1ll_i5_very_g0od!!}

参考:

  1. https://mp.weixin.qq.com/s/aVPlvHkb6ohZ5K_zXVhs0w

  2. cmp指令 - 知乎 (zhihu.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/27075.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

unicode编码 asis_2019_unicorn_shop

这题就是让我们购买第四个商品当我们输入price为1337.0的时候他会报错,显示要我们只输入一个字符那么我们就要想怎样用一个字符来表示一个比1337还要大的数字 答案是unicode 编码 (题目的名字给了提示) 上这个网站我们直接查看2000的unicode编码把这个编码复制一下(这就是2…

C#开源的两款功能强大的录屏神器

ScreenToGif ScreenToGif是一款由C#语言开发且开源的操作简单、免费的屏幕录制和GIF动画制作神器。它可以帮助用户捕捉计算机屏幕上的实时动画,并将其保存为高质量的 GIF 图像格式。该工具不仅适用于技术支持、软件演示和教程制作,还可以用于创建有趣的 GIF 图片和动画表情。…

linux提取具体某一行的日志文件信息出来

在 Linux 系统中提取某一行可以使用命令行工具 sed、awk、grep、head 或 tail。 以下是各个命令的用法:sed 命令sed 命令是一个强大的文本处理工具,可以用来从文件或输入流中选择、编辑、替换某一行。下面的命令提取文件 file.txt 中的第 5 行:sed -n 5p file.txt其中,-n 表…

Eclipse Memory Analyzer (MAT)的安装后提示JDK版本不对要升级到jdk_17

背景 在启动MAT分析内存时报错:Version1.8.0 of the jvm is not suitable for this product,Version17 or greater isrequired。 问题原因很明显,我电脑的JDK和JRE的环境是1.8,需要提升版本:提示需要JDK 11才可以运行,但是我的环境变量配置的是JDK 8,这咋整?不想更改环…

线性代数

线性代数 线性 对于函数 \(f(x)\)(在实数域上)是线性的,当且仅当:对于任意 \(x,y,c\),有 \(f(x+y)=f(x)+f(y)\) 和 \(f(cx)=cf(x)\)。 定义域和值域:\(c\) 是“数”,\(x\) 和 \(f(x)\) 均为“可运算的元素” 向量表示与矩阵 向量 向量 \(\vec{v}\),是一个纵向的列表,列…

Python之禅,开宗明义:import this

#!/usr/bin/env pythonimport thisPython之禅: The Zen of Python, by Tim PetersBeautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better th…

高效遍历:C++中分隔字符串单词的3种方法详解与实例

概述:在C++中,遍历由空格分隔的字符串的单词有多种方法,包括使用`std::istringstream`、手动遍历字符和正则表达式。其中,`std::istringstream`是简单高效的选择,通过流提取单词。手动遍历字符较为繁琐,正则表达式方法更灵活但可能有性能开销。根据实际需求选择方法,本文…