0%

homuraVM

HomuraVM

太丢脸了,这么简单一个vm还是做了三个多小时

homuraVM

概况

这道题是WxyVM的一个加强版,即使把vm的指令翻译出来了,也会发现得到的是一串鸟语,俗称类brainf**k语言

程序的大概思路是这样的,读入一串数据,然后把这段数据载入内存,随后执行保存在程序里面的一段虚拟机代码,虚拟机的各种函数已经给出,最终生成的加密结果也能找到,逆向程序。

过程

对vm的代码进行读取

自己写的IDA中的注释:

while ( 1 )
{
result = *(unsigned __int8 *)(COUNTER + a1);
if ( !(_BYTE)result ) // 没有指令了 就退出
return result;
v2 = *(char *)(COUNTER + a1);
switch ( (unsigned int)off_1048 )
{
case 0x43u: // *j -= 2 * (*i & *a) C
*(_DWORD *)global_j -= 2 * (*(_DWORD *)global_i & *(_DWORD *)array_input);
++COUNTER;
break;
case 0x47u: // (*j)-- G
--*(_DWORD *)global_j;
++COUNTER;
break;
case 0x4Du: // *j = *i + a[0] M
*(_DWORD *)global_j = *(_DWORD *)global_i + *(_DWORD *)array_input;
++COUNTER;
break;
case 0x54u: // (*j)++ T
++*(_DWORD *)global_j;
++COUNTER;
break;
case 0x5Bu: // 如果a[0]!=0 就循环,否则直接跳出 [
if ( *(_DWORD *)array_input )
{
++COUNTER;
}
else
{
do
v3 = COUNTER++;
while ( *(_BYTE *)(v3 + a1) != 93 );
}
break;
case 0x5Du: // 如果a[0]!=0 就返回[继续循环 ]
if ( *(_DWORD *)array_input )
{
do
--COUNTER;
while ( *(_BYTE *)(COUNTER + a1) != 91 );
++COUNTER;
}
else
{
++COUNTER;
}
break;
case 0x61u: // (*i)-- a
--*(_DWORD *)global_i;
++COUNTER;
break;
case 0x68u: // a[0] += 4 h
array_input = (char *)array_input + 4;
++COUNTER;
break;
case 0x6Du: // a[0]自加 m
++*(_DWORD *)array_input;
++COUNTER;
break;
case 0x6Fu: // a[0] -= 4 o
array_input = (char *)array_input - 4;
++COUNTER;
break;
case 0x72u: // (*i)++ r
++*(_DWORD *)global_i;
++COUNTER;
break;
case 0x75u: // a[0]-- u
--*(_DWORD *)array_input;
++COUNTER;
break;
case 0x76u: // j = i v
*(_DWORD *)global_j = *(_DWORD *)global_i;
++COUNTER;
break;
case 0x7Bu:
if ( *(_DWORD *)global_j ) // *j !=0 就进入循环,否则直接跳出
{
++COUNTER;
}
else
{
do
v4 = COUNTER++;
while ( *(_BYTE *)(v4 + a1) != 125 );
}
break;
case 0x7Du:
if ( *(_DWORD *)global_j ) // 如果*j不为0就返回{处
{
do
--COUNTER;
while ( *(_BYTE *)(COUNTER + a1) != 123 );
++COUNTER;
}
else
{
++COUNTER;
}
break;
default:
continue;
}
}

清除无用虚拟机指令

首先我尝试着把无效命令先清除掉:

str = """h[ur]ovMCh{mG}hv{aG}[ur]ovaaaMCh{mG}hv{aG}[ur]
ovrrMCh{mG}hv{aG}[ur]ovrararaMCh{mG}hv{aG}[ur]ovrarar
rrMCh{mG}hv{aG}[ur]ovararaaMCh{mG}hv{aG}[ur]ovrararra
raMCh{mG}hv{aG}[ur]ovrrrarrrMCh{mG}hv{aG}[ur]ovaarrar
rMCh{mG}hv{aG}[ur]ovaaarrarMCh{mG}hv{aG}[ur]ovrrrarrM
Ch{mG}hv{aG}[ur]ovaarrraaMCh{mG}hv{aG}[ur]ovarraarMCh
{mG}hv{aG}[ur]ovrrraaarrMCh{mG}hv{aG}[ur]ovaaarrrrarr
MCh{mG}hv{aG}[ur]ovrrrraarrarrMCh{mG}hv{aG}[ur]ovrrar
raMCh{mG}hv{aG}[ur]ovaaraarMCh{mG}hv{aG}[ur]ovrrarraM
Ch{mG}hv{aG}[ur]ovaarrrarMCh{mG}hv{aG}[ur]ovrraarraMC
h{mG}hv{aG}[ur]ovrrarMCh{mG}hv{aG}[ur]ovaarrarMCh{mG}
hv{aG}[ur]ovrrraarMCh{mG}hv{aG}[ur]ovrrrraaMCh{mG}hv{
aG}[ur]ovrrarraMCh{mG}hv{aG}[ur]ovrrrrrrMCh{mG}hv{aG}
[ur]ovaaaarMCh{mG}hv{aG}[ur]ovrraaaMCh{mG}hv{aG}[ur]o
vaarraMCh{mG}hv{aG}[ur]ovrrarMCh{mG}hv{aG}[ur]ovaarra
aMCh{mG}hv{aG}[ur]ovaarraraMCh{mG}hv{aG}[ur]ovaarrara
rMCh{mG}"""

str = str.replace("\n", "")
old = ""
while(old != str):
old = str
str = str.replace("ra", "")
str = str.replace("ar", "")
str = str.replace("oh", "")
str = str.replace("ho", "")
str = str.replace("um", "")
str = str.replace("mu", "")


print("\n\n\n")
h_cnt = 0
o_cnt = 0
for c in str:
if(c == 'h'):
h_cnt += 1
if(h_cnt == 3):
h_cnt = 1
o_cnt = 0
print(" ")
elif(c == 'o'):
o_cnt += 1
print(c, end="")

可得以下字符串(VScode显示有点问题,打印到文件中就行)

h[ur]ovMCh{mG}
hv{aG}[ur]ovaaaMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovMCh{mG}
hv{aG}[ur]ovrrrMCh{mG}
hv{aG}[ur]ovaaMCh{mG}
hv{aG}[ur]ovrMCh{mG}
hv{aG}[ur]ovrrrrrMCh{mG}
hv{aG}[ur]ovrMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovrrrrMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrrrrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovaaMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrMCh{mG}
hv{aG}[ur]ovrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovrrrrrrMCh{mG}
hv{aG}[ur]ovaaaMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovrrMCh{mG}
hv{aG}[ur]ovaaMCh{mG}
hv{aG}[ur]ovaMCh{mG}
hv{aG}[ur]ovMCh{mG}

得到正向算法(递推公式)

//MCh{mG}hv{aG}[ur]ov
*j = *i + *a - 2 * (*i & *a)

//{mG}: *a += j j清空
while(*j):
*a++
*j--

//{aG}: i -= j j清空
while(*j):
*i--
*j--

//[ur]: i = *a a清空
while(*a)
*a--
*i++

//r:
*i++
//a:
*i--
//v:
j = i

经过研究

“v{ag}” 没有起到任何作用,

“v” 将i的值取到了j中

然后i和j一同被清空了

“ov”中的“v”也没有起到作用,因为在后面j的值会被刷新

再次精简:(去掉v{aG} )

h[ur]oMCh{mG}
h[ur]oaaaMCh{mG}
h[ur]orrMCh{mG}
h[ur]oMCh{mG}
h[ur]orrrMCh{mG}
h[ur]oaaMCh{mG}
h[ur]orMCh{mG}
h[ur]orrrrrMCh{mG}
h[ur]orMCh{mG}
h[ur]oaMCh{mG}
h[ur]orrrrMCh{mG}
h[ur]oaMCh{mG}
h[ur]oMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrrrrMCh{mG}
h[ur]orrMCh{mG}
h[ur]oaaMCh{mG}
h[ur]orrMCh{mG}
h[ur]orMCh{mG}
h[ur]orMCh{mG}
h[ur]orrMCh{mG}
h[ur]oMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrMCh{mG}
h[ur]orrrrrrMCh{mG}
h[ur]oaaaMCh{mG}
h[ur]oaMCh{mG}
h[ur]oaMCh{mG}
h[ur]orrMCh{mG}
h[ur]oaaMCh{mG}
h[ur]oaMCh{mG}
h[ur]oMCh{mG}

h 指针加一

[ur] 将*(a+1)全部转移到 i 中

o 指针减一

ar index改变 i = *(a+1) + index

M j = *(a+1) + index + *a

C j -= ((*(a+1) + index) & *a) * 2

MC: j = *(a+1) + index + *a - (( *(a+1) + index) & *a) * 2

h 指针加一

{mG} 把j的内容放到*(a+1)

递推公式:new(*(a+1)) = *(a+1) + index + *a - (( *( a+1) + index) & *a) * 2

(index为 一大堆rraa 最后得到的值)

解密

知道了*(a+1) 后面的新值,知道了 *(a+1) ,要求原来的 *(a+1)的值,只能通过枚举

enc = [27, 114, 17, 118, 8, 74, 126, 5, 55, 124, 31, 88, 104, 7,
112, 7, 49, 108, 4, 47, 4, 105, 54, 77, 127, 8, 80, 12, 109, 28, 127, 80, 29, 96]


index = []
f = open("index.txt", "r")
for i in range(34):
cnt = 0
s = f.readline()
for c in s:
if(c == 'r'):
cnt += 1
if(c == 'a'):
cnt -= 1
index.append(cnt)

ans = ""
for i in range(33, 0, -1):
for c in range(128):
if(enc[i] == c + index[i] + enc[i-1] - 2 * ((c + index[i]) & enc[i-1])):
ans = chr(c) + ans
break

print(ans)

输出:lag{D3v1L_H0mur4_f**k_y0uR_bra1N}

由于如下代码(IDA dump出来的)

__isoc99_scanf("%s", s);
for ( i = 0; ; ++i )
{
v3 = i;
if ( v3 >= strlen(s) )
break;
*((_DWORD *)array_input + i + 1LL) = s[i];
}
*(_DWORD *)array_input = s[strlen(s) - 1]; // 读取方式有点奇怪,最后一个字符放在开头的

输入的字符串并没有按照顺序存储,而是将输入的最后一个字符放到了内存开头,说明之后的操作全部都是从第二位开始的

通过最后一位和第一位的值同样可以逆推出原值,直接根据格式猜也行,flag嘛缺个”f”hhhh

flag{D3v1L_H0mur4_f**k_y0uR_bra1N}

本题用到了程序语言的化简,还有寻找规律的相关方法,遇到这样的题最好是先自己模拟一个循环节,找到规律再尝试写脚本解。因为并不是所有的虚拟机都是每步操作都可逆的23333