提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一.vm逆向
- 这里的虚拟机指的是一种解释执行系统或者模拟器
- 将程序的代码转换自定义的操作码(opcode),然后在程序执行时再通过解释这些操作码,选择对应的函数执行,从而实现程序原有的功能。
二. wp
- 查壳,32位无壳
- 载入IDA
- 找到main函数
- 逻辑简单,
- qmemcpy就是IDA中的memcpy函数,即从内存中取值进行赋值,找到403040处,找到456字节的数据,shift+E提取出来
- 4字节为一个整数,v4是int型
- 整理一下得到:
- v4=[10, 4, 16, 8, 3, 5, 1, 4, 32, 8, 5, 3, 1, 3, 2, 8, 11, 1, 12, 8, 4, 4, 1, 5, 3, 8, 3, 33, 1, 11, 8, 11, 1, 4, 9, 8, 3, 32, 1, 2, 81, 8, 4, 36, 1, 12, 8, 11, 1, 5, 2, 8, 2, 37, 1, 2, 54, 8, 4, 65, 1, 2, 32, 8, 5, 1, 1, 5, 3, 8, 2, 37, 1, 4, 9, 8, 3, 32, 1, 2, 65, 8, 12, 1]
- 然后就是vm_operad函数
- 传入v4与114两个参数
- 跟进
int __cdecl vm_operad(int *a1, int _114)
{
int result; // eax
char Str[200]; // [esp+13h] [ebp-E5h] BYREF
char v4; // [esp+DBh] [ebp-1Dh]
int v5; // [esp+DCh] [ebp-1Ch]
int v6; // [esp+E0h] [ebp-18h]
int v7; // [esp+E4h] [ebp-14h]
int v8; // [esp+E8h] [ebp-10h]
int v9; // [esp+ECh] [ebp-Ch]
v9 = 0;
v8 = 0;
v7 = 0;
v6 = 0;
v5 = 0;
while ( 1 )
{
result = v9;
if ( v9 >= _114 )
return result;
switch ( a1[v9] )
{
case 1:
Str[v6 + 100] = v4;
++v9;
++v6;
++v8;
break;
case 2:
v4 = a1[v9 + 1] + Str[v8];
v9 += 2;
break;
case 3:
v4 = Str[v8] - LOBYTE(a1[v9 + 1]);
v9 += 2;
break;
case 4:
v4 = a1[v9 + 1] ^ Str[v8];
v9 += 2;
break;
case 5:
v4 = a1[v9 + 1] * Str[v8];
v9 += 2;
break;
case 6:
++v9;
break;
case 7:
if ( Str[v7 + 100] != a1[v9 + 1] )
{
printf("what a shame...");
exit(0);
}
++v7;
v9 += 2;
break;
case 8:
Str[v5] = v4;
++v9;
++v5;
break;
case 10:
read(Str);
++v9;
break;
case 11:
v4 = Str[v8] - 1;
++v9;
break;
case 12:
v4 = Str[v8] + 1;
++v9;
break;
default:
continue;
}
}
}
- 关注case10,发现v4[v9]等于10时,读入字符串,且根据read函数发现字符串长度为15
- 关注case7,str字符串与v4下一位进行比较,且必须相等,也就是说输入的字符串进行一系列操作数后,即加密后,与v4中7后的数据相等
- 我们可以写个脚本提取出v4中7后的数据
str=[]
a1=[10, 4, 16, 8, 3, 5, 1, 4, 32, 8, 5, 3, 1, 3, 2, 8, 11, 1, 12, 8, 4, 4, 1, 5, 3, 8, 3, 33, 1, 11, 8, 11, 1, 4, 9, 8, 3, 32, 1, 2, 81, 8, 4, 36, 1, 12, 8, 11, 1, 5, 2, 8, 2, 37, 1, 2, 54, 8, 4, 65, 1, 2, 32, 8, 5, 1, 1, 5, 3, 8, 2, 37, 1, 4, 9, 8, 3, 32, 1, 2, 65, 8, 12, 1, 7, 34, 7, 63, 7, 52, 7, 50, 7, 114, 7, 51, 7, 24, 7, 167, 255, 255, 255, 7, 49, 7, 241, 255, 255, 255, 7, 40, 7, 132, 255, 255, 255, 7, 193, 255, 255, 255, 7, 30, 7, 122]
for i in range(0,len(a1)):
if a1[i]==7:
str.append(a1[i+1])
print(str)
- 得到***[34, 63, 52, 50, 114, 51, 24, 167, 49, 241, 40, 132, 193, 30, 122]***
- v4数组7前面的应该是操作数,而7后面的就是比较字符串,即加密后的str,那么我们只需要进行逆向操作即可
- 下为python脚本
v4=[10, 4, 16, 8, 3, 5, 1, 4, 32, 8, 5, 3, 1, 3, 2, 8, 11, 1, 12, 8, 4, 4, 1, 5, 3, 8, 3, 33, 1, 11, 8, 11, 1, 4, 9, 8, 3, 32, 1, 2, 81, 8, 4, 36, 1, 12, 8, 11, 1, 5, 2, 8, 2, 37, 1, 2, 54, 8, 4, 65, 1, 2, 32, 8, 5, 1, 1, 5, 3, 8, 2, 37, 1, 4, 9, 8, 3, 32, 1, 2, 65, 8, 12, 1, 7, 34, 7, 63, 7, 52, 7, 50, 7, 114, 7, 51, 7, 24, 7, 167, 255, 255, 255, 7, 49, 7, 241, 255, 255, 255, 7, 40, 7, 132, 255, 255, 255, 7, 193, 255, 255, 255, 7, 30, 7, 122]
a1 = [34, 63, 52, 50, 114, 51, 24, 167, 49, 241, 40, 132, 193, 30, 122]
a1.reverse()
v4.reverse()
v9 = 0
us = 0
v5 = 0
flag = []
for i in range(0, len(v4)):
if i == len(v4) - 1:
flag.append(us)
elif v4[i] == 1 and v4[i - 1] != 1:
v5 = a1[v9]
v9 += 1
flag.append(us)
elif v4[i] == 2:
if (v4[i + 1] != 3 and v4[i + 1] != 4 and v4[i + 1] != 5):
us = v5 - v4[i - 1]
elif v4[i] == 3:
if (v4[i + 1] != 2 and v4[i + 1] != 4 and v4[i + 1] != 5):
us = v5 + v4[i - 1]
elif v4[i] == 4:
if (v4[i + 1] != 3 and v4[i + 1] != 2 and v4[i + 1] != 5):
us = v5 ^ v4[i - 1]
elif v4[i] == 5:
if (v4[i + 1] != 3 and v4[i + 1] != 4 and v4[i + 1] != 2):
us = int(v5 / v4[i - 1])
elif v4[i] == 8:
v5 = us
elif v4[i] == 11:
us = v5 + 1
elif v4[i] == 12:
us = v5 - 1
flag.reverse()
out = ''
for j in flag:
out += chr(j)
print(out)
- 解得flag{757515121f3d478}
- 或者可以正向爆破
#include <stdio.h>
#include <string.h>
int main(void)
{
int key[] = {34, 63, 52, 50, 114, 51, 24, 167, 49, 241, 40, 132, 193, 30, 122};
int a1[] ={4, 16, 8, 3, 5, 1, 4, 32, 8, 5, 3, 1, 3, 2, 8, 11, 1, 12, 8, 4, 4, 1, 5, 3, 8, 3, 33, 1, 11, 8, 11, 1, 4, 9, 8, 3, 32, 1, 2, 81, 8, 4, 36, 1, 12, 8, 11, 1, 5, 2, 8, 2, 37, 1, 2, 54, 8, 4, 65, 1, 2, 32, 8, 5, 1, 1, 5, 3, 8, 2, 37, 1, 4, 9, 8, 3, 32, 1, 2, 65, 8, 12, 1, 7, 34, 7, 63, 7, 52, 7, 50, 7, 114, 7, 51, 7, 24, 7, 167, 255, 255, 255, 7, 49, 7, 241, 255, 255, 255, 7, 40, 7, 132, 255, 255, 255, 7, 193, 255, 255, 255, 7, 30, 7, 122};
int v9 = 0;
int v4 = 0;
int i, j;
int flag;
int index = 0;
for ( i = 0; i < 15; i++, index++)
{
for ( j = 48; j < 123; j++)
{
flag = j;
v9 = index;
while ( a1[v9] != 1)
{
switch ( a1[v9] )
{
case 2:
v4 = a1[v9 + 1] + flag;
v9 += 2;
break;
case 3:
v4 = flag - a1[v9 + 1];
v9 += 2;
break;
case 4:
v4 = a1[v9 + 1] ^ flag;
v9 += 2;
break;
case 5:
v4 = a1[v9 + 1] * flag;
v9 += 2;
break;
case 6:
++v9;
break;
case 8:
flag = v4;
++v9;
break;
case 11:
v4 = flag - 1;
++v9;
break;
case 12:
v4 = flag + 1;
++v9;
break;
default:
continue;
}
}
if ( v4 == key[i])
{
index = v9;
printf("%c", j);
break;
}
}
}
return 0;
}
- 同样解得flag{757515121f3d478}
- 其实,个人认为正向爆破更舒服,更直观
- 这里的switch代码较小,故可以逆向,如果代码很长,就只能正向爆破了