buu题解-[网鼎杯 2020 青龙组]singal

打开文件,下载附件,查壳,32位无壳。

拖入ida中打开找到,找到主函数

可以看到如上操作,最后有个puts()输出,查看asc_404058是什么

可以看到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插件符号执行可以秒掉,但是本人还不会,下次再来看这种

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值