题目源于【GWCTF 2019】,考点为vm逆向

记录一道比较典型的vm题

主函数

程序主函数如下,下面我们对调用到的3个函数进行分析

image-20210327233354971

vm自定义方法

sub_55BE054DCCD1()函数用于存储需要调用到的方法,也可以观察到每调用完一次操作,a1就会自增跳到下一个命令,类似RIP功能

unsigned __int64 __fastcall sub_55BE054DCCD1(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

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; // [rsp+28h] [rbp-18h]
unsigned __int64 v3; // [rsp+38h] [rbp-8h]

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; // [rsp+18h] [rbp-8h]

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; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

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; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

乘法

unsigned __int64 __fastcall sub_55BE054DCA08(__int64 a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 *= *(_DWORD *)(a1 + 12);
++*(_QWORD *)(a1 + 16);
return __readfsqword(0x28u) ^ v2;
}

交换

unsigned __int64 __fastcall sub_55BE054DC8F0(int *a1)
{
int v2; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

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; // [rsp+18h] [rbp-8h]

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; // [rsp+18h] [rbp-8h]

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; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

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 // read
F1 E1 00 00 00 00
F2
F1 E4 20 00 00 00 // input[0x20] = input[0] ^ a1[1]
F1 E1 01 00 00 00
F2
F1 E4 21 00 00 00 // input[0x21] = input[0x01] ^ a1[1]
F1 E1 02 00 00 00
F2
F1 E4 22 00 00 00 // input[0x22] = input[0x02] ^ a1[1]
F1 E1 03 00 00 00
F2
F1 E4 23 00 00 00 // input[0x23] = input[0x03] ^ a1[1]
F1 E1 04 00 00 00
F2
F1 E4 24 00 00 00 // input[0x24] = input[0x04] ^ a1[1]
F1 E1 05 00 00 00
F2
F1 E4 25 00 00 00 // input[0x25] = input[0x05] ^ a1[1]
F1 E1 06 00 00 00
F2
F1 E4 26 00 00 00 // input[0x26] = input[0x06] ^ a1[1]
F1 E1 07 00 00 00
F2
F1 E4 27 00 00 00 // input[0x27] = input[0x07] ^ a1[1]
F1 E1 08 00 00 00
F2
F1 E4 28 00 00 00 // input[0x28] = input[0x08] ^ a1[1]
F1 E1 09 00 00 00
F2
F1 E4 29 00 00 00 // input[0x29] = input[0x09] ^ a1[1]
F1 E1 0A 00 00 00
F2
F1 E4 2A 00 00 00 // input[0x2A] = input[0x0A] ^ a1[1]
F1 E1 0B 00 00 00
F2
F1 E4 2B 00 00 00 // input[0x2B] = input[0x0B] ^ a1[1]
F1 E1 0C 00 00 00
F2
F1 E4 2C 00 00 00 // input[0x2C] = input[0x0C] ^ a1[1]
F1 E1 0D 00 00 00
F2
F1 E4 2D 00 00 00 // input[0x2D] = input[0x0D] ^ a1[1]
F1 E1 0E 00 00 00
F2
F1 E4 2E 00 00 00 // input[0x2E] = input[0x0E] ^ a1[1]
F1 E1 0F 00 00 00
F2
F1 E4 2F 00 00 00 // input[0x2F] = input[0x0F] ^ a1[1]
F1 E1 10 00 00 00
F2
F1 E4 30 00 00 00 // input[0x30] = input[0x10] ^ a1[1]
F1 E1 11 00 00 00
F2
F1 E4 31 00 00 00 // input[0x31] = input[0x11] ^ a1[1]
F1 E1 12 00 00 00
F2
F1 E4 32 00 00 00 // input[0x32] = input[0x12] ^ a1[1]
F1 E1 13 00 00 00
F2
F1 E4 33 00 00 00 // input[0x33] = input[0x13] ^ a1[1]
F4 // ret

a1[1]就是之前存储的*(_DWORD *)(a1 + 4) = 0x12;

密文比较1

sub_55BE054DCF83()函数与aFzAmAmFmtSum的指针值(其实就是我们的input)进行比较

unsigned __int64 sub_55BE054DCF83()
{
int i; // [rsp+Ch] [rbp-14h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

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 !!']))
# [+] flag: This_is_not_flag_233

密文比较2

在假flag上面有一个疑似密文的byte_556FD929F020数据,交叉引用到sub_556FD909DF00()中找到了另外一段密文比较

unsigned __int64 sub_556FD909DF00()
{
int i; // [rsp+Ch] [rbp-14h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

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 // read
F1 E1 00 00 00 00
F1 E2 01 00 00 00
F2
F1 E4 00 00 00 00 // input[0] = input[0] ^ input[0x01]
F1 E1 01 00 00 00
F1 E2 02 00 00 00
F2
F1 E4 01 00 00 00 // input[0x01] = input[0x01] ^ input[0x02]
F1 E1 02 00 00 00
F1 E2 03 00 00 00
F2
F1 E4 02 00 00 00 // input[0x02] = input[0x02] ^ input[0x03]
F1 E1 03 00 00 00
F1 E2 04 00 00 00
F2
F1 E4 03 00 00 00 // input[0x03] = input[0x03] ^ input[0x04]
F1 E1 04 00 00 00
F1 E2 05 00 00 00
F2
F1 E4 04 00 00 00 // input[0x04] = input[0x04] ^ input[0x05]
F1 E1 05 00 00 00
F1 E2 06 00 00 00
F2
F1 E4 05 00 00 00 // input[0x05] = input[0x05] ^ input[0x06]
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 // input[0x06] = (input[0x08] + 2 ** input[0x07] + 3 ** input[0x06]) * input[0x0C]
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 // input[0x07] = (input[0x09] + 2 ** input[0x08] + 3 ** input[0x07]) * input[0x0C]
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 // input[0x08] = (input[0x0A] + 2 ** input[0x09] + 3 ** input[0x08]) * input[0x0C]
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 // input[0x0D], input[0x13] = input[0x13], input[0x0D]
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 // input[0x0E], input[0x12] = input[0x12], input[0x0E]
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 // input[0x0F], input[0x11] = input[0x11], input[0x0F]
F4 // ret

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]

# z3
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]))
# [+] flag: Y0u_hav3_r3v3rs3_1t!