题目源于【GWCTF 2019】,考点为vm逆向
记录一道比较典型的vm题
主函数
程序主函数如下,下面我们对调用到的3个函数进行分析
vm自定义方法
sub_55BE054DCCD1()
函数用于存储需要调用到的方法,也可以观察到每调用完一次操作,a1就会自增跳到下一个命令,类似RIP功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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; }
|
选择置换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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; }
|
异或
1 2 3 4 5 6 7 8 9
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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; }
|
无操作
1 2 3 4 5 6 7 8
| unsigned __int64 __fastcall sub_55BE054DC956(__int64 a1) { unsigned __int64 v2;
v2 = __readfsqword(0x28u); ++*(_QWORD *)(a1 + 16); return __readfsqword(0x28u) ^ v2; }
|
乘法
1 2 3 4 5 6 7 8 9
| 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; }
|
交换
1 2 3 4 5 6 7 8 9 10 11 12
| 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; }
|
加法
1 2 3 4 5 6 7 8 9
| 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进行分析,也是我们解题中最关键的一步
1 2 3 4 5 6 7 8 9 10
| 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进行操作
1 2 3 4 5 6 7 8 9 10 11
| 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那部分就知道其实这里存储的就是加密后的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| 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)进行比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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
1 2
| print('[+] flag:', ''.join([chr(ord(_)^0x12) for _ in 'Fz{aM{aM|}fMt~suM !!']))
|
密文比较2
在假flag上面有一个疑似密文的byte_556FD929F020
数据,交叉引用到sub_556FD909DF00()
中找到了另外一段密文比较
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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,那我们尝试分析一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| 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
直接逆向上面的操作就好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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]))
|