打开文件,下载附件,查壳,32位无壳。
拖入ida中打开找到,找到主函数
![](https://img-blog.csdnimg.cn/img_convert/608da7118d12a3650dd23d6df0e30a78.png)
可以看到如上操作,最后有个puts()输出,查看asc_404058是什么
![](https://img-blog.csdnimg.cn/img_convert/bc1c5e5c3d34e28c7f136d610e0cd636.png)
可以看到asc_404058中就是得到了flag后的输出。我们跟进vm_operad函数查看,是典型的vm逆向
int __cdecl vm_operad(int *a1, int a2)
{
int result; // eax
char flag[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 >= a2 )
return result;
switch ( a1[v9] )
{
case 1:
str[v6 + 100] = v4; // 赋值运算,将中间量v4的值赋给flag
++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] ) // 当输入为7时判断flag的值是否与a1对应的值相等
{
printf(asc_404013);
exit(0);
}
++v7;
v9 += 2;
break;
case 8:
str[v5] = v4;
++v9;
++v5;
break;
case 10:
read(str); // 判断输入的字符串长度必须为15
++v9;
break;
case 11:
v4 = str[v8] - 1;
++v9;
break;
case 12:
v4 = str[v8] + 1;
++v9;
break;
default:
continue;
}
}
}
可以看到是switch分支语句
case 1:是赋值操作,将v4的值赋给str
case 7:进行了一个判断,即str[v7 + 100] == a1[v9 + 1]
case 10:进入查看,要求str的字符串长度为15
其他的case分支是对v4的操作,由此可以判断v4在a1[i]不等于1且不等于7时进行变换操作,在a1[i]==1时被赋给str,在a1[i]=7时,判断str的值是否和a1[i]的下一位相等。这样进行15次判断
我们先在&unk_403040处提取a1的值进行处理,处理后为:
unsigned char Str[] =
{
4,16,8,3,5, 1,
4,8,5,3, 1,
3,2,8,11, 1,
12,8,4,4, 1,
5,3,8,3, 1,
11,8,11, 1,
4,9,8,3, 1,
2,8,4, 1,
12,8,11, 1,
5,2,8,2, 1,
2,8,4, 1,
2,8,5, 1,
1,
5,3,8,2, 1,
4,9,8,3, 1,
2,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,
};
可以看到有15个1和15个7,每个1前面的数值就是对v4的操作,在a1[i]=1时赋值给str,最后的 15个7来进行比较,因为比较时是和a1的下一位比,所以每个7后面的值就是我们要找到加密后的v4
提取出来
v4[]={34,63,52,50,114,51,24,167,49,241,40,132,193,30,122}
理清大致思路后,上脚本解密,本人比较菜,附上一位大佬的脚本
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int v9 = 0;
int v8 = 0;
int v7 = 0;
int v6 = 0;
int v5 = 0;
int v4 = 0;
int last_v9 = 0,last_v8 = 0,last_v7 = 0,last_v6 = 0 ,last_v5 = 0,last_v4 = 0;
int f(int a1[],int k,int v[])//a1是传入的值,每次对flag的操作根据其值判断,k用来暴力破解值,v是解密后的值
{
v4 = k;//假设k为输入的值
while(1)
{
switch ( a1[v9] )
{
case 1:
if(v[v6] == v4)//找到正确值,加密成功
{
printf("k = %d,v6 = %d\n",k,v6);
++v9; //此处为什么要再进行++v9,++v6,++v8可以参考ida伪代码
++v6;
++v8;
last_v9 = v9;//k爆破是假设k=23不符合条件,但此时v9已经经过变换,不能再用来带入k=24的运算
last_v8 = v8;//此时就要将v9变为原来没进行l=23时是数值,其他数值变化同理
//last_v7 = v7;
last_v6 = v6;
//last_v5 = v5;
return 1;
}
else
{
v9 = last_v9; //找不到则返回上一个v9
v8 = last_v8;
// v7 = last_v7;
v6 = last_v6;
// v5 = last_v5;
return 0;
}
break;
case 2:
v4 = a1[v9 + 1] + v4;
v9 += 2;
break;
case 3:
v4 = v4 - a1[v9 + 1];
v9 += 2;
break;
case 4:
v4 = a1[v9 + 1] ^ v4;
v9 += 2;
break;
case 5:
v4 = a1[v9 + 1] * v4;
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: //这里把v4的值赋给了Str[v5],上面也有用到,所以上面的Str[v8]直接代换成v4就行
Str[v5] = v4; //即对v4的操作等于对flag的操作 ,上面操作已经把v4当作str,所以此处不需要了
++v9;
++v5;
break;*/
/*case 10: //可以不用读取
read(Str);
++v9;
break;*/
case 11:
v4 = v4 - 1;
++v9;
break;
case 12:
v4 = v4 + 1;
++v9;
break;
default:
v9++;//都不符合的直接跳下一个a1带入判断
break;
}
}
}
int main()
{
int v[16] = {0x22,0x3f,0x34,0x32,0x72,0x33,0x18,0xa7,0x31,0xf1,0x28,0x84,0xc1,0x1e,0x7a}; //v4的值,即加密完成后要赋给str的值
int a1[] = {0x04,0x10,0x08,0x03,0x05,0x01,0x04, 0x20,0x08,0x05,0x03, 0x01,0x03,0x02,0x08,
0x0B,0x01,0x0C,0x08, 0x04,0x04,0x01,0x05, 0x03,0x08,0x03,0x21, 0x01,0x0B,0x08,0x0B,
0x01,0x04,0x09,0x08, 0x03,0x20,0x01,0x02, 0x51,0x08,0x04,0x24, 0x01,0x0C,0x08,0x0B,
0x01,0x05,0x02,0x08, 0x02,0x25,0x01,0x02, 0x36,0x08,0x04,0x41, 0x01,0x02,0x20,0x08,
0x05,0x01,0x01,0x05, 0x03,0x08,0x02,0x25, 0x01,0x04,0x09,0x08, 0x03,0x20,0x01,0x02,
0x41,0x08,0x0C,0x01}; //0x07后面的值都是变换后的flag,可以不用管
int i,j,k = 0;
/*int a1[] = {0x04,0x10,0x08, 0x03,0x05,0x01,0x04, 0x20,0x08,0x05,0x03, 0x01,0x03,0x02,0x08,
0x0B,0x01,0x0C,0x08, 0x04,0x04,0x01,0x05, 0x03,0x08,0x03,0x21, 0x01,0x0B,0x08,0x0B,
0x01,0x04,0x09,0x08, 0x03,0x20,0x01,0x02, 0x51,0x08,0x04,0x24, 0x01,0x0C,0x08,0x0B,
0x01,0x05,0x02,0x08, 0x02,0x25,0x01,0x02, 0x36,0x08,0x04,0x41, 0x01,0x02,0x20,0x08,
0x05,0x01,0x01,0x05, 0x03,0x08,0x02,0x25, 0x01,0x04,0x09,0x08, 0x03,0x20,0x01,0x02,
0x41,0x08,0x0C,0x01, 0x07,0x22,0x07,0x3F, 0x07,0x34,0x07,0x32, 0x07,0x72,0x07,0x33,
0x07,0x18,0x07,0xA7,0xFF,0xFF,0xFF, 0x07,0x31,0x07,0xF1,0xFF,0xFF,0xFF, 0x07,0x28,0x07,
0x84,0xFF,0xFF,0xFF, 0x07,0xC1,0xFF,0xFF,0xFF,0x07,0x1E, 0x07,0x7A,0};*/
int flag[15] = {0};
for(i = 0 ; i < 15 ; i++) //求解flag的每一个字符
{
for(k = 33 ; k <= 'z'; k ++) //暴力破解flag
{
if(f(a1,k,v))
{
flag[i] = k;
break;
}
}
}
printf("flag{");
for(i = 0 ; i < 15 ; i++)
{
printf("%c",flag[i]);
}
printf("}\n");
return 0;
}
运行得到flag:flag{757515121f3d478}
好像还有其他做法,用ida插件符号执行可以秒掉,但是本人还不会,下次再来看这种