题目源于【GWCTF 2019】,考点为vm逆向
记录一道比较典型的vm题
主函数
程序主函数如下,下面我们对调用到的3个函数进行分析

vm自定义方法
sub_55BE054DCCD1()
函数用于存储需要调用到的方法,也可以观察到每调用完一次操作,a1就会自增跳到下一个命令,类似RIP功能
unsigned __int64 __fastcall sub_55BE054DCCD1(__int64 a1) { unsigned __int64 v2;
v2 = __readfsqword(0x28u); *(_DWORD *)a1 = 0; *(_DWORD *)(a1 + 4) = 0x12; *(_DWORD *)(a1 + 8) = 0; *(_DWORD *)(a1 + 12) = 0; *(_QWORD *)(a1 + 16) = &unk_55BE056DE060; *(_BYTE *)(a1 + 24) = 0xF1; *(_QWORD *)(a1 + 32) = sub_55BE054DCB5F; *(_BYTE *)(a1 + 40) = 0xF2; *(_QWORD *)(a1 + 48) = sub_55BE054DCA64; *(_BYTE *)(a1 + 56) = 0xF5; *(_QWORD *)(a1 + 64) = sub_55BE054DCAC5; *(_BYTE *)(a1 + 72) = 0xF4; *(_QWORD *)(a1 + 80) = sub_55BE054DC956; *(_BYTE *)(a1 + 88) = 0xF7; *(_QWORD *)(a1 + 96) = sub_55BE054DCA08; *(_BYTE *)(a1 + 104) = 0xF8; *(_QWORD *)(a1 + 112) = sub_55BE054DC8F0; *(_BYTE *)(a1 + 120) = 0xF6; *(_QWORD *)(a1 + 128) = sub_55BE054DC99C; qword_55BE056DE2A8 = malloc(0x512uLL); memset(qword_55BE056DE2A8, 0, 0x512uLL); return __readfsqword(0x28u) ^ v2; }
|
选择置换
unsigned __int64 __fastcall sub_55BE054DCB5F(__int64 a1) { int *v2; unsigned __int64 v3;
v3 = __readfsqword(0x28u); v2 = (int *)(*(_QWORD *)(a1 + 16) + 2LL); switch ( *(_BYTE *)(*(_QWORD *)(a1 + 16) + 1LL) ) { case 0xE1: *(_DWORD *)a1 = *((char *)qword_55BE056DE2A8 + *v2); break; case 0xE2: *(_DWORD *)(a1 + 4) = *((char *)qword_55BE056DE2A8 + *v2); break; case 0xE3: *(_DWORD *)(a1 + 8) = *((char *)qword_55BE056DE2A8 + *v2); break; case 0xE4: *((_BYTE *)qword_55BE056DE2A8 + *v2) = *(_DWORD *)a1; break; case 0xE5: *(_DWORD *)(a1 + 12) = *((char *)qword_55BE056DE2A8 + *v2); break; case 0xE7: *((_BYTE *)qword_55BE056DE2A8 + *v2) = *(_DWORD *)(a1 + 4); break; default: break; } *(_QWORD *)(a1 + 16) += 6LL; return __readfsqword(0x28u) ^ v3; }
|
异或
unsigned __int64 __fastcall sub_55BE054DCA64(__int64 a1) { unsigned __int64 v2;
v2 = __readfsqword(0x28u); *(_DWORD *)a1 ^= *(_DWORD *)(a1 + 4); ++*(_QWORD *)(a1 + 16); return __readfsqword(0x28u) ^ v2; }
|
读取数据,并且限制长度为21
unsigned __int64 __fastcall sub_55BE054DCAC5(__int64 a1) { const char *buf; unsigned __int64 v3;
v3 = __readfsqword(0x28u); buf = (const char *)qword_55BE056DE2A8; read(0, qword_55BE056DE2A8, 0x20uLL); dword_55BE056DE2A4 = strlen(buf); if ( dword_55BE056DE2A4 != 21 ) { puts("WRONG!"); exit(0); } ++*(_QWORD *)(a1 + 16); return __readfsqword(0x28u) ^ v3; }
|
无操作
unsigned __int64 __fastcall sub_55BE054DC956(__int64 a1) { unsigned __int64 v2;
v2 = __readfsqword(0x28u); ++*(_QWORD *)(a1 + 16); return __readfsqword(0x28u) ^ v2; }
|
乘法
unsigned __int64 __fastcall sub_55BE054DCA08(__int64 a1) { unsigned __int64 v2;
v2 = __readfsqword(0x28u); *(_DWORD *)a1 *= *(_DWORD *)(a1 + 12); ++*(_QWORD *)(a1 + 16); return __readfsqword(0x28u) ^ v2; }
|
交换
unsigned __int64 __fastcall sub_55BE054DC8F0(int *a1) { int v2; unsigned __int64 v3;
v3 = __readfsqword(0x28u); v2 = *a1; *a1 = a1[1]; a1[1] = v2; ++*((_QWORD *)a1 + 2); return __readfsqword(0x28u) ^ v3; }
|
加法
unsigned __int64 __fastcall sub_55BE054DC99C(__int64 a1) { unsigned __int64 v2;
v2 = __readfsqword(0x28u); *(_DWORD *)a1 = *(_DWORD *)(a1 + 8) + 2 * *(_DWORD *)(a1 + 4) + 3 * *(_DWORD *)a1; ++*(_QWORD *)(a1 + 16); return __readfsqword(0x28u) ^ v2; }
|
加密操作1
sub_55BE054DCE0B()
函数根据unk_55BE056DE060
给出的opcode对a1进行加密,知道操作码等于0xF4
为止
下面我们需要对opcode进行分析,也是我们解题中最关键的一步
unsigned __int64 __fastcall sub_55BE054DCE0B(__int64 a1) { unsigned __int64 v2;
v2 = __readfsqword(0x28u); *(_QWORD *)(a1 + 16) = &unk_55BE056DE060; while ( **(_BYTE **)(a1 + 16) != 0xF4 ) sub_55BE054DCE6E(a1); return __readfsqword(0x28u) ^ v2; }
|
根据地址选取指令对a1进行操作
unsigned __int64 __fastcall sub_556FD909DE6E(__int64 a1) { int i; unsigned __int64 v3;
v3 = __readfsqword(0x28u); for ( i = 0; **(_BYTE **)(a1 + 16) != *(_BYTE *)(16 * (i + 1LL) + a1 + 8); ++i ) ; (*(void (__fastcall **)(__int64))(16 * (i + 1LL) + a1 + 16))(a1); return __readfsqword(0x28u) ^ v3; }
|
把opcode截取到0xF4(在这里你会发现有两段F5-F4),结合动态调试分析,其实一个简单的异或循环,虽然这里写进input[0x20]
显得有点奇怪,但是只要看一下密文对比1那部分就知道其实这里存储的就是加密后的结果
F5 F1 E1 00 00 00 00 F2 F1 E4 20 00 00 00 F1 E1 01 00 00 00 F2 F1 E4 21 00 00 00 F1 E1 02 00 00 00 F2 F1 E4 22 00 00 00 F1 E1 03 00 00 00 F2 F1 E4 23 00 00 00 F1 E1 04 00 00 00 F2 F1 E4 24 00 00 00 F1 E1 05 00 00 00 F2 F1 E4 25 00 00 00 F1 E1 06 00 00 00 F2 F1 E4 26 00 00 00 F1 E1 07 00 00 00 F2 F1 E4 27 00 00 00 F1 E1 08 00 00 00 F2 F1 E4 28 00 00 00 F1 E1 09 00 00 00 F2 F1 E4 29 00 00 00 F1 E1 0A 00 00 00 F2 F1 E4 2A 00 00 00 F1 E1 0B 00 00 00 F2 F1 E4 2B 00 00 00 F1 E1 0C 00 00 00 F2 F1 E4 2C 00 00 00 F1 E1 0D 00 00 00 F2 F1 E4 2D 00 00 00 F1 E1 0E 00 00 00 F2 F1 E4 2E 00 00 00 F1 E1 0F 00 00 00 F2 F1 E4 2F 00 00 00 F1 E1 10 00 00 00 F2 F1 E4 30 00 00 00 F1 E1 11 00 00 00 F2 F1 E4 31 00 00 00 F1 E1 12 00 00 00 F2 F1 E4 32 00 00 00 F1 E1 13 00 00 00 F2 F1 E4 33 00 00 00 F4
|
a1[1]就是之前存储的*(_DWORD *)(a1 + 4) = 0x12;
密文比较1
sub_55BE054DCF83()
函数与aFzAmAmFmtSum
的指针值(其实就是我们的input)进行比较
unsigned __int64 sub_55BE054DCF83() { int i; unsigned __int64 v2;
v2 = __readfsqword(0x28u); for ( i = 0; dword_55BE056DE2A4 - 1 > i; ++i ) { if ( *((_BYTE *)qword_55BE056DE2A8 + i + 0x20) != aFzAmAmFmtSum[i] ) { puts("WRONG!"); exit(0); } } puts("Congratulation?"); puts("tips: input is the start"); return __readfsqword(0x28u) ^ v2; }
|
直接用这个值aFzAmAmFmtSum
解密发现是假flag
print('[+] flag:', ''.join([chr(ord(_)^0x12) for _ in 'Fz{aM{aM|}fMt~suM !!']))
|
密文比较2
在假flag上面有一个疑似密文的byte_556FD929F020
数据,交叉引用到sub_556FD909DF00()
中找到了另外一段密文比较
unsigned __int64 sub_556FD909DF00() { int i; unsigned __int64 v2;
v2 = __readfsqword(0x28u); for ( i = 0; dword_556FD929F2A4 - 1 > i; ++i ) { if ( *((_BYTE *)qword_556FD929F2A8 + i) != byte_556FD929F020[i] ) exit(0); } return __readfsqword(0x28u) ^ v2; }
|
加密操作2
在上面进行加密操作1的时候我们发现还有第二段opcode,那我们尝试分析一下
F5 F1 E1 00 00 00 00 F1 E2 01 00 00 00 F2 F1 E4 00 00 00 00 F1 E1 01 00 00 00 F1 E2 02 00 00 00 F2 F1 E4 01 00 00 00 F1 E1 02 00 00 00 F1 E2 03 00 00 00 F2 F1 E4 02 00 00 00 F1 E1 03 00 00 00 F1 E2 04 00 00 00 F2 F1 E4 03 00 00 00 F1 E1 04 00 00 00 F1 E2 05 00 00 00 F2 F1 E4 04 00 00 00 F1 E1 05 00 00 00 F1 E2 06 00 00 00 F2 F1 E4 05 00 00 00 F1 E1 06 00 00 00 F1 E2 07 00 00 00 F1 E3 08 00 00 00 F1 E5 0C 00 00 00 F6 F7 F1 E4 06 00 00 00 F1 E1 07 00 00 00 F1 E2 08 00 00 00 F1 E3 09 00 00 00 F1 E5 0C 00 00 00 F6 F7 F1 E4 07 00 00 00 F1 E1 08 00 00 00 F1 E2 09 00 00 00 F1 E3 0A 00 00 00 F1 E5 0C 00 00 00 F6 F7 F1 E4 08 00 00 00 F1 E1 0D 00 00 00 F1 E2 13 00 00 00 F8 F1 E4 0D 00 00 00 F1 E7 13 00 00 00 F1 E1 0E 00 00 00 F1 E2 12 00 00 00 F8 F1 E4 0E 00 00 00 F1 E7 12 00 00 00 F1 E1 0F 00 00 00 F1 E2 11 00 00 00 F8 F1 E4 0F 00 00 00 F1 E7 11 00 00 00 F4
|
EXP
直接逆向上面的操作就好
from z3 import * import re
flag = '69 45 2A 37 09 17 C5 0B 5C 72 33 76 33 21 74 31 5F 33 73 72'.split(' ') flag = [int(_, 16) for _ in flag]
flag[15], flag[17] = flag[17], flag[15] flag[14], flag[18] = flag[18], flag[14] flag[19], flag[13] = flag[13], flag[19]
a6, a7, a8 = BitVecs('a6 a7 a8', 8) s = Solver() s.add(flag[6] == (a8 + 2 * a7 + 3 * a6) * flag[12]) s.add(flag[7] == (flag[9] + 2 * a8 + 3 * a7) * flag[12]) s.add(flag[8] == (flag[10] + 2 * flag[9] + 3 * a8) * flag[12]) if s.check() == sat: m = s.model() for i in m: index = int(re.search(r'\d+', str(i)).group()) flag[index] = m[i].as_long()
for i in range(5, -1, -1): flag[i] ^= flag[i + 1]
print('[+] flag: ', ''.join([chr(_) for _ in flag]))
|