Reverse(十三)

StupidOrangeCat2

直接ida32位(7.7或8.3版本,低的分析不出来反调试代码)分析代码,由关键函数可知其为迷宫问题

int __thiscall sub_401140(void *this)
{
  char v2; // [esp+3DFh] [ebp-839h]
  char v3; // [esp+3EBh] [ebp-82Dh]
  int v4; // [esp+454h] [ebp-7C4h]
  int v5; // [esp+460h] [ebp-7B8h]
  int i; // [esp+46Ch] [ebp-7ACh]
  int j; // [esp+46Ch] [ebp-7ACh]
  char Buffer[42]; // [esp+478h] [ebp-7A0h] BYREF
  __int16 v9; // [esp+4A2h] [ebp-776h]
  _WORD v10[22]; // [esp+4A4h] [ebp-774h] BYREF
  _WORD v11[22]; // [esp+4D0h] [ebp-748h] BYREF
  _WORD v12[22]; // [esp+4FCh] [ebp-71Ch] BYREF
  _WORD v13[22]; // [esp+528h] [ebp-6F0h] BYREF
  _WORD v14[22]; // [esp+554h] [ebp-6C4h] BYREF
  _WORD v15[22]; // [esp+580h] [ebp-698h] BYREF
  _WORD v16[22]; // [esp+5ACh] [ebp-66Ch] BYREF
  _WORD v17[22]; // [esp+5D8h] [ebp-640h] BYREF
  _WORD v18[22]; // [esp+604h] [ebp-614h] BYREF
  _WORD v19[22]; // [esp+630h] [ebp-5E8h] BYREF
  _WORD v20[22]; // [esp+65Ch] [ebp-5BCh] BYREF
  _WORD v21[22]; // [esp+688h] [ebp-590h] BYREF
  _WORD v22[22]; // [esp+6B4h] [ebp-564h] BYREF
  _WORD v23[22]; // [esp+6E0h] [ebp-538h] BYREF
  _WORD v24[22]; // [esp+70Ch] [ebp-50Ch] BYREF
  _WORD v25[22]; // [esp+738h] [ebp-4E0h] BYREF
  _WORD v26[22]; // [esp+764h] [ebp-4B4h] BYREF
  _WORD v27[22]; // [esp+790h] [ebp-488h] BYREF
  _WORD v28[22]; // [esp+7BCh] [ebp-45Ch] BYREF
  char v29[1064]; // [esp+7E8h] [ebp-430h] BYREF
  void *v30; // [esp+C10h] [ebp-8h]

  v30 = this;
  __CheckForDebuggerJustMyCode(&unk_46401A);
  printf("@为冒险者,请在迷宫中寻找笨橘猫吧!!\n");
  strcpy(Buffer, "00000000000000000000000000000000000000000");
  v9 = 0;
  strcpy((char *)v10, "0 0 0 0!    0     0 0 0 0 0*    0     0 0");
  v10[21] = 0;
  strcpy((char *)v11, "0 0 0 00000 00000 0 0 0 0 00000 00000 0 0");
  v11[21] = 0;
  strcpy((char *)v12, "0 0               0 0 0               0 0");
  v12[21] = 0;
  strcpy((char *)v13, "0 000 000 0 000 0 0 0 000 000 0 000 0 0 0");
  v13[21] = 0;
  strcpy((char *)v14, "0 0     0 0 0   0 0 0 0     0 0 0   0 0 0");
  v14[21] = 0;
  strcpy((char *)v15, "0 0 0 00000 000 000 0 0 0 00000 000 000 0");
  v15[21] = 0;
  strcpy((char *)v16, "0 0 0     0   0 0     0 0     0   0 0   0");
  v16[21] = 0;
  strcpy((char *)v17, "0 000 0 0 000 0 0 0 0 000 0 0 000 0 0 0 0");
  v17[21] = 0;
  strcpy((char *)v18, "0     0 0 0 0 0 0 0 0     0 0 0 0 0 0 0 0");
  v18[21] = 0;
  strcpy((char *)v19, "0 00000 000 000 0 0 0 00000 000 000 0 0 0");
  v19[21] = 0;
  strcpy((char *)v20, "0     0       0   0 0     0       0   0 0");
  v20[21] = 0;
  strcpy((char *)v21, "000 0 0 0 000 0 0 0 000 0 0 0 000 0 0 0 0");
  v21[21] = 0;
  strcpy((char *)v22, "0 0 0 0 0 0   0 0 0 0 0 0 0 0 0   0 0 0 0");
  v22[21] = 0;
  strcpy((char *)v23, "0 0000000 0 000 00000 0000000 0 000 00000");
  v23[21] = 0;
  strcpy((char *)v24, "0@  0   0 0         0   0   0 0         0");
  v24[21] = 0;
  strcpy((char *)v25, "0 0 0 0 0 00000000000 0 0 0 0 00000000000");
  v25[21] = 0;
  strcpy((char *)v26, "0 0 0 0             0 0 0 0             0");
  v26[21] = 0;
  strcpy((char *)v27, "000 0 00000 0 000 00000 0 00000 0 000 000");
  v27[21] = 0;
  strcpy((char *)v28, "0         0 0   0   0         0 0   0   0");
  v28[21] = 0;
  strcpy(v29, "00000000000000000000000000000000000000000");
  memset(&v29[42], 0, 1014);
  v3 = 0;
  v5 = 15;
  v4 = 1;
  for ( i = 0; i <= 40; ++i )
    puts(&Buffer[44 * i]);
  while ( v5 != 7 || v4 != 40 )                 // (7,40)这个位置会退出循环
  {
    if ( v5 == 1 && v4 == 7 )                   // (1,7)这个位置会被抓起来,退出程序
    {
      printf("当你寻找到这里的时候,你发现它是可恶的偷猫人!?\n");
      printf("你被抓起来了...\n");
      Sleep(0x1388u);
      _loaddll(0);                              // 退出程序
    }
    if ( v5 == 1 && v4 == 27 )                  // (1,27)发现布偶猫
    {
      printf("这只猫咪好像不是笨橘猫,是一只流浪的布偶猫\n");
      printf("但是她好像有话和你说,你能解出她的话语嘛...\n");      // 但是她好像有话和你说,你能解出她的话语嘛...
      printf("布偶:喵喵喵");
      sub_401B90();
      v3 = 1;
      HIBYTE(v28[19]) = (unsigned __int8)"T";
    }
    if ( v5 == 19 && v4 == 39 && v3 == 1 )
    {
      printf("这一整迷宫世界都是假的\n");
      printf("你完全没有找到真正的迷宫\n");
      printf("这个迷宫已经开始支离破碎\n");
      printf("快点找到真正的迷宫世界,不然你将会一直陷入其中!!!\n");   // 快点找到真正的迷宫世界,不然你将会一直陷入其中!!!
      Sleep(0x1F40u);
      _loaddll(0);                              // 退出程序
    }
    ((void (__thiscall *)(int (***)()))**off_46200C)(off_46200C);
    v2 = _getch();                              // 方向输入
    ((void (__thiscall *)(int (***)()))**off_46200C)(off_46200C);
    switch ( v2 )
    {
      case 's':
        if ( Buffer[44 * v5 + 44 + v4] != '0' )
        {
          Buffer[44 * v5++ + v4] = ' ';
          Buffer[44 * v5 + v4] = '@';
        }
        break;
      case 'w':
        if ( Buffer[44 * v5 - 44 + v4] != '0' )
        {
          Buffer[44 * v5-- + v4] = ' ';
          Buffer[44 * v5 + v4] = '@';
        }
        break;
      case 'a':
        if ( Buffer[44 * v5 - 1 + v4] != '0' )
        {
          Buffer[44 * v5 + v4--] = ' ';
          Buffer[44 * v5 + v4] = '@';
        }
        break;
      default:
        if ( v2 == 100 && Buffer[44 * v5 + 1 + v4] != '0' )
        {
          Buffer[44 * v5 + v4++] = ' ';
          Buffer[44 * v5 + v4] = '@';
        }
        break;
    }
    sub_42AD51(&unk_453C04);                    // cls
    for ( j = 0; j <= 40; ++j )
      puts(&Buffer[44 * j]);
  }
  sub_42AD51(&unk_453C04);                      // cls
  Sleep(0x1F4u);
  printf("感谢你找到了笨橘猫并且带小猫咪跑了出来\n");
  printf("为了奖励你flag就是CatCTF{第一段翻译+_+第二段key}\n");
  printf(&asc_453C64);                          // 非常感谢你的爱心,让我们一起爱护流浪猫吧!!!
  printf("其中笨橘猫的那段话语彩蛋你发现了嘛!?");
  sub_42AD51("pause");
  return 0;
}

发现静态分析的代码与实际运行显示不同,说明存在反调试的步骤

根据作者提示https://moodyblue.cn/category/StupidOrangeCat2/找到如下函数,unk_462A40的值是关键,必须要进去才是正确代码

int sub_401ED0()
{
  int i; // [esp+D0h] [ebp-20h]
  int flOldProtect[2]; // [esp+E8h] [ebp-8h] BYREF

  __CheckForDebuggerJustMyCode(&unk_46401A);
  if ( unk_462A40 == 32 )
  {
    VirtualProtect(dword_402780, 0x7C5u, 0x40u, (PDWORD)flOldProtect);
    for ( i = 0; i < 1989; ++i )
      dword_402780[i] ^= unk_462A40;
  }
  return 0;
}

寻找调用unk_462A40并赋值的地方

int sub_402150()
{
  wchar_t *Buffer; // edi
  int v1; // ecx
  int v2; // eax
  bool v3; // zf

  Buffer = NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->CommandLine.Buffer;
  v1 = 256;
  HIWORD(v2) = 0;
  do
  {
    if ( !v1 )
      break;
    v3 = *Buffer++ == 0;
    --v1;
  }
  while ( !v3 );
  LOWORD(v2) = *(Buffer - 2);
  unk_462A40 = v2;
  return 0;
}

根据这个函数可以在xdbg里调试时下断点修改

回到之前sub_401ED0,其做了异或后得到正确代码,因此利用ida自带python可以得到真实代码

s=0x402780
for i in range(1989):
    ida_bytes.patch_byte(s+i,idc.get_wide_byte(s+i)^32)

在0x402780处C转为code,再右键转为函数,得到真实代码

int sub_402780()
{
  char v1; // [esp+3DFh] [ebp-82Dh]
  char v2; // [esp+3EBh] [ebp-821h]
  int v3; // [esp+454h] [ebp-7B8h]
  int v4; // [esp+460h] [ebp-7ACh]
  int i; // [esp+46Ch] [ebp-7A0h]
  int j; // [esp+46Ch] [ebp-7A0h]
  char Buffer[42]; // [esp+478h] [ebp-794h] BYREF
  __int16 v8; // [esp+4A2h] [ebp-76Ah]
  _WORD v9[22]; // [esp+4A4h] [ebp-768h] BYREF
  _WORD v10[22]; // [esp+4D0h] [ebp-73Ch] BYREF
  _WORD v11[22]; // [esp+4FCh] [ebp-710h] BYREF
  _WORD v12[22]; // [esp+528h] [ebp-6E4h] BYREF
  _WORD v13[22]; // [esp+554h] [ebp-6B8h] BYREF
  _WORD v14[22]; // [esp+580h] [ebp-68Ch] BYREF
  _WORD v15[22]; // [esp+5ACh] [ebp-660h] BYREF
  _WORD v16[22]; // [esp+5D8h] [ebp-634h] BYREF
  _WORD v17[22]; // [esp+604h] [ebp-608h] BYREF
  _WORD v18[22]; // [esp+630h] [ebp-5DCh] BYREF
  _WORD v19[22]; // [esp+65Ch] [ebp-5B0h] BYREF
  _WORD v20[22]; // [esp+688h] [ebp-584h] BYREF
  _WORD v21[22]; // [esp+6B4h] [ebp-558h] BYREF
  _WORD v22[22]; // [esp+6E0h] [ebp-52Ch] BYREF
  _WORD v23[22]; // [esp+70Ch] [ebp-500h] BYREF
  _WORD v24[22]; // [esp+738h] [ebp-4D4h] BYREF
  _WORD v25[22]; // [esp+764h] [ebp-4A8h] BYREF
  _WORD v26[22]; // [esp+790h] [ebp-47Ch] BYREF
  _WORD v27[22]; // [esp+7BCh] [ebp-450h] BYREF
  char v28[1060]; // [esp+7E8h] [ebp-424h] BYREF

  __CheckForDebuggerJustMyCode(&unk_46401A);
  printf("@为冒险者,请在迷宫中寻找笨橘猫吧!!\n");
  strcpy(Buffer, "00000000000000000000000000000000000000000");
  v8 = 0;
  strcpy((char *)v9, "0 0 0 0!    0     0 0 0 0 0*    0     0 0");
  v9[21] = 0;
  strcpy((char *)v10, "0 0 0 00000 00000 0 0 0 0 00000 00000 0 0");
  v10[21] = 0;
  strcpy((char *)v11, "0 0               0 0 0               0 0");
  v11[21] = 0;
  strcpy((char *)v12, "0 000 000 0 000 0 0 0 000 000 0 000 0 0 0");
  v12[21] = 0;
  strcpy((char *)v13, "0 0     0 0 0   0 0 0 0     0 0 0   0 0 0");
  v13[21] = 0;
  strcpy((char *)v14, "0 0 0 00000 000 000 0 0 0 00000 000 000 0");
  v14[21] = 0;
  strcpy((char *)v15, "0 0 0     0   0 0     0 0     0   0 0   0");
  v15[21] = 0;
  strcpy((char *)v16, "0 000 0 0 000 0 0 0 0 000 0 0 000 0 0 0 0");
  v16[21] = 0;
  strcpy((char *)v17, "0     0 0 0 0 0 0 0 0     0 0 0 0 0 0 0 0");
  v17[21] = 0;
  strcpy((char *)v18, "0 00000 000 000 0 0 0 00000 000 000 0 0 0");
  v18[21] = 0;
  strcpy((char *)v19, "0     0       0   0 0     0       0   0 0");
  v19[21] = 0;
  strcpy((char *)v20, "000 0 0 0 000 0 0 0 000 0 0 0 000 0 0 0 0");
  v20[21] = 0;
  strcpy((char *)v21, "0 0 0 0 0 0   0 0 0 0 0 0 0 0 0   0 0 0 0");
  v21[21] = 0;
  strcpy((char *)v22, "0 0000000 0 000 00000 0000000 0 000 00000");
  v22[21] = 0;
  strcpy((char *)v23, "0@  0   0 0         0   0   0 0         0");
  v23[21] = 0;
  strcpy((char *)v24, "0 0 0 0 0 00000000000 0 0 0 0 00000000000");
  v24[21] = 0;
  strcpy((char *)v25, "0 0 0 0             0 0 0 0             0");
  v25[21] = 0;
  strcpy((char *)v26, "000 0 00000 0 000 00000 0 00000 0 000 000");
  v26[21] = 0;
  strcpy((char *)v27, "0         0 0   0   0         0 0   0   0");
  v27[21] = 0;
  strcpy(v28, "00000000000000000000000000000000000000000");
  memset(&v28[42], 0, 1014);
  v1 = 0;
  v4 = 15;
  v3 = 1;
  for ( i = 0; i <= 40; ++i )
    puts(&Buffer[44 * i]);
  while ( v4 != 7 || v3 != 40 )
  {
    if ( v4 == 1 && v3 == 7 )
    {
      printf("当你寻找到这里的时候,你发现它是可恶的偷猫人!?\n");     // 当你寻找到这里的时候,你发现它是可恶的偷猫人!?
      printf("你被抓起来了...\n");
      Sleep(0x1388u);
      _loaddll(0);
    }
    if ( v4 == 1 && v3 == 27 )
    {
      printf("好耶!!!你终于找到了笨橘猫\n");               // 好耶!!!你终于找到了笨橘猫
      printf("她的毛上都沾满了污泥,毛发都结成了一块一块\n");        // 她的毛上都沾满了污泥,毛发都结成了一块一块
      printf("原本灵动皎洁的眼睛都变得黯淡无光\n");
      printf("你非常的心疼,你想立马上去带她回家\n");
      printf("但是她好像有话和你说,你能解出她的话语嘛...\n");      // 但是她好像有话和你说,你能解出她的话语嘛...
      printf(
        "笨橘猫:喵呜喵呜/呜喵/喵/呜呜喵喵呜喵/呜呜/喵呜/呜呜喵喵呜喵/喵呜呜喵/呜呜喵喵呜喵/呜喵喵喵喵/喵喵喵喵呜/呜呜喵喵呜喵/喵呜喵喵/呜呜喵喵呜喵/呜呜呜喵喵/喵喵喵喵呜\n");
      sub_402F60();                             // 上面是摩斯密码解密,此处是关键加密函数
      v1 = 1;
      HIBYTE(v27[19]) = (unsigned __int8)"T";
    }
    if ( v4 == 19 && v3 == 39 && v1 == 1 )
    {
      printf("你找到了这只可怜的流浪小猫\n");
      printf("但是你们不知道怎么逃出这个迷宫\n");
      printf("小猫经常在这片区域流浪\n");
      printf("这时小猫告诉你,他知道出口在哪里\n");             // 这时小猫告诉你,他知道出口在哪里
      printf("但是需要你先找到开启出口的钥匙,你能找到它嘛!!!\n");    // 但是需要你先找到开启出口的钥匙,你能找到它嘛!!!
      sub_402410();
      LOBYTE(v15[20]) = (unsigned __int8)&unk_453C00;
    }
    ((void (__thiscall *)(int (***)()))**off_46200C)(off_46200C);
    v2 = _getch();
    ((void (__thiscall *)(int (***)()))**off_46200C)(off_46200C);
    switch ( v2 )
    {
      case 's':
        if ( Buffer[44 * v4 + 44 + v3] != '0' )
        {
          Buffer[44 * v4++ + v3] = ' ';
          Buffer[44 * v4 + v3] = '@';
        }
        break;
      case 'w':
        if ( Buffer[44 * v4 - 44 + v3] != '0' )
        {
          Buffer[44 * v4-- + v3] = ' ';
          Buffer[44 * v4 + v3] = '@';
        }
        break;
      case 'a':
        if ( Buffer[44 * v4 - 1 + v3] != '0' )
        {
          Buffer[44 * v4 + v3--] = ' ';
          Buffer[44 * v4 + v3] = '@';
        }
        break;
      default:
        if ( v2 == 100 && Buffer[44 * v4 + 1 + v3] != '0' )
        {
          Buffer[44 * v4 + v3++] = ' ';
          Buffer[44 * v4 + v3] = '@';
        }
        break;
    }
    sub_42AD51(&unk_453C04);                    // cls
    for ( j = 0; j <= 40; ++j )
      puts(&Buffer[44 * j]);
  }
  sub_42AD51(&unk_453C04);                      // cls
  Sleep(0x1F4u);
  printf("感谢你找到了笨橘猫并且带小猫咪跑了出来\n");
  printf("为了奖励你flag就是CatCTF{第一段翻译+_+第二段key}\n");
  printf("非常感谢你的爱心,让我们一起爱护流浪猫吧!!!\n");
  printf("其中笨橘猫的那段话语彩蛋你发现了嘛!?\n");
  sub_42AD51("pause");
  return 0;
}

首先需要到达星号位置,即(1,27),首先https://www.geekku.com/tool/morse/自定义摩斯密码解码器解密得到CAT_IN_X_19_Y_39

image-20240301223847948.png

接着进入sub_402F60函数

int sub_402F60()
{
  char v1[27]; // [esp+D0h] [ebp-114h]
  unsigned __int8 v2; // [esp+EBh] [ebp-F9h]
  char v3; // [esp+F7h] [ebp-EDh]
  unsigned int i; // [esp+100h] [ebp-E4h]
  char v5[140]; // [esp+10Ch] [ebp-D8h] BYREF
  char v6[28]; // [esp+198h] [ebp-4Ch] BYREF
  char v7[28]; // [esp+1B4h] [ebp-30h] BYREF
  char v8[20]; // [esp+1D0h] [ebp-14h] BYREF

  __CheckForDebuggerJustMyCode(&unk_46401A);
  qmemcpy(v8, "wuwuwuyoucatchme", 16);
  v3 = 0;
  v2 = 0;
  printf("请输入你的翻译\n");
  while ( v3 != '\n' )
  {
    v3 = j___fgetchar();
    v7[v2++] = v3;                              // 输入字符串到v7
  }
  v1[0] = -74;
  v1[1] = 117;
  v1[2] = -31;
  v1[3] = 121;
  v1[4] = 112;
  v1[5] = -63;
  v1[6] = 39;
  v1[7] = 72;
  v1[8] = 9;
  v1[9] = 11;
  v1[10] = -74;
  v1[11] = 77;
  v1[12] = 2;
  v1[13] = -68;
  v1[14] = 6;
  v1[15] = 25;
  sub_403DF0(v5, v8);    // 发现0xA3B1BAC6硬编码,查询可知为sm4算法关键字,由此可知v8为密钥
  sub_403630(v5, 1, 16, v7, v6);
  for ( i = 0; i < 0x10; ++i )
  {
    if ( v6[i] != v1[i] )
    {
      printf(&asc_4533FC);                      // 笨橘猫好像不是这个意思,再想想吧!!
      Sleep(0x1388u);
      _loaddll(0);
    }
  }
  printf("你终于弄懂笨橘猫在说什么了\n");
  printf(&asc_45343C);                          // 原来是这样,笨橘猫看见了可怜的小猫咪流浪街头
  printf(&asc_45346C);                          // 而且这时有可怕的偷猫人想要偷走小猫,笨橘猫带着小猫逃跑
  printf(&asc_4534A4);                          // 跑进了下水管道,但是下水道太黑和小猫失散了..
  return printf(&asc_4534D4);                   // 快去找到小猫吧,她已经在地图上出现了
}

sm4解密

from sm4_code import SM4Cipher
v1 = [0]*16
v1[0] = -74;
v1[1] = 117;
v1[2] = -31;
v1[3] = 121;
v1[4] = 112;
v1[5] = -63;
v1[6] = 39;
v1[7] = 72;
v1[8] = 9;
v1[9] = 11;
v1[10] = -74;
v1[11] = 77;
v1[12] = 2;
v1[13] = -68;
v1[14] = 6;
v1[15] = 25;
ciphertext = "".join([hex(i)[2:].zfill(2) if i > 0 else hex((-i ^ 0xff)+1)[2:].zfill(2) for i in v1])
key = bytes.fromhex(bytes("wuwuwuyoucatchme", "utf-8").hex())  # 128bit密钥
ciphertext = bytes.fromhex(ciphertext)  # 128bit明文
sm4 = SM4Cipher(key)
plaintext = sm4.decrypt(ciphertext).hex()
print(bytes.fromhex(plaintext).decode())    # CAT_IN_X_19_Y_39

得到和上面摩斯密码解密一样的结果,输入会提示去找对应位置的小猫(19,39)

再去看sub_402410函数根据魔术数字可知为RC5加密,在网上找了两个脚本改编了下

#include <iostream>
#include <cmath>
#include <string.h>
using namespace std;
typedef unsigned int WORD;   
#define w 32        // 字长 32bit
#define r 12        // 加密轮数12 
#define b 16        // 主密钥(字节为单位8bit)个数  这里有16个
#define t 26        // 2*r+2=12*2+2=26 
#define c 4         // 主密钥个数*8/w = 16*8/32  
WORD S[t];          // 扩展密钥表
WORD P = 0xB7E15163, Q = 0x9E3779B9;    // 魔术常量
#define ROTL(x, y) (((x) << (y & (w - 1))) | ((x) >> (w - (y & (w - 1)))))
#define ROTR(x, y) (((x) >> (y & (w - 1))) | ((x) << (w - (y & (w - 1)))))

typedef unsigned long int FOURBYTEINT;//四字节 
typedef unsigned short int TWOBYTEINT;//2字节 
typedef unsigned char BYTE;

void InitialKey(unsigned char *KeyK)
{
    for (int i = 0; i < b; i++)
        KeyK[i] = 0;
    KeyK[0] = 3;
    for (int j = 1; j < b; j++)
        KeyK[j] = (int)pow(3, j) % (255 - j);
}

void rc5_keygen(unsigned char *KeyK)
{
    WORD u = w / 8, A = 0, B = 0, L[c];
  
    S[0] = P;
    for (int i = 1; i < t; i++) 
        S[i] = S[i - 1] + Q;

    for (int i = b - 1; i != -1; i--)
    {
        L[i / u] = (L[i / u] << 8) + KeyK[i];
    }
    int i = 0, j = 0;
    for (int k = 0; k < 3 * t; k++)
    {
        A = S[i] = _rotl(S[i] + A + B, 3);
        i = (i + 1) % t;
        B = L[j] = _rotl(L[j] + A + B, A + B);
        j = (j + 1) % c;
    }
}

void Encipher(WORD *plain, WORD *cipher)
{
    WORD X, Y;
    for (int i = 0; i < 4; i += 2)
    {
        X = plain[i] + S[0];
        Y = plain[i + 1] + S[1];
        for (int j = 1; j <= r; j++)
        {
            X = _rotl((X ^ Y), Y) + S[2 * j];
            Y = _rotl((Y ^ X), X) + S[2 * j + 1];
        }
        cipher[i] = X;
        cipher[i + 1] = Y; 
    }
}

void Decipher(WORD *cipher, WORD *plain)
{
    WORD X, Y;
    for (int i = 0; i < 4; i += 2)
    {
        X = cipher[i];
        Y = cipher[i + 1];
        for (int j = r; j > 0; j--)
        {
            Y = _rotr(Y - S[2 * j + 1], X) ^ X;
            X = _rotr(X - S[2 * j], Y) ^ Y;
        }
        plain[i] = X - S[0];
        plain[i + 1] = Y - S[1];
    }
}

void string2int(char *s, WORD *intA) {
    if (strlen(s)!=16) {
        printf("String size must be 32.");
        exit(0);
    }
    for (int i = 0; i < 4; i++)
        intA[i] = ((int)s[4*i]<<24) + ((int)s[4*i+1]<<16) + ((int)s[4*i+2]<<8) + ((int)s[4*i+3]);
}

void int2string(WORD *intA, char *s) {
    for (int i = 0; i < 4; i++) {
        *s++ = (char)(intA[i]>>24); 
        *s++ = (char)(intA[i]>>16&0xff);
        *s++ = (char)(intA[i]>>8&0xff); 
        *s++ = (char)(intA[i]&0xff);
    }
}

int main(void) {
    unsigned char KeyK[16];
    InitialKey(KeyK);
    rc5_keygen(KeyK); 
  
    WORD cipher[4] = {0x936AB12C, 0xED8330B5, 0xEE5C5E88, 0xE10B508C};       
    WORD plain[4] = {0};  
    Decipher(cipher, plain); //解密 

    // char input[] = "LIKE_OR_LOVE_CAT";
    // string2int(input, plain);
    char output[17];
    int2string(plain, output);
    output[16] = '\0';
    printf("%s", output);
}

得到LIKE_OR_LOVE_CAT,因此flag为CatCTF{CAT_IN_X_19_Y_39_LIKE_OR_LOVE_CAT}

handcrafted-pyc HITCON

给的文件名里含有pyc,打开查看发现正是python代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import marshal, zlib, base64

exec(marshal.loads(zlib.decompress(base64.b64decode('eJyNVktv00AQXm/eL0igiaFA01IO4cIVCUGFBBJwqRAckLhEIQmtRfPwI0QIeio/hRO/hJ/CiStH2M/prj07diGRP43Hs9+MZ2fWMxbnP6mux+oK9xVMHPFViLdCTB0xkeKDFEFfTIU4E8KZq8dCvB4UlN3hGEsdddXU9QTLv1eFiGKGM4cKUgsFCNLFH7dFrS9poayFYmIZm1b0gyqxMOwJaU3r6xs9sW1ooakXuRv+un7Q0sIlLVzOCZq/XtsK2oTSYaZlStogXi1HV0iazoN2CV2HZeXqRQ54TlJRb7FUlKyUatISsdzo+P7UU1Gb1POdMruckepGwk9tIXQTftz2yBaT5JQovWvpSa6poJPuqgao+b9l5Aj/R+mLQIP4f6Q8Vb3g/5TB/TJxWGdZr9EQrmn99fwKtTvAZGU7wzS7GNpZpDm2JgCrr8wrmPoo54UqGampFIeS9ojXjc4E2yI06bq/4DRoUAc0nVnng4k6p7Ks0+j/S8z9V+NZ5dhmrJUM/y7JTJeRtnJ2TSYJvsFq3CQt/vnfqmQXt5KlpuRcIvDAmhnn2E0t9BJ3SvB/SfLWhuOWNiNVZ+h28g4wlwUp00w95si43rZ3r6+fUIEdgOZbQAsyFRRvBR6dla8KCzRdslar7WS+a5HFb39peIAmG7uZTHVm17Czxju4m6bayz8e7J40DzqM0jr0bmv9PmPvk6y5z57HU8wdTDHeiUJvBMAM4+0CpoAZ4BPgJeAYEAHmgAUgAHiAj4AVAGORtwd4AVgC3gEmgBBwCPgMWANOAQ8AbwBHgHuAp4D3gLuARwoGmNUizF/j4yDC5BWM1kNvvlxFA8xikRrBxHIUhutFMBlgQoshhPphGAXe/OggKqqb2cibxwuEXjUcQjccxi5eFRL1fDSbKrUhy2CMb2aLyepkegDWsBwPlrVC0/kLHmeCBQ=='))))

但是运行发现报错 ValueError: bad marshal data (unknown type code)

image-20240303222800417.png

查询可知的用python2运行,分析代码:

  • base64.b64decode:base64解密
  • zlib.decompress:解压字符串
  • marshal.loads:将字节对象转换为值,这里可以知道上一步得到的正是字节码
  • exec:很明显是执行代码的函数

要想反编译字节码,先提取到pyc文件中:

import zlib, base64

handcraft_pyc = zlib.decompress(base64.b64decode('eJyNVktv00AQXm/eL0igiaFA01IO4cIVCUGFBBJwqRAckLhEIQmtRfPwI0QIeio/hRO/hJ/CiStH2M/prj07diGRP43Hs9+MZ2fWMxbnP6mux+oK9xVMHPFViLdCTB0xkeKDFEFfTIU4E8KZq8dCvB4UlN3hGEsdddXU9QTLv1eFiGKGM4cKUgsFCNLFH7dFrS9poayFYmIZm1b0gyqxMOwJaU3r6xs9sW1ooakXuRv+un7Q0sIlLVzOCZq/XtsK2oTSYaZlStogXi1HV0iazoN2CV2HZeXqRQ54TlJRb7FUlKyUatISsdzo+P7UU1Gb1POdMruckepGwk9tIXQTftz2yBaT5JQovWvpSa6poJPuqgao+b9l5Aj/R+mLQIP4f6Q8Vb3g/5TB/TJxWGdZr9EQrmn99fwKtTvAZGU7wzS7GNpZpDm2JgCrr8wrmPoo54UqGampFIeS9ojXjc4E2yI06bq/4DRoUAc0nVnng4k6p7Ks0+j/S8z9V+NZ5dhmrJUM/y7JTJeRtnJ2TSYJvsFq3CQt/vnfqmQXt5KlpuRcIvDAmhnn2E0t9BJ3SvB/SfLWhuOWNiNVZ+h28g4wlwUp00w95si43rZ3r6+fUIEdgOZbQAsyFRRvBR6dla8KCzRdslar7WS+a5HFb39peIAmG7uZTHVm17Czxju4m6bayz8e7J40DzqM0jr0bmv9PmPvk6y5z57HU8wdTDHeiUJvBMAM4+0CpoAZ4BPgJeAYEAHmgAUgAHiAj4AVAGORtwd4AVgC3gEmgBBwCPgMWANOAQ8AbwBHgHuAp4D3gLuARwoGmNUizF/j4yDC5BWM1kNvvlxFA8xikRrBxHIUhutFMBlgQoshhPphGAXe/OggKqqb2cibxwuEXjUcQjccxi5eFRL1fDSbKrUhy2CMb2aLyepkegDWsBwPlrVC0/kLHmeCBQ=='))
f = open('out.pyc', 'wb')
f.write(handcraft_pyc)
f.close()

使用uncompyle6发现提示魔数对不上,查看文件hex值可以发现没有文件头

image-20240303224106571.png

随便写一个py,然后编译提取文件头,用16进制编辑器添加即可

import py_compile
py_compile.compile('gen_pyc27.py')

再用uncompyle6反编译,结果提示failed,但是生成了out.py(uncompyle6 -o out.py .out.pyc),特别长

# uncompyle6 version 3.9.0
# Python bytecode version base 2.7 (62211)
# Decompiled from: Python 3.9.12 (main, Apr  4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: <string>
# Compiled at: 2024-03-03 23:45:48


def main--- This code section failed: ---

 L.   1         0  LOAD_GLOBAL           0  'chr'    # 函数
                3  LOAD_CONST               108        # 传入的值
                6  CALL_FUNCTION_1       1  None    # 调用函数
                9  LOAD_GLOBAL           0  'chr'
               12  LOAD_CONST               108
               15  CALL_FUNCTION_1       1  None
               18  LOAD_GLOBAL           0  'chr'
               21  LOAD_CONST               97
               24  CALL_FUNCTION_1       1  None
               27  LOAD_GLOBAL           0  'chr'
               30  LOAD_CONST               67
               33  CALL_FUNCTION_1       1  None
               36  ROT_TWO                             # 交换最顶部的两个堆栈项 相当于a,b=b,a        
               37  BINARY_ADD                         # 取出栈顶两个元素求和,得到的结果再入栈  
               38  ROT_TWO      
               39  BINARY_ADD   
               40  ROT_TWO      
               41  BINARY_ADD
               ...  
             2212  PRINT_ITEM   
             2213  PRINT_NEWLINE_CONT

Parse error at or near `None' instruction at offset -1


if __name__ == '__main__':
    main()

但是通过观察可以发现这些字节码有一定规律,都是先chr函数取几个字符,然后是ROT_TWO和BINARY_TWO两个指令。其中带有字符常量,因此分析下这些字节码指令

由上分析可知,这是在做字符交换然后相加,最终得到的是字符串

写脚本模拟下栈:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# import marshal, zlib, base64
#
# # exec(marshal.loads(zlib.decompress(base64.b64decode('eJyNVktv00AQXm/eL0igiaFA01IO4cIVCUGFBBJwqRAckLhEIQmtRfPwI0QIeio/hRO/hJ/CiStH2M/prj07diGRP43Hs9+MZ2fWMxbnP6mux+oK9xVMHPFViLdCTB0xkeKDFEFfTIU4E8KZq8dCvB4UlN3hGEsdddXU9QTLv1eFiGKGM4cKUgsFCNLFH7dFrS9poayFYmIZm1b0gyqxMOwJaU3r6xs9sW1ooakXuRv+un7Q0sIlLVzOCZq/XtsK2oTSYaZlStogXi1HV0iazoN2CV2HZeXqRQ54TlJRb7FUlKyUatISsdzo+P7UU1Gb1POdMruckepGwk9tIXQTftz2yBaT5JQovWvpSa6poJPuqgao+b9l5Aj/R+mLQIP4f6Q8Vb3g/5TB/TJxWGdZr9EQrmn99fwKtTvAZGU7wzS7GNpZpDm2JgCrr8wrmPoo54UqGampFIeS9ojXjc4E2yI06bq/4DRoUAc0nVnng4k6p7Ks0+j/S8z9V+NZ5dhmrJUM/y7JTJeRtnJ2TSYJvsFq3CQt/vnfqmQXt5KlpuRcIvDAmhnn2E0t9BJ3SvB/SfLWhuOWNiNVZ+h28g4wlwUp00w95si43rZ3r6+fUIEdgOZbQAsyFRRvBR6dla8KCzRdslar7WS+a5HFb39peIAmG7uZTHVm17Czxju4m6bayz8e7J40DzqM0jr0bmv9PmPvk6y5z57HU8wdTDHeiUJvBMAM4+0CpoAZ4BPgJeAYEAHmgAUgAHiAj4AVAGORtwd4AVgC3gEmgBBwCPgMWANOAQ8AbwBHgHuAp4D3gLuARwoGmNUizF/j4yDC5BWM1kNvvlxFA8xikRrBxHIUhutFMBlgQoshhPphGAXe/OggKqqb2cibxwuEXjUcQjccxi5eFRL1fDSbKrUhy2CMb2aLyepkegDWsBwPlrVC0/kLHmeCBQ=='))))
#
# handcraft_pyc = zlib.decompress(base64.b64decode('eJyNVktv00AQXm/eL0igiaFA01IO4cIVCUGFBBJwqRAckLhEIQmtRfPwI0QIeio/hRO/hJ/CiStH2M/prj07diGRP43Hs9+MZ2fWMxbnP6mux+oK9xVMHPFViLdCTB0xkeKDFEFfTIU4E8KZq8dCvB4UlN3hGEsdddXU9QTLv1eFiGKGM4cKUgsFCNLFH7dFrS9poayFYmIZm1b0gyqxMOwJaU3r6xs9sW1ooakXuRv+un7Q0sIlLVzOCZq/XtsK2oTSYaZlStogXi1HV0iazoN2CV2HZeXqRQ54TlJRb7FUlKyUatISsdzo+P7UU1Gb1POdMruckepGwk9tIXQTftz2yBaT5JQovWvpSa6poJPuqgao+b9l5Aj/R+mLQIP4f6Q8Vb3g/5TB/TJxWGdZr9EQrmn99fwKtTvAZGU7wzS7GNpZpDm2JgCrr8wrmPoo54UqGampFIeS9ojXjc4E2yI06bq/4DRoUAc0nVnng4k6p7Ks0+j/S8z9V+NZ5dhmrJUM/y7JTJeRtnJ2TSYJvsFq3CQt/vnfqmQXt5KlpuRcIvDAmhnn2E0t9BJ3SvB/SfLWhuOWNiNVZ+h28g4wlwUp00w95si43rZ3r6+fUIEdgOZbQAsyFRRvBR6dla8KCzRdslar7WS+a5HFb39peIAmG7uZTHVm17Czxju4m6bayz8e7J40DzqM0jr0bmv9PmPvk6y5z57HU8wdTDHeiUJvBMAM4+0CpoAZ4BPgJeAYEAHmgAUgAHiAj4AVAGORtwd4AVgC3gEmgBBwCPgMWANOAQ8AbwBHgHuAp4D3gLuARwoGmNUizF/j4yDC5BWM1kNvvlxFA8xikRrBxHIUhutFMBlgQoshhPphGAXe/OggKqqb2cibxwuEXjUcQjccxi5eFRL1fDSbKrUhy2CMb2aLyepkegDWsBwPlrVC0/kLHmeCBQ=='))
# f = open('out.pyc', 'wb')
# f.write(handcraft_pyc)
# f.close()
import re

stack = []
with open("out.py", "r") as f:
    while True:
        code = f.readline()
        if code == '':
            break
        chr_value = re.findall("LOAD_CONST\s*(\d*)\n", code)
        if len(chr_value):
            chr_value = chr(int(chr_value[0]))
            stack.append(chr_value)
        if "ROT_TWO" in code and '759' not in code:    # 759不属于上述规律,属于其他作用的交换栈值,因此排除
            a = stack.pop()
            b = stack.pop()
            stack.extend([a, b])
        if "BINARY_ADD" in code:
            a = stack.pop()
            b = stack.pop()
            stack.append(b+a)
print("".join(stack))

"""
Call me a Python virtual machine! I can interpret Python bytecodes!!!hitcon{Now you can compile and run Python bytecode in your brain!}password: Wrong password... Please try again. Do not brute force. =)
"""

https://rycbar77.github.io/2020/03/16/handcrafted-pyc-%E6%94%BB%E9%98%B2%E4%B8%96%E7%95%8C/

其实还可以查看下中间的判断代码:==POP_JUMP_IF_FALSE==很明显是逻辑判断处,尝试修改源pyc,False改为True

              741  JUMP_ABSOLUTE       759  'to 759'
              744  LOAD_GLOBAL           1  'raw_input'
              747  JUMP_ABSOLUTE      1480  'to 1480'
              750  LOAD_FAST             0  'password'
              753  COMPARE_OP            2  ==
              756  JUMP_ABSOLUTE       767  'to 767'
              759  ROT_TWO      
              760  STORE_FAST            0  'password'
              763  POP_TOP      
              764  JUMP_BACK           744  'to 744'
              767  POP_JUMP_IF_FALSE  1591  'to 1591'
              770  LOAD_GLOBAL           0  'chr'

POP_JUMP_IF_FALSE对应的值为114,即0x72;POP_JUMP_IF_TRUE对应的值为115,即0x73

1591=0x637,因此hex找 72 37 06(小端存储)替换为 73 37 06

import opcode
# 查询指令集指令的值
for op in range(len(opcode.opname)):
    print('ox%.2X(%.3d): %s' % (op, op, opcode.opname[op]))

运行后直接打印flag

image-20240308165350295.png

babybc-2021

https://www.nssctf.cn/problem/841

给了个bc文件,没见过,搜了半天以为是bitcomet(种子下载器)打开的,最后发现不对,忙活半天

谷歌大法好,搜到了https://blog.csdn.net/pc153262603/article/details/89553688

博客里说bc后缀的文件是bitcode的二进制格式,可以使用clang编译为可执行文件

clang baby.bc -o baby

ida64分析

int __fastcall main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 v4; // [rsp+8h] [rbp-20h]
  unsigned __int64 i; // [rsp+10h] [rbp-18h]
  size_t v6; // [rsp+18h] [rbp-10h]

  __isoc99_scanf(&unk_403004, input, envp);     // 输入
  if ( (unsigned int)strlen(input) == 25 )      // 长度25
  {
    if ( input[0] )
    {
      if ( (unsigned __int8)(input[0] - 48) > 5u )// 大于'5'
        return 0;
      v6 = strlen(input);
      for ( i = 1LL; ; ++i )
      {
        v4 = i;
        if ( i >= v6 )
          break;
        if ( (unsigned __int8)(input[v4] - 48) > 5u )
          return 0;
      }
    }                                           // 前面限制了范围0-5
    if ( (fill_number((__int64)input) & 1) != 0 && (docheck() & 1) != 0 )
      printf("CISCN{MD5(%s)}", input);
  }
  return 0;
}

首先看fill_number函数,分析可知此函数负责填充map数组,要求是输入字符均不为0,即1-4范围

__int64 __fastcall fill_number(__int64 a1)
{
  char v2; // [rsp+1h] [rbp-69h]
  char v3; // [rsp+11h] [rbp-59h]
  char v4; // [rsp+21h] [rbp-49h]
  char v5; // [rsp+31h] [rbp-39h]
  char v6; // [rsp+40h] [rbp-2Ah]
  char v7; // [rsp+41h] [rbp-29h]
  __int64 v8; // [rsp+4Ah] [rbp-20h]
  __int64 v9; // [rsp+52h] [rbp-18h]
  __int64 v10; // [rsp+5Ah] [rbp-10h]

  v10 = 0LL;
  do
  {
    v9 = v10;
    v8 = 5 * v10;
    v7 = *(_BYTE *)(a1 + 5 * v10);              // 遍历字符串
    if ( map[5 * v10] )                         // 初始有值,因此必须确保当不为0时,v7=='0'
    {
      v6 = 0;
      if ( v7 != '0' )
        return v6 & 1;                          // 0
    }
    else
    {
      map[5 * v10] = v7 - '0';                  // 赋值
    }
    v5 = *(_BYTE *)(a1 + v8 + 1);               // 每五位的第二个
    if ( map[5 * v10 + 1] )
    {
      v6 = 0;
      if ( v5 != '0' )
        return v6 & 1;
    }
    else
    {
      map[5 * v10 + 1] = v5 - 48;
    }
    v4 = *(_BYTE *)(a1 + v8 + 2);
    if ( map[5 * v10 + 2] )
    {
      v6 = 0;
      if ( v4 != '0' )
        return v6 & 1;
    }
    else
    {
      map[5 * v10 + 2] = v4 - 48;
    }
    v3 = *(_BYTE *)(a1 + v8 + 3);
    if ( map[5 * v10 + 3] )
    {
      v6 = 0;
      if ( v3 != '0' )
        return v6 & 1;
    }
    else
    {
      map[5 * v10 + 3] = v3 - 48;
    }
    v2 = *(_BYTE *)(a1 + v8 + 4);
    if ( map[5 * v10 + 4] )
    {
      v6 = 0;
      if ( v2 != '0' )
        return v6 & 1;
    }
    else
    {
      map[5 * v10 + 4] = v2 - 48;
    }
    ++v10;
    v6 = 1;
  }
  while ( v9 + 1 < 5 );                         // 必须完整走完循环,使得v6=1,才能保证return 1
  return v6 & 1;
}

再去看docheck函数,可知row和col数组比较关键

__int64 docheck()
{
  char v1; // [rsp+2Eh] [rbp-9Ah]
  __int64 v2; // [rsp+30h] [rbp-98h]
  __int64 v3; // [rsp+40h] [rbp-88h]
  __int64 v4; // [rsp+50h] [rbp-78h]
  __int64 v5; // [rsp+58h] [rbp-70h]
  char *v6; // [rsp+68h] [rbp-60h]
  __int64 v7; // [rsp+70h] [rbp-58h]
  char v8; // [rsp+7Fh] [rbp-49h]
  char *v9; // [rsp+88h] [rbp-40h]
  __int64 v10; // [rsp+90h] [rbp-38h]
  __int64 v11; // [rsp+98h] [rbp-30h]
  __int64 v12; // [rsp+A8h] [rbp-20h]
  char v13[6]; // [rsp+BCh] [rbp-Ch] BYREF
  char v14[6]; // [rsp+C2h] [rbp-6h] BYREF

  v12 = 0LL;
  do
  {
    v10 = v12;
    memset(v14, 0, sizeof(v14));                // v14初始均为0
    v9 = &v14[(unsigned __int8)map[5 * v12]];   // v14下标只有0-5
    if ( *v9
      || (*v9 = 1, v14[(unsigned __int8)map[5 * v12 + 1]])
      || (v14[(unsigned __int8)map[5 * v12 + 1]] = 1, v14[(unsigned __int8)map[5 * v12 + 2]])
      || (v14[(unsigned __int8)map[5 * v12 + 2]] = 1, v14[(unsigned __int8)map[5 * v12 + 3]])
      || (v14[(unsigned __int8)map[5 * v12 + 3]] = 1, v14[(unsigned __int8)map[5 * v12 + 4]]) )
    {                                           // 要想确保不进入if,必须保证5*v12!=5*v12+1,5*v12+1!=5v12+2...以此类推
      v8 = 0;                                   // 因此map每行的五个都不同
      return v8 & 1;
    }
    ++v12;
  }
  while ( v10 + 1 < 5 );                        // 这段循环分析是个很巧妙的判断矩阵
  v11 = 0LL;
  while ( 1 )
  {
    v7 = v11;
    memset(v13, 0, sizeof(v13));
    v6 = &v13[(unsigned __int8)map[v11]];
    if ( *v6 )
      break;
    *v6 = 1;
    if ( v13[(unsigned __int8)byte_405035[v11]] )// 同理每列也不同
      break;
    v13[(unsigned __int8)byte_405035[v11]] = 1;
    if ( v13[(unsigned __int8)byte_40503A[v11]] )
      break;
    v13[(unsigned __int8)byte_40503A[v11]] = 1;
    if ( v13[(unsigned __int8)byte_40503F[v11]] )
      break;
    v13[(unsigned __int8)byte_40503F[v11]] = 1;
    if ( v13[(unsigned __int8)byte_405044[v11]] )
      break;
    ++v11;
    if ( v7 + 1 >= 5 )
    {
      v5 = 0LL;
      while ( 1 )
      {
        v4 = v5;                                // 这部分循环是根据row数组对map作限制
        if ( row[4 * v5] == 1 )
        {
          if ( (unsigned __int8)map[5 * v5] < (unsigned __int8)map[5 * v5 + 1] )
            goto LABEL_27;
        }
        else if ( row[4 * v5] == 2 && (unsigned __int8)map[5 * v5] > (unsigned __int8)map[5 * v5 + 1] )
        {
LABEL_27:
          v8 = 0;
          return v8 & 1;
        }
        if ( byte_405051[4 * v5] == 1 )         // row[4*v5+1]
        {
          if ( (unsigned __int8)map[5 * v5 + 1] < (unsigned __int8)map[5 * v5 + 2] )
            goto LABEL_27;
        }
        else if ( byte_405051[4 * v5] == 2 && (unsigned __int8)map[5 * v5 + 1] > (unsigned __int8)map[5 * v5 + 2] )
        {
          goto LABEL_27;
        }
        if ( byte_405052[4 * v5] == 1 )
        {
          if ( (unsigned __int8)map[5 * v5 + 2] < (unsigned __int8)map[5 * v5 + 3] )
            goto LABEL_27;
        }
        else if ( byte_405052[4 * v5] == 2 && (unsigned __int8)map[5 * v5 + 2] > (unsigned __int8)map[5 * v5 + 3] )
        {
          goto LABEL_27;
        }
        if ( byte_405053[4 * v5] == 1 )
        {
          if ( (unsigned __int8)map[5 * v5 + 3] < (unsigned __int8)map[5 * v5 + 4] )
            goto LABEL_27;
        }
        else if ( byte_405053[4 * v5] == 2 && (unsigned __int8)map[5 * v5 + 3] > (unsigned __int8)map[5 * v5 + 4] )
        {
          goto LABEL_27;
        }
        ++v5;
        if ( v4 + 1 >= 5 )
        {
          v3 = 0LL;
          while ( 1 )
          {
            v2 = v3 + 1;                        // 要想v2大于等于4,v3必须循环加了4次
            if ( col[5 * v3] == 1 )             // 同理这部分是根据col数组对map做限制
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3] > (unsigned __int8)map[5 * v2] )
                goto LABEL_26;
            }
            else if ( col[5 * v3] == 2 )
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3] < (unsigned __int8)map[5 * v2] )
              {
LABEL_26:
                v8 = v1;
                return v8 & 1;                  // 只有此处是正确出口,且需要确保v1=1
              }
            }
            if ( byte_405071[5 * v3] == 1 )
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3 + 1] > (unsigned __int8)map[5 * v2 + 1] )
                goto LABEL_26;
            }
            else if ( byte_405071[5 * v3] == 2 )
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3 + 1] < (unsigned __int8)map[5 * v2 + 1] )
                goto LABEL_26;
            }
            if ( byte_405072[5 * v3] == 1 )
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3 + 2] > (unsigned __int8)map[5 * v2 + 2] )
                goto LABEL_26;
            }
            else if ( byte_405072[5 * v3] == 2 )
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3 + 2] < (unsigned __int8)map[5 * v2 + 2] )
                goto LABEL_26;
            }
            if ( byte_405073[5 * v3] == 1 )
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3 + 3] > (unsigned __int8)map[5 * v2 + 3] )
                goto LABEL_26;
            }
            else if ( byte_405073[5 * v3] == 2 )
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3 + 3] < (unsigned __int8)map[5 * v2 + 3] )
                goto LABEL_26;
            }
            if ( byte_405074[5 * v3] == 1 )
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3 + 4] > (unsigned __int8)map[5 * v2 + 4] )
                goto LABEL_26;
            }
            else if ( byte_405074[5 * v3] == 2 )
            {
              v1 = 0;
              if ( (unsigned __int8)map[5 * v3 + 4] < (unsigned __int8)map[5 * v2 + 4] )
                goto LABEL_26;
            }
            ++v3;
            v1 = 1;                             // 此处v1=1,因此需要进入下面的条件判断使得跳转成立
            if ( v2 >= 4 )
              goto LABEL_26;
          }
        }
      }
    }
  }
  v8 = 0;
  return v8 & 1;
}

首先ipython获取初始的row、col、map

addr = 0x405050
row = []
for i in range(20):
    row.append(idc.get_wide_byte(addr+i))
print(row)

addr = 0x405070
col = []
for i in range(20):
    col.append(idc.get_wide_byte(addr+i))
print(col)

addr = 0x405030
map = []
for i in range(25):
    map.append(idc.get_wide_byte(addr+i))
print(map)
'''
[0, 0, 0, 1, 1, 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0]
[0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0]
'''

图省事直接手动计算map,是一个对我来说比较有难度的数独,做了半天,网上有直接用z3包来算的

IMG_20240514_154527_edit_7364539052337190.jpg

把红色4和3位置替换为0就是输入1425353142350212150442315,做一个md5加密32位小写

ezbyte2023

https://www.nssctf.cn/problem/4052

完全不会,看wp:

首先linux下使用 readelf -Wwf ezbyte > output.txt 生成DWARF调试信息,发现很异常的表达式,属于r12的

  DW_CFA_val_expression: r12 (r12) (DW_OP_constu: 2616514329260088143; DW_OP_constu: 1237891274917891239; DW_OP_constu: 1892739; DW_OP_breg12 (r12): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_constu: 8502251781212277489; DW_OP_constu: 1209847170981118947; DW_OP_constu: 8971237; DW_OP_breg13 (r13): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_or; DW_OP_constu: 2451795628338718684; DW_OP_constu: 1098791727398412397; DW_OP_constu: 1512312; DW_OP_breg14 (r14): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_or; DW_OP_constu: 8722213363631027234; DW_OP_constu: 1890878197237214971; DW_OP_constu: 9123704; DW_OP_breg15 (r15): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_or)

根据官方文档解析即可

DW_OP_constu: 2616514329260088143:将一个无符号整数压入堆栈。
DW_OP_constu: 1237891274917891239:将另一个无符号整数压入堆栈。
DW_OP_constu: 1892739:将第三个无符号整数压入堆栈。
DW_OP_breg12 (r12): 0:从 r12 这个寄存器中读取一个值,并将其加上偏移量 0。
DW_OP_plus:从堆栈中弹出两个值,相加后再将结果压入堆栈。
DW_OP_xor:从堆栈中弹出两个值,进行异或运算后再将结果压入堆栈。
DW_OP_xor:重复前面的操作,再进行一次异或运算。
DW_OP_constu: 8502251781212277489:将另一个无符号整数压入堆栈。
DW_OP_constu: 1209847170981118947:将另一个无符号整数压入堆栈。
DW_OP_constu: 8971237:将第三个无符号整数压入堆栈。
DW_OP_breg13 (r13): 0:从 r13 这个寄存器中读取一个值,并将其加上偏移量 0。
DW_OP_plus:从堆栈中弹出两个值,相加后再将结果压入堆栈。
DW_OP_xor:从堆栈中弹出两个值,进行异或运算后再将结果压入堆栈。
DW_OP_xor:重复前面

...重复

代码解密

def decrypt():
    r15 = (8722213363631027234 ^ 1890878197237214971) - 9123704
    r14 = (2451795628338718684 ^ 1098791727398412397) - 1512312
    r13 = (8502251781212277489 ^ 1209847170981118947) - 8971237
    r12 = (2616514329260088143 ^ 1237891274917891239) - 1892739
    print(hex(r12))
    print(hex(r13))
    print(hex(r14))
    print(hex(r15))
    import binascii
    hexstring = "65363039656662352d653730652d346539342d616336392d6163333164393663"
    print("flag{" + binascii.unhexlify(hexstring).decode(encoding="utf-8") + "3861}")
    # print(binascii.unhexlify(hex(r15)[2:]).decode('utf-8'), end='')
    # print(binascii.unhexlify(hex(r14)[2:]).decode('utf-8'), end='')
    # print(binascii.unhexlify(hex(r13)[2:]).decode('utf-8'), end=' ')
    # print(binascii.unhexlify(hex(r12)[2:]).decode('utf-8'), end=' ')

decrypt()

moveAside-2023

https://www.nssctf.cn/problem/4053

demovfuscator:http://hexo.iyzyi.com/2020/01/21/demovfuscator%20docker%E9%95%9C%E5%83%8F/

docker配置好环境(我自己的乱了)。使用./demov -g cfg.dot -o patched FILE(源文件)指令,获取patched再分析,逻辑会稍微清晰些,一些函数能够识别

网上分析的很清楚,读代码太难懂,基本都根据一串特殊字符串猜测

image-20240515230849618.png

猜测异或,发现有一定规律,低四位都会取反,同时之间重复出现的是-

题目没给完整,flag里面都是十六进制字符和-

首先计算出字符映射

alphabet = "0123456789abcdef"
# 从IDA获取的42长的byte数组
raw = [0x67, 0x9D, 0x60, 0x66, 0x8A, 0x56, 0x49, 0x50, 0x65, 0x65,
       0x60, 0x55, 0x64, 0x5C, 0x65, 0x48, 0x50, 0x51, 0x5C, 0x55,
       0x67, 0x51, 0x57, 0x5C, 0x49, 0x67, 0x54, 0x63, 0x5C, 0x54,
       0x62, 0x52, 0x56, 0x54, 0x54, 0x50, 0x49, 0x53, 0x52, 0x52,
       0x56, 0x8C]
flag_dict = {0x67:'f', 0x9d:'l', 0x60:'a', 0x66:'g', 0x8a:'{', 0x8c:'}', 0x5c:'-'}
# 遍历查看{括号中的可能内容}
for j in range(42):
    print(f"{hex(raw[j])}:", end=' ')
    # 0x5c对应'-'
    if raw[j] in flag_dict.keys():
        print(flag_dict[raw[j]])
        continue
    # 遍历0x00-0xff 只取其中的奇数作亦或
    for i in range(1, 256, 2):
        s = chr(raw[j] ^ i)
        #在字母表内 而且运算先后 低1,2,3bit位相同
        if s in alphabet and ord(s) & 0xe == raw[j] & 0xe and s not in flag_dict.values():
            print(s, end=' ')
    print()
# {103: 'f', 157: 'l', 96: 'a', 102: 'g', 138: '{', 140: '}', 92: '-', 86: '7', 73: '8', 80: '1', 101: ['d', '4'], 85: ['d', '4'], 100: ['e', '5'], 72: '9', 81: '0', 87: '6', 84: ['e', '5'], 99: ['b', '2'], 98: ['c', '3'], 82: ['c', '3'], 83: ['b', '2']}

接着调试,在strcmp调用前断点,会看到比较的两个字符,可以看输入发生什么变化。

输入2345(因为这四个不确定),如下图可以看到传入的两个字符地址

image-20240516100435885.png

经查看一个0x67(42字节第一个)一个0x53(83),所以2对应83,即b对应99。以此类推得到flag

baby_tree 2022

https://www.nssctf.cn/problem/2341

查询了资料可知是swift代码转换成的ast,但没找到什么方法可以还原源代码,只能硬分析代码了

代码太长,因此搜了很多关键词,最后搜比较==的时候搜到了下方一大串字符值

image-20240516110534271.png

其中变量名为b(525行那里)

因此还是需要分析出函数的逻辑,首先ast由一个top_level_code_decl(里面调用了check)和func_decl(check)组成,很明显check传入两个字符串参数

image-20240516144034922.png

下面逐个分析:先分析主函数(调用了check),首先有一个大的if判断,具体翻译的代码是len(CommandLine.arguments)>=2,有点类似c主函数

(binary_expr type='Bool' location=re.swift:16:32 range=[re.swift:16:4 - line:16:35] nothrow
          (dot_syntax_call_expr implicit type='(Int, Int) -> Bool' location=re.swift:16:32 range=[re.swift:16:32 - line:16:32] nothrow
            (declref_expr type='(Int.Type) -> (Int, Int) -> Bool' location=re.swift:16:32 range=[re.swift:16:32 - line:16:32] decl=Swift.(file).Int extension.>= function_ref=single)    // 这里声明了比较两个int,且大于等于
            (argument_list implicit
              (argument
                (type_expr implicit type='Int.Type' location=re.swift:16:32 range=[re.swift:16:32 - line:16:32] typerepr='Int'))
            ))
          (argument_list implicit
            (argument
              (member_ref_expr type='Int' location=re.swift:16:26 range=[re.swift:16:4 - line:16:26] decl=Swift.(file).Array extension.count [with (substitution_map generic_signature=<Element> (substitution Element -> String))]
                (load_expr implicit type='[String]' location=re.swift:16:16 range=[re.swift:16:4 - line:16:16]
                  (member_ref_expr type='@lvalue [String]' location=re.swift:16:16 range=[re.swift:16:4 - line:16:16] decl=Swift.(file).CommandLine.arguments
                    (type_expr type='CommandLine.Type' location=re.swift:16:4 range=[re.swift:16:4 - line:16:4] typerepr='CommandLine')))))
            (argument
              (integer_literal_expr type='Int' location=re.swift:16:35 range=[re.swift:16:35 - line:16:35] value=2 builtin_initializer=Swift.(file).Int.init(_builtinIntegerLiteral:) initializer=**NULL**))
          ))

满足条件进入下一个逻辑:data=CommandLine.arguments[1]

(pattern_binding_decl range=[re.swift:17:5 - line:17:39]
    (pattern_named type='String' 'data')    // 定义参数名data
    Original init:
    (subscript_expr type='<null>'
        (member_ref_expr type='@lvalue [String]' location=re.swift:17:28 range=[re.swift:17:16 - line:17:28] decl=Swift.(file).CommandLine.arguments
            (type_expr type='CommandLine.Type' location=re.swift:17:16 range=[re.swift:17:16 - line:17:16] typerepr='CommandLine'))
        (argument_list
            (argument
                (integer_literal_expr type='Int' location=re.swift:17:38 range=[re.swift:17:38 - line:17:38] value=1 builtin_initializer=Swift.(file).Int.init(_builtinIntegerLiteral:) initializer=**NULL**))
          ))    // 取CommandLine.arguments[1]
    Processed init:
    (load_expr implicit type='String' location=re.swift:17:37 range=[re.swift:17:16 - line:17:39]
        (subscript_expr type='@lvalue String' location=re.swift:17:37 range=[re.swift:17:16 - line:17:39] decl=Swift.(file).Array extension.subscript(_:) [with (substitution_map generic_signature=<Element> (substitution Element -> String))]
            (inout_expr implicit type='inout Array<String>' location=re.swift:17:16 range=[re.swift:17:16 - line:17:28]
                  (member_ref_expr type='@lvalue [String]' location=re.swift:17:28 range=[re.swift:17:16 - line:17:28] decl=Swift.(file).CommandLine.arguments
                    (type_expr type='CommandLine.Type' location=re.swift:17:16 range=[re.swift:17:16 - line:17:16] typerepr='CommandLine')))
            (argument_list
                  (argument
                    (integer_literal_expr type='Int' location=re.swift:17:38 range=[re.swift:17:38 - line:17:38] value=1 builtin_initializer=Swift.(file).Int.init(_builtinIntegerLiteral:) initializer=**NULL**))
            ))))
(var_decl range=[re.swift:17:9 - line:17:9] "data" type='String' interface type='String' access=fileprivate let readImpl=stored immutable)

然后又定义了String key='345y'

(pattern_binding_decl range=[re.swift:18:5 - line:18:15]
    (pattern_named type='String' 'key')
    Original init:
    (string_literal_expr type='String' location=re.swift:18:15 range=[re.swift:18:15 - line:18:15] encoding=utf8 value="345y" builtin_initializer=Swift.(file).String extension.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**)
    Processed init:
    (string_literal_expr type='String' location=re.swift:18:15 range=[re.swift:18:15 - line:18:15] encoding=utf8 value="345y" builtin_initializer=Swift.(file).String extension.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**))
    (var_decl range=[re.swift:18:9 - line:18:9] "key" type='String' interface type='String' access=fileprivate let readImpl=stored immutable)

接着定义了Bool result=check(data,key)

(pattern_binding_decl range=[re.swift:19:5 - line:19:33]
    (pattern_named type='Bool' 'result')
    Original init:
    (call_expr type='Bool' location=re.swift:19:18 range=[re.swift:19:18 - line:19:33] nothrow
        (declref_expr type='(String, String) -> Bool' location=re.swift:19:18 range=[re.swift:19:18 - line:19:18] decl=re.(file).check@re.swift:1:6 function_ref=single)
              (argument_list
                (argument
                      (declref_expr type='String' location=re.swift:19:24 range=[re.swift:19:24 - line:19:24] decl=re.(file).top-level code.data@re.swift:17:9 function_ref=unapplied))
                (argument
                      (declref_expr type='String' location=re.swift:19:30 range=[re.swift:19:30 - line:19:30] decl=re.(file).top-level code.key@re.swift:18:9 function_ref=unapplied))
                  ))
    Processed init:
    (call_expr type='Bool' location=re.swift:19:18 range=[re.swift:19:18 - line:19:33] nothrow
          (declref_expr type='(String, String) -> Bool' location=re.swift:19:18 range=[re.swift:19:18 - line:19:18] decl=re.(file).check@re.swift:1:6 function_ref=single)
          (argument_list
            (argument
                  (declref_expr type='String' location=re.swift:19:24 range=[re.swift:19:24 - line:19:24] decl=re.(file).top-level code.data@re.swift:17:9 function_ref=unapplied))
            (argument
                  (declref_expr type='String' location=re.swift:19:30 range=[re.swift:19:30 - line:19:30] decl=re.(file).top-level code.key@re.swift:18:9 function_ref=unapplied))
              )))
(var_decl range=[re.swift:19:9 - line:19:9] "result" type='Bool' interface type='Bool' access=fileprivate let readImpl=stored immutable)

最后一段貌似是打印result

(call_expr type='()' location=re.swift:20:5 range=[re.swift:20:5 - line:20:17] nothrow
    (declref_expr type='(Any..., String, String) -> ()' location=re.swift:20:5 range=[re.swift:20:5 - line:20:5] decl=Swift.        (file).print(_:separator:terminator:) function_ref=single)
        (argument_list labels=_:separator:terminator:
        (argument
            (vararg_expansion_expr implicit type='Any...' location=re.swift:20:11 range=[re.swift:20:11 - line:20:11]
                (array_expr implicit type='Any...' location=re.swift:20:11 range=[re.swift:20:11 - line:20:11] initializer=**NULL**
                    (erasure_expr implicit type='Any' location=re.swift:20:11 range=[re.swift:20:11 - line:20:11]
                        (declref_expr type='Bool' location=re.swift:20:11 range=[re.swift:20:11 - line:20:11] decl=re.(file).top-level code.result@re.swift:19:9 function_ref=unapplied)))))
        (argument label=separator
            (default_argument_expr implicit type='String' location=re.swift:20:10 range=[re.swift:20:10 - line:20:10] default_args_owner=Swift.(file).print(_:separator:terminator:) param=1))
        (argument label=terminator
            (default_argument_expr implicit type='String' location=re.swift:20:10 range=[re.swift:20:10 - line:20:10] default_args_owner=Swift.(file).print(_:separator:terminator:) param=2))
))

分析check,传入参数为字符串encoded和keyValue(发现个妙处,可以根据行号提示来划分代码逻辑),第二行定义了UInt8 b[UTF8View(encoded)]

(pattern_binding_decl range=[re.swift:2:5 - line:2:33]
    (pattern_named type='[UInt8]' 'b')    // 变量名b
    Original init:
    (call_expr type='[UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:33] nothrow
          (constructor_ref_call_expr type='(String.UTF8View) -> [UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:19] nothrow
            (declref_expr implicit type='(Array<UInt8>.Type) -> (String.UTF8View) -> Array<UInt8>' location=re.swift:2:19 range=[re.swift:2:19 - line:2:19] decl=Swift.(file).Array extension.init(_:) [with (substitution_map generic_signature=<Element, S where Element == S.Element, S : Sequence> (substitution Element -> UInt8) (substitution S -> String.UTF8View))] function_ref=single)    // Array
            (argument_list implicit
                  (argument
                    (type_expr type='[UInt8].Type' location=re.swift:2:13 range=[re.swift:2:13 - line:2:19] typerepr='[UInt8]'))
            ))    // UInt8类型
          (argument_list
                (argument
                      (member_ref_expr type='String.UTF8View' location=re.swift:2:29 range=[re.swift:2:21 - line:2:29] decl=Swift.(file).String extension.utf8
                        (declref_expr type='String' location=re.swift:2:21 range=[re.swift:2:21 - line:2:21] decl=re.(file).check(_:_:).encoded@re.swift:1:14 function_ref=unapplied)))
    ))
    Processed init:
    (call_expr type='[UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:33] nothrow
        (constructor_ref_call_expr type='(String.UTF8View) -> [UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:19] nothrow
            (declref_expr implicit type='(Array<UInt8>.Type) -> (String.UTF8View) -> Array<UInt8>' location=re.swift:2:19 range=[re.swift:2:19 - line:2:19] decl=Swift.(file).Array extension.init(_:) [with (substitution_map generic_signature=<Element, S where Element == S.Element, S : Sequence> (substitution Element -> UInt8) (substitution S -> String.UTF8View))] function_ref=single)
            (argument_list implicit
                  (argument
                    (type_expr type='[UInt8].Type' location=re.swift:2:13 range=[re.swift:2:13 - line:2:19] typerepr='[UInt8]'))
            ))
              (argument_list
                (argument
                      (member_ref_expr type='String.UTF8View' location=re.swift:2:29 range=[re.swift:2:21 - line:2:29] decl=Swift.(file).String extension.utf8
                        (declref_expr type='String' location=re.swift:2:21 range=[re.swift:2:21 - line:2:21] decl=re.(file).check(_:_:).encoded@re.swift:1:14 function_ref=unapplied)))
)))

(var_decl range=[re.swift:2:9 - line:2:9] "b" type='[UInt8]' interface type='[UInt8]' access=private readImpl=stored writeImpl=stored readWriteImpl=stored)

接着第三行又定义了同样的UInt8 k=[UTF8View(keyValue)]

(pattern_binding_decl range=[re.swift:3:5 - line:3:34]
    (pattern_named type='[UInt8]' 'k')
    Original init:
    (call_expr type='[UInt8]' location=re.swift:3:19 range=[re.swift:3:13 - line:3:34] nothrow
          (constructor_ref_call_expr type='(String.UTF8View) -> [UInt8]' location=re.swift:3:19 range=[re.swift:3:13 - line:3:19] nothrow
            (declref_expr implicit type='(Array<UInt8>.Type) -> (String.UTF8View) -> Array<UInt8>' location=re.swift:3:19 range=[re.swift:3:19 - line:3:19] decl=Swift.(file).Array extension.init(_:) [with (substitution_map generic_signature=<Element, S where Element == S.Element, S : Sequence> (substitution Element -> UInt8) (substitution S -> String.UTF8View))] function_ref=single)
            (argument_list implicit
                  (argument
                    (type_expr type='[UInt8].Type' location=re.swift:3:13 range=[re.swift:3:13 - line:3:19] typerepr='[UInt8]'))
            ))
              (argument_list
                (argument
                      (member_ref_expr type='String.UTF8View' location=re.swift:3:30 range=[re.swift:3:21 - line:3:30] decl=Swift.(file).String extension.utf8
                        (declref_expr type='String' location=re.swift:3:21 range=[re.swift:3:21 - line:3:21] decl=re.(file).check(_:_:).keyValue@re.swift:1:33 function_ref=unapplied)))
    ))
    Processed init:
    (call_expr type='[UInt8]' location=re.swift:3:19 range=[re.swift:3:13 - line:3:34] nothrow
        (constructor_ref_call_expr type='(String.UTF8View) -> [UInt8]' location=re.swift:3:19 range=[re.swift:3:13 - line:3:19] nothrow
            (declref_expr implicit type='(Array<UInt8>.Type) -> (String.UTF8View) -> Array<UInt8>' location=re.swift:3:19 range=[re.swift:3:19 - line:3:19] decl=Swift.(file).Array extension.init(_:) [with (substitution_map generic_signature=<Element, S where Element == S.Element, S : Sequence> (substitution Element -> UInt8) (substitution S -> String.UTF8View))] function_ref=single)
            (argument_list implicit
                  (argument
                    (type_expr type='[UInt8].Type' location=re.swift:3:13 range=[re.swift:3:13 - line:3:19] typerepr='[UInt8]'))
            ))
              (argument_list
                (argument
                  (member_ref_expr type='String.UTF8View' location=re.swift:3:30 range=[re.swift:3:21 - line:3:30] decl=Swift.(file).String extension.utf8
                    (declref_expr type='String' location=re.swift:3:21 range=[re.swift:3:21 - line:3:21] decl=re.(file).check(_:_:).keyValue@re.swift:1:33 function_ref=unapplied)))
)))
(var_decl range=[re.swift:3:9 - line:3:9] "k" type='[UInt8]' interface type='[UInt8]' access=private readImpl=stored writeImpl=stored readWriteImpl=stored)

第四行分别定义了UInt8类型的r0、r1、r2、r3

(pattern_binding_decl range=[re.swift:4:5 - line:4:25]
    (pattern_typed type='UInt8'
          (pattern_named type='UInt8' 'r0')
          (type_ident
            (component id='UInt8' bind=Swift.(file).UInt8)))
    (pattern_typed type='UInt8'
          (pattern_named type='UInt8' 'r1')
          (type_ident
            (component id='UInt8' bind=Swift.(file).UInt8)))
    (pattern_typed type='UInt8'
          (pattern_named type='UInt8' 'r2')
          (type_ident
            (component id='UInt8' bind=Swift.(file).UInt8)))
    (pattern_typed type='UInt8'
          (pattern_named type='UInt8' 'r3')
          (type_ident
            (component id='UInt8' bind=Swift.(file).UInt8))))
(var_decl range=[re.swift:4:9 - line:4:9] "r0" type='UInt8' interface type='UInt8' access=private readImpl=stored writeImpl=stored readWriteImpl=stored)
(var_decl range=[re.swift:4:13 - line:4:13] "r1" type='UInt8' interface type='UInt8' access=private readImpl=stored writeImpl=stored readWriteImpl=stored)
(var_decl range=[re.swift:4:17 - line:4:17] "r2" type='UInt8' interface type='UInt8' access=private readImpl=stored writeImpl=stored readWriteImpl=stored)
(var_decl range=[re.swift:4:21 - line:4:21] "r3" type='UInt8' interface type='UInt8' access=private readImpl=stored writeImpl=stored readWriteImpl=stored)

第五行是一个for循环,从0到b.count-4

(for_each_stmt range=[re.swift:5:5 - line:12:5]
    (pattern_named type='Int' 'i')
    (pattern_named type='Int' 'i')
    (binary_expr type='ClosedRange<Int>' location=re.swift:5:15 range=[re.swift:5:14 - line:5:26] nothrow
          (dot_syntax_call_expr implicit type='(Int, Int) -> ClosedRange<Int>' location=re.swift:5:15 range=[re.swift:5:15 - line:5:15] nothrow
            (declref_expr type='(Int.Type) -> (Int, Int) -> ClosedRange<Int>' location=re.swift:5:15 range=[re.swift:5:15 - line:5:15] decl=Swift.(file).Comparable extension.... [with (substitution_map generic_signature=<Self where Self : Comparable> (substitution Self -> Int))] function_ref=double)
            (argument_list implicit
                  (argument
                    (type_expr implicit type='Int.Type' location=re.swift:5:15 range=[re.swift:5:15 - line:5:15] typerepr='Int'))
            ))
              (argument_list implicit
                (argument
                      (integer_literal_expr type='Int' location=re.swift:5:14 range=[re.swift:5:14 - line:5:14] value=0 builtin_initializer=Swift.(file).Int.init(_builtinIntegerLiteral:) initializer=**NULL**)) // 0开始
                (argument
                      (binary_expr type='Int' location=re.swift:5:25 range=[re.swift:5:18 - line:5:26] nothrow
                        (dot_syntax_call_expr implicit type='(Int, Int) -> Int' location=re.swift:5:25 range=[re.swift:5:25 - line:5:25] nothrow
                      (declref_expr type='(Int.Type) -> (Int, Int) -> Int' location=re.swift:5:25 range=[re.swift:5:25 - line:5:25] decl=Swift.(file).Int extension.- function_ref=double)    // 类比上面的大于等于,可知这里做了减法,即b.count-4
                      (argument_list implicit
                        (argument
                              (type_expr implicit type='Int.Type' location=re.swift:5:25 range=[re.swift:5:25 - line:5:25] typerepr='Int'))
                      ))
                    (argument_list implicit
                          (argument
                            (member_ref_expr type='Int' location=re.swift:5:20 range=[re.swift:5:18 - line:5:20] decl=Swift.(file).Array extension.count [with (substitution_map generic_signature=<Element> (substitution Element -> UInt8))]
                                  (load_expr implicit type='[UInt8]' location=re.swift:5:18 range=[re.swift:5:18 - line:5:18]
                                    (declref_expr type='@lvalue [UInt8]' location=re.swift:5:18 range=[re.swift:5:18 - line:5:18] decl=re.(file).check(_:_:).b@re.swift:2:9 function_ref=unapplied))))    // 这里算了b.count
                  (argument
                                (integer_literal_expr type='Int' location=re.swift:5:26 range=[re.swift:5:26 - line:5:26] value=4 builtin_initializer=Swift.(file).Int.init(_builtinIntegerLiteral:) initializer=**NULL**))    //这里4
                    )))
))
(var_decl implicit range=[re.swift:5:11 - line:5:11] "$i$generator" type='ClosedRange<Int>.Iterator' interface type='ClosedRange<Int>.Iterator' access=private readImpl=stored writeImpl=stored readWriteImpl=stored)

下一个块很长,但总体看来其实就是第6-11行代码,均为赋值表达式

image-20240516192424988.png

第6行代码,两个tuple,第一个r0、r1、r2、r3,

image-20240516192725554.png

第二个发现根据下标取值,得到b[0](这里写的i,i正好0)、b[1]、b[2]、b[3]

image-20240516192904793.png

第7行代码:首先取b[i+0]

image-20240516193648621.png

接着最外层做了r2和另一个数异或

image-20240516194227454.png

再往里是0xff和另一个数与计算

image-20240516194424711.png

再往里是k[0]和另一个数求和

image-20240516194557282.png

最后一层r0右移4位

image-20240516194709062.png

综上第7行代码表达式为 b[i+0]=r2^(0xff&(k[0]+(r0>>4))),真的挺复杂

以此类推得到所有代码

bool check(string encoded, string keyValue) {
    UInt8 b=[UTF8View(encoded)];
    UInt8 k=[UTF8View(keyValue)];
    UInt8 r0, r1, r2, r3;
    for (int i = 0; i < len(b)-4; i++) {
        r0, r1, r2, r3 = b[i+0], b[i+1], b[i+2], b[i+3];
        b[i+0]=r2^(0xff&(k[0]+(r0>>4)));
        b[i+1]=r3^(0xff&(k[1]+(r1>>2)));
        b[i+2]=r0^k[2];
        b[i+3]=r1^k[3];
        k[0], k[1], k[2], k[3] = k[1], k[2], k[3], k[0];
    }
    return b == [88, 35, 88, 225, 7, 201, 57, 94, 77, 56, 75, 168, 72, 218, 64, 91, 16, 101, 32, 207, 73, 130, 74, 128, 76, 201, 16, 248, 41, 205, 103, 84, 91, 99, 79, 202, 22, 131, 63, 255, 20, 16]
}

python正则提取比较的值:

import re
with open('./baby_tree.ast') as f:
    content = f.read()
results = re.findall(r"(?<=line:13:).*(?<=value=)([\d]{1,3})", content)
results = list(map(lambda x: int(x), results))
# [88, 35, 88, 225, 7, 201, 57, 94, 77, 56, 75, 168, 72, 218, 64, 91, 16, 101, 32, 207, 73, 130, 74, 128, 76, 201, 16, 248, 41, 205, 103, 84, 91, 99, 79, 202, 22, 131, 63, 255, 20, 16]

编写脚本逆向

import re
with open('./baby_tree.ast') as f:
    content = f.read()
results = re.findall(r"(?<=line:13:).*(?<=value=)([\d]{1,3})", content)
results = list(map(lambda x: int(x), results))
print(results, len(results))


k = [ord(i) for i in "5y34"]    # 38次左移结果
print(k)

b = results
for i in range(len(b)-4, -1, -1):
    r1 = b[i+3] ^ k[3]
    r0 = b[i+2] ^ k[2]
    r3 = b[i+1] ^ (0xff & (k[1] + (r1 >> 2)))
    r2 = b[i] ^ (0xff & (k[0] + (r0 >> 4)))
    b[i], b[i+1], b[i+2], b[i+3] = r0, r1, r2, r3
    k[0], k[1], k[2], k[3] = k[3], k[0], k[1], k[2]
flag = ''
for i in b:
    flag += chr(i)
print(flag)

happymath 2022

https://www.nssctf.cn/problem/2405

int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v4; // rbx
  __int64 v5; // rdx
  char v6; // cl
  __int64 v7; // r10
  int v8; // edi
  unsigned int v9; // eax
  __int64 i; // r9
  char v11; // cl
  unsigned int v12; // edx
  unsigned int v13; // r9d
  unsigned int v14; // r8d
  unsigned int v15; // r8d
  unsigned int v16; // r8d
  int v17; // r8d
  int v18; // r8d
  int v19; // r9d
  int v20; // r9d

  scanf("%32s", input);
  v3 = -1i64;
  do
    ++v3;
  while ( input[v3] );
  if ( (_DWORD)v3 == 32 )                       // 长度32
  {
    v4 = 0i64;
    v5 = 0i64;
    while ( 1 )
    {
      v6 = input[v5];
      if ( (unsigned __int8)(v6 - 97) > 5u && (unsigned __int8)(v6 - 48) > 9u )// 0-9a-f
        break;
      if ( ++v5 >= 32 )
      {
        v7 = 0i64;
        v8 = 4;
        while ( 1 )
        {
          v9 = -1;
          for ( i = v4; i < v8; v9 = dword_140024630[v7 + (~(v11 & v9) & (unsigned __int8)(v11 | v9))] ^ (v9 >> 8) )
            v11 = input[i++];
          v12 = (v9 | ~*(_DWORD *)((char *)&unk_140024610 + v4)) & ~(v9 & ~*(_DWORD *)((char *)&unk_140024610 + v4));
          v13 = ((((((((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 4) | ((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 8) | ((((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 4) | ((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 16) | ((((((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 4) | ((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 8) | ((((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 4) | ((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1);
          v14 = ((v12 & (v13 | (v13 >> 1)) & ~(v13 & (v13 >> 1))) >> 1) | v12 & (v13 | (v13 >> 1)) & ~(v13 & (v13 >> 1));
          v15 = (((v14 >> 2) | v14) >> 4) | (v14 >> 2) | v14;
          v16 = (((v15 >> 8) | v15) >> 16) | (v15 >> 8) | v15;
          v17 = (4 * ((2 * v16) | v16)) | (2 * v16) | v16;
          v18 = (((16 * v17) | v17) << 8) | (16 * v17) | v17;
          v19 = (4 * ((2 * (v13 & 1)) | v13 & 1)) | (2 * (v13 & 1)) | v13 & 1;
          v20 = (((16 * v19) | v19) << 8) | (16 * v19) | v19;
          if ( ((v20 | (v20 << 16)) & (~((v18 << 16) | v18) | v18 & 1)) != 0 )
            break;                              // 不能break
          v8 += 4;
          v7 += 256i64;
          v4 += 4i64;
          if ( v8 >= 36 )
          {
            printf("Your flag is flag{%s}\n", input);
            return 0;
          }
        }
        break;
      }
    }
  }
  printf("Wrong!");
  return 0;
}

直接爆破,注意unk_140024610也需要是4字节(卡了半天)

#include<iostream>
using namespace std;
unsigned int dword_140024630[2048] = {略};
unsigned int unk_140024610[8] = {
    0x95F553DC, 0x1D3B4E23, 0x0B913414, 0x6B9E4582, 0x35E7621B, 0xFC86887F, 0x26DEA0FB, 0x87044CAA
};

int calc(int input[], int v4, int v7, int v8) {
    int v11;
    unsigned int v9 = -1;
    for (int i = v4; i < v8; v9 = dword_140024630[v7 + (~(v11 & v9) & (v11 | v9) & 0xff)] ^ (v9 >> 8) )
        v11 = input[i++];
    unsigned int v12 = (v9 | ~unk_140024610[v4/4]) & ~(v9 & ~unk_140024610[v4/4]);
    unsigned int v13 = ((((((((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 4) | ((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 8) | ((((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 4) | ((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 16) | ((((((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 4) | ((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 8) | ((((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1)) >> 4) | ((v12 | (v12 >> 1)) >> 2) | v12 | (v12 >> 1);
    unsigned int v14 = ((v12 & (v13 | (v13 >> 1)) & ~(v13 & (v13 >> 1))) >> 1) | v12 & (v13 | (v13 >> 1)) & ~(v13 & (v13 >> 1));
    unsigned int v15 = (((v14 >> 2) | v14) >> 4) | (v14 >> 2) | v14;
    unsigned int v16 = (((v15 >> 8) | v15) >> 16) | (v15 >> 8) | v15;
    int v17 = (4 * ((2 * v16) | v16)) | (2 * v16) | v16;
    int v18 = (((16 * v17) | v17) << 8) | (16 * v17) | v17;
    int v19 = (4 * ((2 * (v13 & 1)) | v13 & 1)) | (2 * (v13 & 1)) | v13 & 1;
    int v20 = (((16 * v19) | v19) << 8) | (16 * v19) | v19;
    if ( ((v20 | (v20 << 16)) & (~((v18 << 16) | v18) | v18 & 1)) != 0 )
        return 0; 
    else
        return 1;
}

void loop(int input[], int v4, int v7, int v8) {
    for (int k1 = 48; k1 < 103; k1++) {
        for (int k2 = 48; k2 < 103; k2++) {
            for (int k3 = 48; k3 < 103; k3++) {
                for (int k4 = 48; k4 < 103; k4++) {
                    input[v4] = k1, input[v4+1] = k2, input[v4+2] =k3, input[v4+3] = k4;
                    int result = calc(input, v4, v7, v8);
                    if (result) {
                        printf("%c%c%c%c", input[v4], input[v4+1], input[v4+2], input[v4+3]);
                        return;
                    }
                    if (k4 == 57) k4 = 96;
                }
                if (k3 == 57) k3 = 96; 
            }
            if (k2 == 57) k2 = 96;
        }
        if (k1 == 57) k1 = 96;
    }
}

int main() {
    int v4 = 0;
    int v5 = 0;
    int input[32];
    while ( 1 )
    {
        if ( ++v5 >= 32 )
        {
            int v7 = 0;
            int v8 = 4;
            while (1) {
                loop(input, v4, v7, v8);    // 每次4个字符
                v8 += 4;
                v7 += 256;
                v4 += 4;
                if ( v8 >= 36 )
                    return 0;
            }
            printf("\n");
        }   
    }
}

hana

易语言代码逆向,比较难懂,最好动态调试和静态分析相结合分析主函数

首先使用ida的易语言反编译器插件,将一些函数转为中文函数名,更方便理解,由于有输出,可以先定位标准输出,然后向上找

这是网上的代码:

image-20240517172601741.png

这是调试分析过程中的代码,基本对应

image-20240517180223840.png

使用如下代码加密我们的输入

〈字节集〉 加密数据 (字节集 字节集数据,文本型 密码文本,[整数型 加密算法]) - 数据操作支持库一->数据加解密

因此上述代码是使用RC4算法加密,key为"Wrong!",加密的是输入的字符串转成的字节集(到字节集)

image-20240517180345119.png

字节集结构为

typdef struct 字节集
{
   int unkown;
   int length;
   char* bytes;
};

再往下分析可以看到字节集比较,传入的参数分别为:比较的两个字节集和文本长度

image-20240517181110104.png

其中0x667BD8是先前RC4加密后的字节集地址+8(正好是结构体的bytes),因此查看0x4804CB是比较的对象,直接转为字符串,转为字节格式

使用Crypto包下的ARC4解密即可

from Crypto.Cipher import ARC4

s = b"\x56\xEC\xA0\xDC\x57\x07\xF4\xA3\xE9\x77\xBF\x93\xBC\x86\x52\xA5\x14\x6A\xA5\xBD\xB5\xD2\x7F\x0B\x9B\x67\x1D\x08\xEF\xC9\x32\x5D\x43\xED\x1E\x01\x4B\x7B"
key = bytes("Wrong!", encoding="utf-8")
enc = ARC4.new(key)
flag = enc.decrypt(s)
print(flag)     # b'flag{08360c3f9f994e199427d9c7ed14ef23}'

easyCpp

c++ transfrom:https://blog.csdn.net/zhang___bo/article/details/119389457

transform(first,last,result,op);//first是容器的首迭代器,last为容器的末迭代器,result为存放结果的容器,op为要进行操作的一元函数对象或sturct、class。

本题中可以直接双击查看具体内容

int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rdx
  __int64 v4; // rdx
  __int64 v5; // rdx
  __int64 v6; // rdx
  __int64 v7; // r12
  __int64 v8; // rbx
  __int64 v9; // rax
  __int64 v10; // rdx
  int v11; // eax
  int v12; // r8d
  int v13; // r9d
  int v14; // r12d
  int v15; // ebx
  int v16; // eax
  int v17; // ecx
  int v18; // r8d
  int v19; // r9d
  unsigned int *v20; // rax
  int i; // [rsp+1Ch] [rbp-174h]
  int j; // [rsp+20h] [rbp-170h]
  char v24[32]; // [rsp+30h] [rbp-160h] BYREF
  char v25[32]; // [rsp+50h] [rbp-140h] BYREF
  char v26[32]; // [rsp+70h] [rbp-120h] BYREF
  char v27[32]; // [rsp+90h] [rbp-100h] BYREF
  char v28[32]; // [rsp+B0h] [rbp-E0h] BYREF
  __int64 v29[4]; // [rsp+D0h] [rbp-C0h] BYREF
  __int64 v30[4]; // [rsp+F0h] [rbp-A0h] BYREF
  _DWORD v31[18]; // [rsp+110h] [rbp-80h] BYREF
  unsigned __int64 v32; // [rsp+158h] [rbp-38h]

  v32 = __readfsqword(0x28u);
  std::vector<int>::vector(v24, argv, envp);
  std::vector<int>::vector(v25, argv, v3);
  std::vector<int>::vector(v26, argv, v4);
  std::vector<int>::vector(v27, argv, v5);
  std::vector<int>::vector(v28, argv, v6);
  for ( i = 0; i <= 15; ++i )
  {
    scanf("%d", &v31[i]);                       // 输入
    std::vector<int>::push_back(v25, &v31[i]);
  }
  for ( j = 0; j <= 15; ++j )
  {
    LODWORD(v30[0]) = fib(j);// 生成斐波那契数列16个
    std::vector<int>::push_back(v24, v30);
  }
  std::vector<int>::push_back(v26, v31);
  v7 = std::back_inserter<std::vector<int>>(v26);// 存放结果
  v8 = std::vector<int>::end(v25);
  v30[0] = std::vector<int>::begin(v25);
  v9 = __gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator+(v30, 1LL);
  std::transform<__gnu_cxx::__normal_iterator<int *,std::vector<int>>,std::back_insert_iterator<std::vector<int>>,main::{lambda(int)#1}>(
    v9,                                         // v9是存放输入数字容器的开始+1,即从第二个开始
    v8,                                         // v8是存放输入数字容器的结尾
    v7,
    v31);
  std::vector<int>::vector(v29, v8, v10);
  LODWORD(v8) = std::vector<int>::end(v26);     // 前面处理完新的容器
  v11 = std::vector<int>::begin(v26);
  std::accumulate<__gnu_cxx::__normal_iterator<int *,std::vector<int>>,std::vector<int>,main::{lambda(std::vector<int>,int)#2}>(
    (unsigned int)v30,                          // 存放输入的容器
    v11,
    v8,
    (unsigned int)v29,
    v12,
    v13);                                       // 深入分析发现是对上一个容器做了逆序操作
  std::vector<int>::operator=(v27, v30);
  std::vector<int>::~vector(v30);
  std::vector<int>::~vector(v29);
  if ( (unsigned __int8)std::operator!=<int,std::allocator<int>>(v27, v24) )// 处理后的输入和斐波那契数列比较
  {
    puts("You failed!");
    exit(0);
  }
  v14 = std::back_inserter<std::vector<int>>(v28);
  v15 = std::vector<int>::end(v25);
  v16 = std::vector<int>::begin(v25);
  std::copy_if<__gnu_cxx::__normal_iterator<int *,std::vector<int>>,std::back_insert_iterator<std::vector<int>>,main::{lambda(int)#3}>(
    v16,
    v15,
    v14,
    v17,
    v18,
    v19);    // 这里是输入为奇数的复制到v28
  puts("You win!");
  printf("Your flag is:flag{");
  v29[0] = std::vector<int>::begin(v28);        // 之后打印v28
  v30[0] = std::vector<int>::end(v28);
  while ( (unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int>>(v29, v30) )
  {
    v20 = (unsigned int *)__gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator*(v29);
    std::ostream::operator<<(&std::cout, *v20);
    __gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator++(v29);
  }
  putchar(125);
  std::vector<int>::~vector(v28);
  std::vector<int>::~vector(v27);
  std::vector<int>::~vector(v26);
  std::vector<int>::~vector(v25);
  std::vector<int>::~vector(v24);
  return 0;
}

解密脚本

fib = [1, 1]
while len(fib) < 16:
    fib.append(fib[len(fib)-1]+fib[len(fib)-2])
print(fib, len(fib))

fib = fib[::-1]
fib = [i-fib[0] if i != fib[0] else i for i in fib]
print(fib)
flag = 'flag{'
flag += str(fib[0])
for i in range(1, len(fib)):
    if fib[i] % 2:
        flag += str(fib[i])
flag += '}'
print(flag)

mfc逆向-200

题目涉及MFC(微软基础类)控件的逆向,由图可知需要获取控件里的信息

image-20240521155355401.png

由于Flag在控件里,需要我们获取句柄,发送消息

首先使用xspy(可以获取运行程序的窗口信息)找到控件值,如下图一串加密字符串

image-20240521160134148.png

往下找可以看到内部函数onmsg

image-20240521160326394.png

image-20240521160435987.png

image-20240521160645336.png

因此重点针对自定义消息,写个c脚本给窗口发送消息

#include<stdio.h>
#include<string.h>
#include<windows.h>

int main()
{
    HWND h = FindWindowA("944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b",NULL);
    //HWND h = FindWindowA(NULL, "Flag就在控件里");
    //HWND h = FindWindowA("944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b", "Flag就在控件里");
    //这里用到两个关键函数,一个是获取窗口句柄函数,第二个就是根据句柄发送消息函数。获取句柄的FindWindowA中第一个可以传入类名,第二个可以传入标题,因为我们两个都有,所以任意一个都可以锁定程序窗口。
    if (h)
    {
        SendMessage(h, 0x464, NULL, NULL);
        //发送函数中第二个是区别其他消息的常量值,这里题目用了自定义常量值,所以我们要对应一致。
    }
    getchar();
    return 0;
}

运行发现内容改变

image-20240521160831453.png

为DES加密,DES解密即可,试了好多加解密工具,发现只有飘云阁一个工具可以解密超过8字节的DES

image-20240521171153172.png

thIs_Is_real_kEy_hahaaa

最后修改:2024 年 05 月 21 日
如果觉得我的文章对你有用,请随意赞赏