[DSO-NUS CTF 2021] SOAR Writeup 题解

SOAR


This blog entry is available in multiple languages
这篇文章在多个语言下可用
中文/Chinese
英文/English

题解

从题目描述中,我们可以得到一个文件,SOAR-challenge.pdf。用PDF查看器打开它,没发现什么。用一个普通的文件查看器/16进制查看器打开它,一眼就能看到一个"PK", 里面藏了一个zip压缩文件。

把zip提取出来,我们可以看到他有密码保护。这个密码太简单,猜猜就能猜出来,密码是"dso"。或者也可以暴力破解,几秒钟就出来。

解压后有一个文件"soar", file告诉我们他是一个 ELF 文件。

用IDA反编译它

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  char v4; // al
  char v5; // cl
  __int64 v6; // rax
  int v7; // kr00_4
  __int64 v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rax
  __int64 v12; // rax
  unsigned __int64 v13; // rax
  char v14; // si
  unsigned __int64 v15; // rax
  int v17; // [rsp+Ch] [rbp-6C4h]
  int v18; // [rsp+Ch] [rbp-6C4h]
  int v19; // [rsp+10h] [rbp-6C0h]
  char v20; // [rsp+10h] [rbp-6C0h]
  int v21; // [rsp+14h] [rbp-6BCh]
  time_t timer; // [rsp+18h] [rbp-6B8h] BYREF
  __int64 i; // [rsp+20h] [rbp-6B0h]
  __int64 j; // [rsp+28h] [rbp-6A8h]
  __int64 v25; // [rsp+30h] [rbp-6A0h]
  __int64 k; // [rsp+38h] [rbp-698h]
  __int64 l; // [rsp+40h] [rbp-690h]
  char *filename; // [rsp+48h] [rbp-688h]
  _BYTE *v29; // [rsp+50h] [rbp-680h]
  struct tm *v30; // [rsp+58h] [rbp-678h]
  FILE *stream; // [rsp+60h] [rbp-670h]
  void *ptr; // [rsp+68h] [rbp-668h]
  int v33[47]; // [rsp+70h] [rbp-660h]
  int v34; // [rsp+12Ch] [rbp-5A4h]
  int v35[64]; // [rsp+130h] [rbp-5A0h]
  int v36[68]; // [rsp+230h] [rbp-4A0h] BYREF
  __int64 v37[48]; // [rsp+340h] [rbp-390h] BYREF
  __int64 v38[66]; // [rsp+4C0h] [rbp-210h] BYREF

  v38[65] = __readfsqword(0x28u);
  filename = (char *)malloc(0xD50C51uLL);
  j = 0LL;
  v25 = 0LL;
  v29 = malloc(0xD50C51uLL);
  for ( i = 2LL; i <= 13962321; ++i )
    v29[i] = 1;
  for ( i = 2LL; i <= 13962321; ++i )
  {
    if ( v29[i] )
    {
      for ( j = i * i; j <= 13962320; j += i )
        v29[j] = 0;
    }
  }
  time(&timer);
  v30 = localtime(&timer);
  v19 = v30->tm_min;
  v33[0] = 54;
  v33[1] = 38;
  v33[2] = 24;
  v33[3] = 11;
  v33[4] = 36;
  v33[5] = 17;
  v33[6] = 18;
  v33[7] = 16;
  v33[8] = 31;
  v33[9] = 40;
  v33[10] = 32;
  v33[11] = 51;
  v33[12] = 35;
  v33[13] = 30;
  v33[14] = 22;
  v33[15] = 47;
  v33[16] = 55;
  v33[17] = 13;
  v33[18] = 37;
  v33[19] = 44;
  v33[20] = 33;
  v33[21] = 20;
  v33[22] = 21;
  v33[23] = 12;
  v33[24] = 26;
  v33[25] = 48;
  v33[26] = 28;
  v33[27] = 29;
  v33[28] = 14;
  v33[29] = 25;
  v33[30] = 46;
  v33[31] = 23;
  v33[32] = 49;
  v33[33] = 52;
  v33[34] = 41;
  v33[35] = 57;
  v33[36] = 19;
  v33[37] = 50;
  v33[38] = 42;
  v33[39] = 45;
  v33[40] = 53;
  v33[41] = 56;
  v33[42] = 27;
  v33[43] = 43;
  v33[44] = 39;
  v33[45] = 34;
  v33[46] = 15;
  v34 = 5;
  qmemcpy(v37, &unk_2040, sizeof(v37));
  filename[47] = 0;
  for ( i = 0LL; i <= 46; ++i )
  {
    v37[i] -= 106496LL;
    v37[i] -= 3153LL;
    v37[i] -= v19 ^ (LODWORD(v37[47]) + v34);
    filename[v33[i] - (LODWORD(v37[47]) + v34)] = v37[i];
  }
  i = 0LL;
  stream = fopen(filename, "r");
  if ( stream )
  {
    fseek(stream, 0LL, 2);
    v3 = ftell(stream);
    i = v3 == (v19 ^ (LODWORD(v37[47]) + v34)) + 101606;
    fclose(stream);
  }
  v21 = v30->tm_hour;
  if ( i )
  {
    for ( k = 0LL; k <= 13962320; ++k )
    {
      if ( v29[k] )
      {
        ptr = malloc(0x2AuLL);
        v17 = k;
        j = 0LL;
        do
        {
          if ( v17 % 16 <= 9 )
            v4 = 48;
          else
            v4 = 87;
          v5 = v4 + v17 % 16;
          v6 = j++;
          *((_BYTE *)ptr + v6) = v5;
          v7 = v17;
          v17 /= 16;
        }
        while ( v7 / 16 );
        v8 = j;
        LODWORD(j) = j + 1;
        *((_BYTE *)ptr + v8) = 0;
        v18 = j;
        l = k;
        k = 0LL;
        for ( j = (int)j - 2; k < j; --j )
        {
          v20 = *((_BYTE *)ptr + k);
          *((_BYTE *)ptr + k) = *((_BYTE *)ptr + j);
          *((_BYTE *)ptr + j) = v20;
          ++k;
        }
        k = l;
        for ( l = 0LL; l < v18 - 1; ++l )
        {
          v9 = v25++;
          filename[v9] = *((_BYTE *)ptr + l);
        }
        v10 = v25++;
        filename[v10] += 67;
        v11 = v25++;
        filename[v11] = filename[v11];
        v12 = v25++;
        filename[v12] += 76;
        filename[v25 - 2] = 83;
        free(ptr);
      }
    }
  }
  qmemcpy(v38, &unk_21C0, 0x208uLL);
  qmemcpy(v36, &unk_23E0, 0x104uLL);
  for ( i = 0LL; (unsigned __int64)i <= 0x3F; ++i )
  {
    if ( v36[i] - v21 <= 0 )
      v13 = v21 - v36[i];
    else
      v13 = v36[i] - v21;
    v14 = filename[v38[v13 % 0x41]];
    if ( v36[i] - v21 <= 0 )
      v15 = v21 - v36[i];
    else
      v15 = v36[i] - v21;
    v35[v15 % 0x41] = v14;
  }
  for ( i = 0LL; (unsigned __int64)i <= 0x3F; ++i )
  {
    if ( (i & 1) != 0 )
    {
      if ( v21 == LODWORD(v38[64]) + v36[64] )
      {
        filename[i] = v35[i];
        if ( i == 1 )
          j += 3LL;
      }
    }
    else
    {
      filename[i] = v35[i];
      if ( !i )
        j = 0LL;
    }
  }
  filename[i] = 0;
  printf((const char *)*(&fStr + j), filename);
  return 0;
}

我们可以注意到两件有意思的事情:
1. 程序不需要任何输入,但是它读取了系统时间,根据系统时间不同执行的东西不太一样
2. 程序读取了一个文件,对这个文件可能有什么特殊要求

文件名称并没有明文存储,而是需要用当前的系统时间的分钟数与一些数据异或得到。因为只有60种可能,这里就暴力就好了。结论是正确的分钟数是 43,文件名是 "3CHOLARSHIP"。

然后仔细看这部分:

  if ( stream )
  {
    fseek(stream, 0LL, 2);
    v3 = ftell(stream);
    i = v3 == (v19 ^ (LODWORD(v37[47]) + v34)) + 101606;
    fclose(stream);
  }
  v21 = v30->tm_hour;
  if ( I )
  {
  .....

程序只会在 i 等于 1 的时候继续运行, 所以 v3 == (v19 ^ (LODWORD(v37[47]) + v34)) + 101606 必须成立。 v3 是文件长度, 因为 fseek 把当前文件指针移到文件末尾,然后 ftell 返回当前指针位置。 v37[47] 的值除了 qmemcpy(v37, &unk_2040, sizeof(v37)); 以外都没被修改过, 所以我们知道他是 6。 v19 是当前分钟的值 43,v34 是 5. 计算下, (v19 ^ (LODWORD(v37[47]) + v34)) + 101606 事实上就是 101638。所以,我们执行

 with open("3CHOLARSHIP", "w") as f:
    f.write("A"*101638)

来创建正确长度的这个文件。

下一部分跟最开始类似,用系统时间的小时数解密。因此我们就把24种可能性暴力一遍。正确的时间是 11 点。

所以,在 11:43 运行这个程序(或者修改系统时间到这个时间) 就可以得到 flag, DSO-NUS{654c7b655ed2e3eb42f1d886786c14fe5a757e416f5373de5cd2e4089b870eb5da}

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注