🔥

picoCTF 2024 Writeup - Reverse Engineering

2024/03/31に公開

packer - 100 points

ELFのバイナリが提供される

stringsでもフラグっぽいものが見当たらない

$ strings out | grep -i pico
$ strings out | grep -i flag

タイトルがpackerなのでupxでunpackする

$ upx -d out
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar & John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    877724 <-    336520   38.34%   linux/amd64   out

Unpacked 1 file.

stringsすると今度は引っかかる

$ strings out  | grep -i flag
Password correct, please see flag: 7069636f4354467b5539585f556e5034636b314e365f42316e34526933535f39343130343638327d
(mode_flags & PRINTF_FORTIFY) != 0
WARNING: Unsupported flag value(s) of 0x%x in DT_FLAGS_1.
version == NULL || !(flags & DL_LOOKUP_RETURN_NEWEST)
flag.c
_dl_x86_hwcap_flags
_dl_stack_flags

7069636f4354467b5539585f556e5034636b314e365f42316e34526933535f39343130343638327dをhexとして解釈すると、フラグは以下になる。

picoCTF{U9X_UnP4ck1N6_B1n4Ri3S_94104682}

FactCheck - 200 points

バイナリが提供される。

デコンパイラのHex-Raysでデコンパイルして、フラグを作っている部分を抜き出すと以下のようなコードになる。

  std::allocator<char>::allocator(&v21, argv, envp);
  std::string::basic_string(v22, "picoCTF{wELF_d0N3_mate_", &v21);
  std::string::basic_string(v23, "4", &v21);
  std::string::basic_string(v24, "5", &v21);
  std::string::basic_string(v25, "6", &v21);
  std::string::basic_string(v26, "3", &v21);
  std::string::basic_string(v27, "e", &v21);
  std::string::basic_string(v28, "5", &v21);
  std::string::basic_string(v29, "a", &v21);
  std::string::basic_string(v30, "e", &v21);
  std::string::basic_string(v31, "e", &v21);
  std::string::basic_string(v32, "d", &v21);
  std::string::basic_string(v33, "b", &v21);
  std::string::basic_string(v34, "f", &v21);
  std::string::basic_string(v35, "6", &v21);
  std::string::basic_string(v36, "e", &v21);
  if ( *(char *)std::string::operator[](v24, 0LL) <= 65 ) // true
    std::string::operator+=(v22, v34); // v22 += "f"
  if ( *(_BYTE *)std::string::operator[](v35, 0LL) != 65 ) // true
    std::string::operator+=(v22, v37); // v22 += "d"
  if ( "Hello" == "World" ) // false
    std::string::operator+=(v22, v25);
  v19 = *(char *)std::string::operator[](v26, 0LL);
  if ( v19 - *(char *)std::string::operator[](v30, 0LL) == 3 ) // false
    std::string::operator+=(v22, v26);
  std::string::operator+=(v22, v25); // v22 += "6"
  std::string::operator+=(v22, v28); // v22 += "5"
  if ( *(_BYTE *)std::string::operator[](v29, 0LL) == 71 ) // false
    std::string::operator+=(v22, v29);
  std::string::operator+=(v22, v27); // v22 += "e"
  std::string::operator+=(v22, v36); // v22 += "e"
  std::string::operator+=(v22, v23); // v22 += "4"
  std::string::operator+=(v22, v31); // v22 += "e"
  std::string::operator+=(v22, 125LL); // v22 += "}"

この文字列連結を実行すると、フラグは以下になる。

picoCTF{wELF_d0N3_mate_fd65ee4e}

Classic Crackme 0x100 - 300 points

バイナリが提供されるが、これはサンプルフラグになっており、実際のフラグはインスタンスにアクセスして取得する。

バイナリをHex-Raysでデコンパイルするとmain関数は以下のようになる。

//----- (0000000000401176) ----------------------------------------------------
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char input[51]; // [rsp+0h] [rbp-A0h] BYREF
  char output[51]; // [rsp+40h] [rbp-60h] BYREF
  int random2; // [rsp+7Ch] [rbp-24h]
  int random1; // [rsp+80h] [rbp-20h]
  char fix; // [rsp+87h] [rbp-19h]
  int secret3; // [rsp+88h] [rbp-18h]
  int secret2; // [rsp+8Ch] [rbp-14h]
  int secret1; // [rsp+90h] [rbp-10h]
  int len; // [rsp+94h] [rbp-Ch]
  int i_0; // [rsp+98h] [rbp-8h]
  int i; // [rsp+9Ch] [rbp-4h]

  strcpy(output, "qhcpgbpuwbaggepulhstxbwowawfgrkzjstccbnbshekpgllze");
  setvbuf(_bss_start, 0LL, 2, 0LL);
  printf("Enter the secret password: ");
  __isoc99_scanf("%50s", input);
  i = 0;
  len = strlen(output);
  secret1 = 85; 
  secret2 = 51; 
  secret3 = 15; 
  fix = 97; 
  while ( i <= 2 ) 
  {
    for ( i_0 = 0; i_0 < len; ++i_0 )
    {   
      random1 = (secret1 & (i_0 % 255)) + (secret1 & ((i_0 % 255) >> 1));
      random2 = (random1 & secret2) + (secret2 & (random1 >> 2));
      input[i_0] = ((random2 & secret3) + input[i_0] - fix + (secret3 & (random2 >> 4))) % 26 + fix;
    }   
    ++i;
  }
  if ( !memcmp(input, output, len) )
    printf("SUCCESS! Here is your flag: %s\n", "picoCTF{sample_flag}");
  else
    puts("FAILED!");
  return 0;
}

ユーザの入力(input)に対して、while (i<=2)のブロックで変換処理を入れて、outputと等しくなれば正解としてフラグを出力する動きになっている。

outputに対して変換処理の逆演算を実行することで、入力すべき文字列を作成するスクリプトを作る


output = "qhcpgbpuwbaggepulhstxbwowawfgrkzjstccbnbshekpgllze"
length = len(output)
secret1 = 85
secret2 = 51
secret3 = 15
fix = 97

input = list(output)

for _ in range(3):
    for i in range(length):
        random1 = (secret1 & (i % 255)) + (secret1 & ((i % 255) >> 1))
        random2 = (random1 & secret2) + (secret2 & (random1 >> 2))
        input[i] = chr((ord(input[i]) - fix - (secret3 & (random2 >> 4)) - (random2 & secret3)) % 26 + fix)

print("".join(input))

これを実行すると以下のように出力される

qezjdvjltvuxavgiibmkrsncqrntxfykgmntwsepmyvyguzwtv

インスタンスにアクセスしてこの文字列を入力するとフラグを取得できる。

Enter the secret password: qezjdvjltvuxavgiibmkrsncqrntxfykgmntwsepmyvyguzwtv
SUCCESS! Here is your flag: picoCTF{s0lv3_angry_symb0ls_4699696e}

weirdSnake - 300 points

ファイルが提供される。

このファイルは以下のようなテキストで、pythonのバイトコードをdisassembleした結果のようである。

  1           0 LOAD_CONST               0 (4)
              2 LOAD_CONST               1 (54)
              4 LOAD_CONST               2 (41)
              6 LOAD_CONST               3 (0)
              8 LOAD_CONST               4 (112)
             10 LOAD_CONST               5 (32)
             12 LOAD_CONST               6 (25)
             14 LOAD_CONST               7 (49)
             16 LOAD_CONST               8 (33)
             18 LOAD_CONST               9 (3)
             20 LOAD_CONST               3 (0)

disassembleしたものを直接実行するようなツールは探してみたが見つからなかったので、自力でpythonのコードに変換してみる。
基本的にはdisモジュールにdisassembleした命令の定義が記載されている。最新のバージョンではなく少し古いバージョンで書かれているようなので、古いドキュメントを読む必要がある。

def listcomp(iterator):
    result = []
    for char in iterator:
        result.append(ord(char))
    return result

def listcomp2(iterator):
    result = []
    for a, b in iterator:
       result.append(a ^ b)
    return result

input_list = [4,54,41,0,112,32,25,49,33,3,0,0,57,32,108,23,48,4,9,70,7,110,36,8,108,7,49,10,4,86,43,104,44,91,7,18,106,124,89,78]
key_str = 'J'
key_str = '_' + key_str
key_str = key_str + 'o'
key_str = key_str + '3'
key_str = 't' + key_str
key_list = listcomp(iter(key_str))
while len(key_list) < len(input_list):
    key_list.extend(key_list)

result =  listcomp2(iter(zip(input_list, key_list)))
result_text = ''.join(map(chr, result))

print(result_text)

これを実行すると以下のフラグが出力される

picoCTF{N0t_sO_coNfus1ng_sn@ke_7f44f566}

Discussion