3

CatFly【汇编代码还原】 - yuanyy720

 8 months ago
source link: https://www.cnblogs.com/yuanyy/p/17921456.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

CatFly【难度:1】

下载附件,发现是dll文件,放到linux中运行一下,运行界面如图所示:

2805886-20231222140432868-2103821851.png

从上图中可以看到两处字符串,上面的字符串不断滚动,下方字符串在次数上不断累加,猜测上方字符串与flag相关。

  • 打开IDA,找到main函数

    2805886-20231222140448734-142504415.png

    方便分析,此处只粘贴关键部分代码(源代码的后半部分)

      time(&timer);
      v13 = 1;
      v24 = 0LL;
      v23 = 0;
      v22 = 0;
      v12 = off_FA88;
      while ( v13 )
      {
        if ( dword_E104 )
          printf("\x1B[H");
        else
          printf("\x1B[u");
        for ( k = dword_E1EC; k < dword_E1F0; ++k )
        {
          for ( m = dword_E1F4; m < dword_E1F8; ++m )
          {
            if ( k <= 23 || k > 42 || m >= 0 )
            {
              if ( m >= 0 && (unsigned int)k <= 0x3F && m <= 63 )
              {
    
                v19 = off_FA20[v24][k][m];
                off_FA88 = sub_6314((unsigned int)v24, k, m, (__int64)v12);
              }
              else
              {
                v19 = 44;
              }
            }
            else
            {
              v18 = (2 - m) % 16 / 8;
              if ( ((v24 >> 1) & 1) != 0 )
                v18 = 1 - v18;
              s[128] = (__int64)",,>>&&&+++###==;;;,,";
              v19 = asc_BFE3[v18 - 23 + k];
              if ( !v19 )
                v19 = 44;
            }
            if ( v25 )
            {
              printf("%s", *((const char **)&unk_FCC0 + v19));
            }
            else if ( v19 == v22 || !*((_QWORD *)&unk_FCC0 + v19) )
            {
              printf("%s", off_FA88);
            }
            else
            {
              v22 = v19;
              printf("%s%s", *((const char **)&unk_FCC0 + v19), off_FA88);
            }
          }
          sub_65E2(1LL);
        }
        if ( dword_E100 )
        {
          time(&time1);
          v11 = difftime(time1, timer);
          v10 = sub_63FF((unsigned int)(int)v11);
          for ( n = (dword_E1FC - 29 - v10) / 2; n > 0; --n )
            putchar(32);
          dword_E1E8 += printf("\x1B[1;37mYou have nyaned for %d times!\x1B[J\x1B[0m", (unsigned int)++dword_108E0);
        }
        v22 = 0;
        ++v23;
        if ( dword_104C4 && v23 == dword_104C4 )
          sub_6471();
        if ( !off_FA20[++v24] )
          v24 = 0LL;
        usleep(1000 * v27);
      }
      return 0LL;
    

    因为flag可能与屏幕上滚动的字符串有关,所以此处需要格外关注printf函数。从上述关键代码中,可以看到off_FA88最可能与flag有关,因为其他的printf函数打印的基本是一个确定的值。

  • 查看目标的交叉引用路径

    确定了off_FA88后,开始查找与其相关的函数。

    • 第一处是469行中的对off_FA88的赋值操作

      2805886-20231222140543813-161658086.png

      off_FA88的值是sub_6314()函数的返回值。

    • 接下来进入sub_6314()函数

      函数伪代码如下图所示:

      2805886-20231222140602087-1041646837.png

      其功能为:当a2!=18,a3≤4||a3>54时off_FA88不变(v12的值就是off_FA88),所以此处相当于是k=18(一次)且m∈(4,53)时会执行sub_6314函数,即循环执行50次。该函数的返回值是byte_104C8的地址,byte_104C8的值与dword_E120[a3-5],dword_E120[a3-5]的值又与sub_62B5()函数的返回值有关。这里还有个sub_62E3作为逻辑判断的条件。将此处逻辑进行还原,还原代码如下:

      for (int i = 0; i < 50; i++) {
           dword_E120[i] ^= sub_62B5();
       }
      
    • 查看sub_62B5()函数

      函数伪代码如下图所示:

      2805886-20231222140623728-466852389.png

      可以看到该函数的返回值与dword_E1E8相关,查看dword_E1E8的交叉引用,可以看到只在main函数中有一次自增操作:

      2805886-20231222140642429-1818366198.png

    • 总结整个流程

      2805886-20231222140652432-19925160.png
  • 还原关键汇编代码

    此处使用大佬wp给的代码进行分析,基本都给了解析:

    #include<stdio.h>
    #include<string.h>
    //在ida里面点开dword_E1E8会发现初始值为0x1106
    int dword_E1E8 = 0x1106;
    //同理,ida里也能看到dword_E120的初始值
    int dword_E120[50] = { 0x27fb, 0x27a4, 0x464e, 0x0e36, 0x7b70, 0x5e7a, 0x1a4a, 0x45c1, 0x2bdf, 0x23bd, 0x3a15, 0x5b83, 0x1e15, 0x5367, 0x50b8, 0x20ca, 0x41f5, 0x57d1, 0x7750, 0x2adf, 0x11f8, 0x09bb, 0x5724, 0x7374, 0x3ce6, 0x646e, 0x010c, 0x6e10, 0x64f4, 0x3263, 0x3137, 0x00b8, 0x229c, 0x7bcd, 0x73bd, 0x480c, 0x14db, 0x68b9, 0x5c8a, 0x1b61, 0x6c59, 0x5707, 0x09e6, 0x1fb9, 0x2ad3, 0x76d4, 0x3113, 0x7c7e, 0x11e0, 0x6c70 };
    //原封不动抄下来就好了
    int sub_62B5()
    {
        dword_E1E8 = 1103515245 * dword_E1E8 + 12345;
        return (dword_E1E8 >> 10) & 0x7FFF;
    }
    //这块是拿来算输出数字n需要多少个字符的。
    //因为main函数中的dword_E1E8需要接收printf函数的返回值
    //而printf函数的返回值是打印的字符长度
    int llog(int n) {
        int a = 0;
        while (n /= 10)a++;
        return a;
    }
    //这个函数我没有特别放出来,反正这里也是照抄的
    int sub_62E3(char a1)
    {
        int result; // rax
    
        if ((a1 & 0x7Fu) <= 0x7E)
            result = (a1 & 0x7Fu) > 0x20;
        else
            result = 0LL;
        return result;
    }
    
    int main() {
        //count代表那个不停自增的dword_108E0
        int count = 0;
        while (1) {
            for (int i = 0; i < 50; i++) {
                dword_E120[i] ^= sub_62B5();
            }
            count++;
            //计算printf的返回值,更改dword_E1E8,每10倍增加一个位数
            dword_E1E8 += 42 + llog(count);
            if (count % 1000000 == 0) {
                printf("Count:%d\n", count);
            }
            //flag代表off_FA88
            unsigned char flag[51] = { 0 };
            for (int i = 0; i < 50; i++) {
                //根据出题人所说,出题时循环次数为705980581,但是线性同余随机数算法出现了循环导致在100427942就出现了flag,若只考虑数组的最低字节,能在100001958得到flag
                // Loop: 100427942
                // if((dword_E120[i] & 0xff00)){
                //     break;
                // }
                // Loop: 100001958
                if (!sub_62E3(dword_E120[i])) {
                    break;
                }
                flag[i] = dword_E120[i] & 0xff;
            }
            if (memcmp("CatCTF", flag, 6) == 0) {
                puts(flag);
                printf("Count:%d\n", count);
                break;
            }
        }
    }
    
  • 运行代码,得到flag

    2805886-20231222140705890-1975326537.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK