很恶心的双重vm,通宵大概做了十个小时
main函数:
1 | __int64 __fastcall main(int a1, char **a2, char **a3) |
InitEmulator函数:
1 | void *__fastcall InitEmu(const char *filename) |
解析了MASM文件的文件头和结构,包含MASM文件头、字节码段、文本段和一个暂时作用还未知的段(后面发现是用于选择vm函数),观察内存中的v3:
1 | [heap]:000055555555C490 dq offset opcode |
其中的数值是每段的长度,对应文件头(19字节)中的:
1 | 4D 41 53 4D ; MASM |
ExecuteOpc函数:
1 | __int64 __fastcall ExecuteOpc(__int64 ptr) |
这里会进行VM函数的选择,由v1控制,v1来源于:
1 | unsigned __int8 __fastcall GetFuncChoice(__int64 a1) |
a1是MASM文件结构体,a1+51的部分转为dword对应的初始值是0x1000(4096),v2根据v1是否大于等于4096来选择要减去的值(初始值为0),a1+24是MASM文件中第三个数据段,则v3为这个数据段中v2>>3索引的字节,最终返回的是v3的第v1&7个比特位
整个vm结构体的排布为:
1 | [heap]:000055555555C7E0 dq offset opcode |
实际动态调试输入一个值后发现Func0执行到第10次时读取了用户输入,然后又执行了8次,才第一次执行Func1,Func1共计执行5次后返回错误退出,对于func0,其字节码长度均为5,是一个字节opcode+一个dword参数的形式:
字节码 | 作用 |
---|---|
0x10、0x20、0x30、0x40 | load |
0x50 | nop |
0x60、0x61、0x62、0x63 | add sub xor and |
0x70、0x71、0x72 | jmp jnz jz |
0x80 | cmp |
0xA000、0xA001、0xA002、0xA003、0xA004、0xA005、0xA006 | scanf、unsupport、stdout、srand、rand、output flag、malloc |
其中A0开头的“系统调用”的参数要在其上一条指令中加载,且必定为0x10+参数值
查看opcode前18步:
1 | 0x10, 0x4B, 0x00, 0x00, 0x00, //加载0x4B到memory的0xEFF偏移量(7FFFF7FBAEFF),对应字符串长度 |
Challenge1:2405061754
之后来到Func1部分,Func1的字节码分的很细碎:
其他类
字节码 | 作用 |
---|---|
0x10 | mov mem num |
0x11u-0x14 | push reg0-reg3 |
0x15u-0x18 | pop reg0-reg3 |
0xc0-0xff | v9case5→load? |
0x70-0x7f | mov rega regb |
0x80-0x8f | mov reg num |
系统调用类
字节码 | 作用 |
---|---|
1-0 | scanf |
1-1 | stdin into mem |
1-2 | stdout |
1-3 | srand |
1-4 | rand |
1-5 | unsupport |
1-6 | malloc |
运算类
字节码 | 作用 |
---|---|
0x20 | add reg, reg |
0x21 | add reg, num |
0x30 | sub reg, reg |
0x31 | sub reg, num |
0x40 | xor reg, reg |
0x41 | xor reg, num |
0x50 | mul reg, reg |
0x51 | mul reg, num |
跳转类
字节码 | 作用 |
---|---|
0x60 | call |
0x61 | retn |
0x62 | jz |
0x63 | jnz |
0x64 | js (offset50) |
0x68 | jmp |
继续查看Challenge2,从第91字节开始进入func1:
1 | 0xC5, 0x02, 0x00, 0x00, 0x00, //加载0x2到VM结构体偏移量59的位置(系统调用参数) |
Chanllenge2只需构造任意字符串,每4个字节按小端序合并为一个dword,乘以0xCAFEBABE后保留高位,再异或前一项的高位后,最后一个dword为0x7331即可,一个可行的解是~VVD$T}WI%m54lAEA%-n,3ufs[sp[1@=
(z3求解):
1 | from z3 import * |
Challenge3:
1 | - - - - - - 0x1092 - - - - - - |
20轮循环理论上只要其中有一轮等于0xC0FFEE就可以,因此可以编写爆破脚本:
1 |
|
可以求出来很多很多符合要求的nums.
flag:CTF{st3ph3n_str4ng3_0nly_us3s_m1ps_wh4t_a_n00b}