# vm逆向
vm逆向
对上面博客的总结。
引
vm逆向题,一般是小型虚拟机程序,可以理解为一种模拟器,有start,dispatcher,opcode等结构。常见使用while-switch/if这类循环+选择结构来实现简单的虚拟机模拟,如下:
逆向重点:
-
分析入口,搞清输入和opcode的位置
-
理清结构,包括dispatcher和各handler
-
逆向各handler,分析opcode意义
小型虚拟机实现
目标:
-
定义一套opcode
-
实现opcode解释器
要点:
-
定义系列虚拟寄存器ri,指针eip指向正在解释的地址,opcode及其对应处理函数的列表
-
初始化。将寄存器置0,eip指向opcode首地址,并初始化opcode列表每一项的opcode值以及对应的handle函数。
-
start。用while循环结构,如果eip目前指向的opcode不为ret(或其他标识程序结束的指令),那么调用dispatcher来执行当前eip所指向的指令。
-
dispatcher。在opcode列表中查找当前需要的opcode,如果找到,那么执行对应的handler函数,退出遍历。
-
注意要关注handler函数所占字节数,函数执行完毕后要将eip偏移相应位移。
例题:moectf2024 moejvav
用jadx打开,看Main:
package defpackage;
import exceptions.BuDaoLePaoException;
import exceptions.DxIsNanTongException;
import exceptions.GenshinImpactException;
import exceptions.LuoIsNotDogException;
import exceptions.NotSigninException;
import exceptions.NullCafeException;
import exceptions.StarrySkyMeowNotFoundException;
import exceptions.TokioEatWhatException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/* renamed from: Main reason: default package */
/* loaded from: moejvav.jar:Main.class */
public class Main {
public static void main(String[] args) {
System.out.println("这里是moejvav! 请输入你的flag:");
String flag = new Scanner(System.in).next();
if (flag.length() != 44) {
System.out.println("flag长度不对");
return;
}
List<Byte> array = new ArrayList<>();
for (byte b : flag.getBytes(StandardCharsets.UTF_8)) {
array.add(Byte.valueOf((byte) ((Byte.valueOf(b).byteValue() ^ 202) + 32)));
}
int[] vmInsn = new int[310];
vmInsn[0] = 0;
vmInsn[1] = 1;
vmInsn[2] = 60;
vmInsn[3] = 2;
vmInsn[4] = -20;
vmInsn[5] = 6;
vmInsn[6] = -25;
vmInsn[7] = 0;
vmInsn[8] = 1;
vmInsn[9] = 60;
vmInsn[10] = 2;
vmInsn[11] = -20;
vmInsn[12] = 6;
vmInsn[13] = -27;
vmInsn[14] = 0;
vmInsn[15] = 1;
vmInsn[16] = 60;
vmInsn[17] = 2;
vmInsn[18] = -20;
vmInsn[19] = 6;
vmInsn[20] = -33;
vmInsn[21] = 0;
vmInsn[22] = 1;
vmInsn[23] = 60;
vmInsn[24] = 2;
vmInsn[25] = -20;
vmInsn[26] = 6;
vmInsn[27] = -31;
vmInsn[28] = 0;
vmInsn[29] = 1;
vmInsn[30] = 60;
vmInsn[31] = 2;
vmInsn[32] = -20;
vmInsn[33] = 6;
vmInsn[34] = -50;
vmInsn[35] = 0;
vmInsn[36] = 1;
vmInsn[37] = 60;
vmInsn[38] = 2;
vmInsn[39] = -20;
vmInsn[40] = 6;
vmInsn[41] = -36;
vmInsn[42] = 0;
vmInsn[43] = 1;
vmInsn[44] = 60;
vmInsn[45] = 2;
vmInsn[46] = -20;
vmInsn[47] = 6;
vmInsn[48] = -39;
vmInsn[49] = 0;
vmInsn[50] = 1;
vmInsn[51] = 60;
vmInsn[52] = 2;
vmInsn[53] = -20;
vmInsn[54] = 6;
vmInsn[55] = -24;
vmInsn[56] = 0;
vmInsn[57] = 1;
vmInsn[58] = 60;
vmInsn[59] = 2;
vmInsn[60] = -20;
vmInsn[61] = 6;
vmInsn[62] = -52;
vmInsn[63] = 0;
vmInsn[64] = 1;
vmInsn[65] = 60;
vmInsn[66] = 2;
vmInsn[67] = -20;
vmInsn[68] = 6;
vmInsn[69] = -29;
vmInsn[70] = 0;
vmInsn[71] = 1;
vmInsn[72] = 60;
vmInsn[73] = 2;
vmInsn[74] = -20;
vmInsn[75] = 6;
vmInsn[76] = -52;
vmInsn[77] = 0;
vmInsn[78] = 1;
vmInsn[79] = 14;
vmInsn[80] = 2;
vmInsn[81] = 5;
vmInsn[82] = 6;
vmInsn[83] = -64;
vmInsn[84] = 0;
vmInsn[85] = 1;
vmInsn[86] = 14;
vmInsn[87] = 2;
vmInsn[88] = 5;
vmInsn[89] = 6;
vmInsn[90] = -58;
vmInsn[91] = 0;
vmInsn[92] = 1;
vmInsn[93] = 14;
vmInsn[94] = 2;
vmInsn[95] = 5;
vmInsn[96] = 6;
vmInsn[97] = -63;
vmInsn[98] = 0;
vmInsn[99] = 1;
vmInsn[100] = 14;
vmInsn[101] = 2;
vmInsn[102] = 5;
vmInsn[103] = 6;
vmInsn[104] = -52;
vmInsn[105] = 0;
vmInsn[106] = 1;
vmInsn[107] = 14;
vmInsn[108] = 2;
vmInsn[109] = 5;
vmInsn[110] = 6;
vmInsn[111] = -90;
vmInsn[112] = 0;
vmInsn[113] = 1;
vmInsn[114] = 14;
vmInsn[115] = 2;
vmInsn[116] = 5;
vmInsn[117] = 6;
vmInsn[118] = -39;
vmInsn[119] = 0;
vmInsn[120] = 1;
vmInsn[121] = 14;
vmInsn[122] = 2;
vmInsn[123] = 5;
vmInsn[124] = 6;
vmInsn[125] = -43;
vmInsn[126] = 0;
vmInsn[127] = 1;
vmInsn[128] = 14;
vmInsn[129] = 2;
vmInsn[130] = 5;
vmInsn[131] = 6;
vmInsn[132] = 26;
vmInsn[133] = 0;
vmInsn[134] = 1;
vmInsn[135] = 14;
vmInsn[136] = 2;
vmInsn[137] = 5;
vmInsn[138] = 6;
vmInsn[139] = 25;
vmInsn[140] = 0;
vmInsn[141] = 1;
vmInsn[142] = 14;
vmInsn[143] = 2;
vmInsn[144] = 5;
vmInsn[145] = 6;
vmInsn[146] = -49;
vmInsn[147] = 0;
vmInsn[148] = 1;
vmInsn[149] = 14;
vmInsn[150] = 2;
vmInsn[151] = 5;
vmInsn[152] = 6;
vmInsn[153] = -64;
vmInsn[154] = 0;
vmInsn[155] = 1;
vmInsn[156] = 10;
vmInsn[157] = 2;
vmInsn[158] = 5;
vmInsn[159] = 6;
vmInsn[160] = -51;
vmInsn[161] = 0;
vmInsn[162] = 1;
vmInsn[163] = 10;
vmInsn[164] = 2;
vmInsn[165] = 5;
vmInsn[166] = 6;
vmInsn[167] = 25;
vmInsn[168] = 0;
vmInsn[169] = 1;
vmInsn[170] = 10;
vmInsn[171] = 2;
vmInsn[172] = 5;
vmInsn[173] = 6;
vmInsn[174] = -45;
vmInsn[175] = 0;
vmInsn[176] = 1;
vmInsn[177] = 10;
vmInsn[178] = 2;
vmInsn[179] = 5;
vmInsn[180] = 6;
vmInsn[181] = -55;
vmInsn[182] = 0;
vmInsn[183] = 1;
vmInsn[184] = 10;
vmInsn[185] = 2;
vmInsn[186] = 5;
vmInsn[187] = 6;
vmInsn[188] = -47;
vmInsn[189] = 0;
vmInsn[190] = 1;
vmInsn[191] = 10;
vmInsn[192] = 2;
vmInsn[193] = 5;
vmInsn[194] = 6;
vmInsn[195] = 24;
vmInsn[196] = 0;
vmInsn[197] = 1;
vmInsn[198] = 10;
vmInsn[199] = 2;
vmInsn[200] = 5;
vmInsn[201] = 6;
vmInsn[202] = -41;
vmInsn[203] = 0;
vmInsn[204] = 1;
vmInsn[205] = 10;
vmInsn[206] = 2;
vmInsn[207] = 5;
vmInsn[208] = 6;
vmInsn[209] = -60;
vmInsn[210] = 0;
vmInsn[211] = 1;
vmInsn[212] = 10;
vmInsn[213] = 2;
vmInsn[214] = 5;
vmInsn[215] = 6;
vmInsn[216] = 22;
vmInsn[217] = 0;
vmInsn[218] = 1;
vmInsn[219] = 10;
vmInsn[220] = 2;
vmInsn[221] = 5;
vmInsn[222] = 6;
vmInsn[223] = -40;
vmInsn[224] = 0;
vmInsn[225] = 1;
vmInsn[226] = 10;
vmInsn[227] = 2;
vmInsn[228] = 5;
vmInsn[229] = 6;
vmInsn[230] = -60;
vmInsn[231] = 0;
vmInsn[232] = 2;
vmInsn[233] = 14;
vmInsn[234] = 2;
vmInsn[235] = 10;
vmInsn[236] = 6;
vmInsn[237] = -15;
vmInsn[238] = 0;
vmInsn[239] = 2;
vmInsn[240] = 14;
vmInsn[241] = 2;
vmInsn[242] = 10;
vmInsn[243] = 6;
vmInsn[244] = 50;
vmInsn[245] = 0;
vmInsn[246] = 2;
vmInsn[247] = 14;
vmInsn[248] = 2;
vmInsn[249] = 10;
vmInsn[250] = 6;
vmInsn[251] = -51;
vmInsn[252] = 0;
vmInsn[253] = 2;
vmInsn[254] = 14;
vmInsn[255] = 2;
vmInsn[256] = 10;
vmInsn[257] = 6;
vmInsn[258] = -31;
vmInsn[259] = 0;
vmInsn[260] = 2;
vmInsn[261] = 14;
vmInsn[262] = 2;
vmInsn[263] = 10;
vmInsn[264] = 6;
vmInsn[265] = 50;
vmInsn[266] = 0;
vmInsn[267] = 2;
vmInsn[268] = 14;
vmInsn[269] = 2;
vmInsn[270] = 10;
vmInsn[271] = 6;
vmInsn[272] = 50;
vmInsn[273] = 0;
vmInsn[274] = 2;
vmInsn[275] = 14;
vmInsn[276] = 2;
vmInsn[277] = 10;
vmInsn[278] = 6;
vmInsn[279] = -35;
vmInsn[280] = 0;
vmInsn[281] = 2;
vmInsn[282] = 14;
vmInsn[283] = 2;
vmInsn[284] = 10;
vmInsn[285] = 6;
vmInsn[286] = 50;
vmInsn[287] = 0;
vmInsn[288] = 2;
vmInsn[289] = 14;
vmInsn[290] = 2;
vmInsn[291] = 10;
vmInsn[292] = 6;
vmInsn[293] = -35;
vmInsn[294] = 0;
vmInsn[295] = 2;
vmInsn[296] = 14;
vmInsn[297] = 2;
vmInsn[298] = 10;
vmInsn[299] = 6;
vmInsn[300] = 51;
vmInsn[301] = 0;
vmInsn[302] = 2;
vmInsn[303] = 14;
vmInsn[304] = 2;
vmInsn[305] = 10;
vmInsn[306] = 6;
vmInsn[307] = -17;
vmInsn[308] = 114514;
vmInsn[309] = 1919810;
Exception[] exceptions2 = {new BuDaoLePaoException(), new DxIsNanTongException(), new GenshinImpactException(), new LuoIsNotDogException(), new NotSigninException(), new NullCafeException(), new StarrySkyMeowNotFoundException(), new TokioEatWhatException(), new RuntimeException()};
int i = 0;
int store = 0;
while (i < vmInsn.length) {
int insn = vmInsn[i];
i++;
if (insn == 114514) {
break;
}
try {
throw exceptions2[insn];
break;
} catch (BuDaoLePaoException e) {
store = array.get(0).byteValue();
array.remove(0);
} catch (DxIsNanTongException e2) {
store ^= vmInsn[i];
i++;
} catch (GenshinImpactException e3) {
store += vmInsn[i];
i++;
} catch (LuoIsNotDogException e4) {
store &= vmInsn[i];
i++;
} catch (NotSigninException e5) {
store <<= vmInsn[i];
i++;
} catch (NullCafeException e6) {
store |= vmInsn[i];
i++;
} catch (StarrySkyMeowNotFoundException e7) {
i++;
if (store != vmInsn[i]) {
vmInsn[i] = 7;
}
} catch (TokioEatWhatException e8) {
vmInsn[i] = 8;
} catch (Exception e9) {
System.out.println("wrong flag, oh no...");
throw new RuntimeException(e9);
}
}
System.out.println("输入的flag正确!");
}
}
这里使用了try-catch抛出异常来实现switch分支选择结构,原理是throw出expections2数组中的函数,catch捕捉到对应编号的函数异常,编号即为该分支的case条件。这个虚拟机的opcode与其对应的handler可以明显地看出。每当opcode为0,取出flag的一个字符,opcode为1~5,对字符进行加密运算,opcode为6,进行一次check,若check不通过则说wrong flag。因此对每一次opcode6的结果一步步向上解密,直到遇到opcode0,则输出该字符,就能得到flag。
exp:
#include<bits/stdc++.h>
using namespace std;
int vmInsn[310];
int decode1(int x)
{
return (x-32)^202;
}
int cracker_and(int chip,int x)
{
for(int i=-255;i<=255;i++)
if(i&x==chip)return i;
}
int cracker_or(int chip,int x)
{
for(int i=-255;i<=255;i++)
if(i|x==chip)return i;
}
int main()
{
//vmInsn[...]=...
int cnt=0;
for(int i=0;;i++)
{
if(vmInsn[i]==114514)break;
if(vmInsn[i]!=6)continue;
int p=i,chip=vmInsn[p+1];
while(1)
{
if(vmInsn[p-1]==0)break;
p-=2;
int opnum=vmInsn[p+1];
switch(vmInsn[p])
{
case 1:chip^=opnum;
break;
case 2:chip-=opnum;
break;
case 3:chip=cracker_and(chip,opnum);
break;
case 4:chip>>=opnum;
break;
case 5:chip=cracker_or(chip,opnum);
break;
}
}
printf("%c",decode1(chip));
}
return 0;
//cout<<cnt;
}
//moectf{jvav_eXcEpt10n_h4ndl3r_1s_s0_c00o0o1}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?