Try   HackMD

SECCON beginners CTF 2024 Writeup

2024/06/15(~24h)に行われたSECCONのbeginner向けCTF大会にチームで出場しました。(Team Name: FCS-Zundamons, User Name: HydrangeA)(順位: 366/962)

CTFを3回ほど経験した上でチームで出場させてもらいました。チームメンバーの後輩たちにCTFこんな感じだよ~と教えつつ主にreversingの問題に取り組みました。しかし、実際に解けた問題は1問だけであまり成長できてないことを実感しました。(悲しい)

なのでwriteupとしては1問、他の問題で解けなかったものに関してはここまで勉強できたという記録を残す意味で書かせていただきました。

何かの参考になれば幸いです。

解答した問題

  • (reversing easy) cha-ll-enge

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

環境

  • Windows 11 Home 22H2
  • WSL2 Ubuntu 22.04

cha-ll-enge [reversing easy]

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

reversingの問題の傾向として、ファイルが添付されてその中身を解読してフラグを取る問題が定番というイメージがあったので、この問題もそうだろうと感じました。なのでさっそくダウンロード、中身を見てみます。

ファイルの中身
cha.ll.enge

.ll.enge?となったのでfileコマンドを打ってみる。

file cha.ll.enge
cha.ll.enge: ASCII text, with very long lines (478)

ただのテキストファイルのようなので、catコマンドでファイルを見てみると

@__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

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
...

見たことのないコードのようなものが出てきました。これを解読して欲しいということか?気合で読むものなのだろうか
一旦自力で読むことは置いといて、8行目の@main()の行を検索すれば何の言語で書かれたものか分かるのではないか、と予想して検索しました。すると、LLVMというキーワードが出てきました。
LLVMはコンパイラの実行を最適化するための言語という認識があっているか分かりませんが、知らず知らずの内にお世話になっていそうな言語だと感じました。

それはともかく、これを自力で1から学習して読むか、LLVMを逆変換してCプログラムに直すツールがあるかどうか探すか、AI(Chat-GPT)に変換してもらうか、の3択を考えました。
結論から言うと、Chat-GPTに変換してもらい、C言語プログラムを読むことで解答しました。

以下が返答結果のCプログラムです。

#include <stdio.h>
#include <string.h>
#include <stdint.h>

const int32_t key[50] = {
    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
};

int main() {
    char input[70];
    int32_t xor_result[50];
    int correct_count = 0;

    // ユーザーにフラグを入力させる
    printf("Input FLAG: ");
    scanf("%s", input);

    // 入力の長さを確認
    if (strlen(input) != 49) {
        puts("Incorrect FLAG.");
        return 1;
    }

    // XOR操作
    for (int64_t i = 0; i < 49; ++i) {
        int32_t input_val = (int32_t)input[i];
        int32_t key_val1 = key[i];
        int32_t key_val2 = key[i + 1];
        int32_t result = input_val ^ key_val1;
        result = result ^ key_val2;
        xor_result[i] = result;

        // 正しい結果かどうかを確認
        if (xor_result[i] == 0) {
            ++correct_count;
        }
    }

    // フラグの正否を出力
    if (correct_count == 49) {
        printf("Correct! FLAG is %s.\n", input);
    } else {
        puts("Incorrect FLAG.");
    }

    return 0;
}

これを見ると、key[50]というものがあり、これはLLVMコードでも確認できましたが、ユーザの標準入力に対して処理を行う秘密鍵であることが明確になりました。そして正しい標準入力(FLAG)に対してkeyの中身をxorし、xor_resultがすべて0になることでフラグの正誤を判定しているようです。
xor_resultの結果に入る値は

result=input_valkey_val[i]key_val[i+1]
であり、result = 0の時にflagと対応する文字となるため、
input_val=key_val[i]key_val[i+1]

となることが分かります。
後は上記の法則を満たすCプログラムに変換し、実行します。するとフラグが出現します。

ctf4b{7ick_7ack_11vm_int3rmed14te_repr3sen7a7i0n}

感想 見たことのない言語を見るととんでもなくどでかい壁にぶち当たったような感覚になりますが、現在では解読のためのツールでなくともChat-GPTが読み替えてくれたりするので、上手く使って学習効率などもあげていきたいと思えました。しかしChat-GPTの利用は邪道感もあり、技術力を磨くならしっかり読み直して基礎を身に着けるくらいはした方がいいのだろうか...?と思ったりしました。LLVMがトレンドなんだろうか...?出題者の意図を聞いてみたいところですね。

ここからはunsolveな問題の、考えたことの殴り書きです。

(unsolve)assemble [reversing beginner]

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

ページ先を見ると、Intel記法のアセンブリ言語で、mov・push・syscallのみでフラグを取得せよ、といった内容がありました。
アセンブリ言語については事前にある程度学んできましたが、実際に1から書くことはなかったのでいい経験になったような気がします。
ステップ3の標準出力のところまでは達成しましたが、最後にflag.txtの中身を出力するに至りませんでした。未だに最後が分からないので、その点は他のwriteupを参考にさせていただくとします。
少なくとも今回学べた内容としては、
・syscallする際の引数にあたるレジスタはrdi, rsi, rdxであること
・rbp, rspがスタックに関するアドレスを持っていること
です。レジスタ毎の特徴を理解し、実際に書きながら体験できたことが大きいです。

(unsolve) Wooorker [web beginner]

adminのみflagを取得できる認可サービスを作りました!
https://wooorker.beginners.seccon.games
脆弱性報告bot(URL)
wooorker.tar.gz 2911ea65e5bb56cca44780edeae2ccf8c0956a52

ざっくりと説明すると、adminとしてログインできた時のみ正しいトークンを受け取り、そのトークン情報があればフラグを回収できる、という問題だと思います。

adminのパスワードを取得するすべが無いように見えたため、パスワードを予想することは諦めました。代わりにトークンを作る箇所で怪しい部分があったので、トークンを偽造すれば攻撃に成功すると考えました。

adminとは別にadmin権限を持たないguestアカウントがあり、そちらでログインすればトークン(フラグは取れない)が取得できます。

そのトークンを復号する(jwtによる署名文を復号できるサイトがあった)と、暗号化前の平文(json)が取得できたため、その中身を書き換えることで偽造できそうでした。

パラメータを書き換えつつ、トークン作成時の秘密パラメータjwtSecretはよく見ると1秒で1ずつ増えているように見えたので、これが今回の脆弱性の部分につながっていると予想し、jwtSecretを先読みして書き換えることで攻撃できると考えましたが、結果としてはうまくいかず失敗しました。これ以上の方法が思いつきません。良いところまでいった気もするんですがねぇ

それ以上に脆弱性報告botの使い方が良くわからんかった。他のwriteupできちんと学習します。

まとめ

beginner向けとはいえ、やはり骨のある問題が多い。CTFというコンテストの難しさを改めて実感しました。いっぱい解いて、手札を増やすに限りますね。地道に精進していきましょう。
最後まで読んでいただきありがとうございます。