[DSO-NUS CTF 2021] SOAR Writeup

SOAR


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

Solution

From the challenge description, we can download a SOAR-challenge.pdf file. Opening the file using a PDF viewer produces no useful information. By looking at it using a hex editor, we can easily spot the file header "PK", which stands for a zip file.

Extracting the zip file out, we realise it is password-protected. By simply guess and check, the password can be obtained. The password is "dso". Alternatively, brute-forcing the password out is another viable solution.

Therefore, we get a file "soar", file it shows that it is an ELF file.

Decompiling the file using IDA gives the following output

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;
}

We can see two interesting things from the decompiled code.
1. Despite no input is required, the program takes the current time and would behave differently with different system time.
2. The program reads a file, and the file may have some format requirement in order for us to get the flag.

The filename is decrypted through xor a fixed set of value with the current minute of system time. As there are only 60 possibilities, brute-forcing is a viable solution. This would give us that the minute value of system time should be 43 and the filename is "3CHOLARSHIP".

Look closer to this part:

  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 )
  {
  .....

The code will only proceed if i is 1, so that v3 == (v19 ^ (LODWORD(v37[47]) + v34)) + 101606 must be true. v3 is actually the length of content in the file, as the fseek function moves the current pointer to the end of the file and ftell function returns the current position. The value of v37[47] is never modified except by qmemcpy(v37, &unk_2040, sizeof(v37));, so that we know its value is 6. v19 is the current minute which is 43 and v34 is 5. By calculating, (v19 ^ (LODWORD(v37[47]) + v34)) + 101606 actually equals to 101638. Therefore, we execute

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

to create and set the correct length of the file.

The next part uses the hour value of system time to decrypt. Similarity, we brute-force the 24 possibilities. This gives us the correct hour is 11.

Therefore, run the program at 11:43 (change the system time) would give us the flag, which is DSO-NUS{654c7b655ed2e3eb42f1d886786c14fe5a757e416f5373de5cd2e4089b870eb5da}

点赞

发表评论

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