Beginners CTF 2020 Writeup (ctf4b)

概要

タイトル通りです。
2020/5/23 ~ 2020/5/24にかけて開催されたCTFイベントに参加しました。

注意

  • Writeupを書くのは初めてです
  • CTF初心者です

Writeup 目次

Web

Reversing

  • mask

Misc

  • Welcome
  • emoemoencode

Writeup

Web

Spy

ある会社の従業員が使用しているサイトについて、アカウントが登録されている従業員を探し出す問題です。

サーバプログラム「app.py」と従業員のリスト「employee.txt」が配布されています。

始めはSQLインジェクション…?なんて思っていたのですが、app.pyやサイト表示を確認してみると、ログインボタンが押されてから処理が完了するまでの時間が表示/計算されていることに気づきました。

またapp.pyをよく読むと、「アカウントが存在しない」「パスワードが正しくない」といった分岐が成立しなかったときに処理時間の計測を終了するようになっていました。

加えてプログラム内にヒント

# auth.calc_password_hash(salt, password) adds salt and performs stretching so many times.
# You know, it's really secure... isn't it? :-)

が書かれていました。

これらの情報から

  • 処理時間を確認することで、どこで認証プロセスが打ち切られたのかが分かる
  • どうやら auth.calc_password_hash には何かしらの脆弱性があるらしい

ことが分かりました。

そして、従業員全てについてログインを試行してみると、ある従業員だけが異様に処理時間が遅いことに気づきました。

よって、auth.calc_password_hash を実行した場合相当の時間がかかるのだろうと判断し、その呼び出しに到達している時点でユーザの存在確認がされていたことから、処理時間が異様に長い従業員がアカウント登録されているのだと分かりました。

得られた情報をサイト上のChallengeページに入力するとフラグ ctf4b{4cc0un7_3num3r4710n_by_51d3_ch4nn3l_4774ck} を獲得できました!

Reversing

mask

配布されているバイナリファイル「mask」の挙動を検証する問題です。

mask を実行してみると文字列の入力を求められました。

入力した文字列はある一定の規則のもとで変換が行われ、その変換結果が検証されるようです。

そこで、mask を逆アセンブルしたものを読んでいくと以下の規則があることが判明しました。

  • 入力された文字列の i 番目の文字コード0x75 のandを取ったものが変換後の文字列Aになる
  • 入力された文字列の i 番目の文字コード0xffffffeb のandを取ったものが変換後の文字列Bになる
  • AとBが別に存在する文字列と一致したときの入力文字列がフラグ

文字列A, Bと比較する文字列は strings コマンドを使って取り出します。

よって、全探索で雑にフラグを見つけるコードを書いて実行するとフラグ ctf4b{dont_reverse_face_mask} を得ることができました!

(解いている途中にmaskのcへの人力逆コンパイルを行ったのでその結果も貼っておきます)

(間違いがあればご指摘ください)

mask_solve.c

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

#define TARGETA "atd4`qdedtUpetepqeUdaaeUeaqau"
#define TARGETB "c`b bk`kj`KbababcaKbacaKiacki"

char *target_a = TARGETA;
char *target_b = TARGETB;

char solve(int32_t idx) {
    for(char c = '!'; c <= '~'; ++ c) {
        if((c&0x75) == target_a[idx] && (c&0xffffffeb) == target_b[idx])
            return c;
    }
    return '@';
}

int main() {
    char *target_a = TARGETA;
    char *target_b = TARGETB;

    for(int32_t idx = 0; idx < strlen(target_a); ++ idx) {
        printf("%c", solve(idx));
    }
}

mask(人力逆コンパイル結果)

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

#define USAGE   "Usage: ./mask [FLAG]\n"
#define PUTTING "Putting on masks...\n"
#define TARGETA "atd4`qdedtUpetepqeUdaaeUeaqau"
#define TARGETB "c`b bk`kj`KbababcaKbacaKiacki"
#define SUCCESS "Correct! Submit your FLAG.\n"
#define FAILED  "Wrong FLAG. Try again.\n"

int main(int32_t argc, char **args) {
    // 1. 引数チェック
    if(argc < 2) {
        printf(USAGE);
        return 0;
    }

    // 2. strcpy, strlen FLAG
    int32_t flag_len = strlen(args[1]);
    char *flag = malloc(flag_len*sizeof(char)+1);
    strcpy(flag, args[1]);

    // 3. "Putting~" 出力
    printf(PUTTING);

    // 4. 変数準備
    char *str_90 = malloc(flag_len*sizeof(char)+1);
    char *str_50 = malloc(flag_len*sizeof(char)+1);

    for(int32_t idx = 0; idx < flag_len; ++ idx) {
        // 4. str90のidx文字目 = (flagのidx文字目) & 0x75
        str_90[idx] = flag[idx] & 0x75;

        // 5. str50のidx文字目 = (flagのidx文字目) & 0xffffffeb
        str_50[idx] = flag[idx] & 0xffffffeb;
    }

    // 6. str90, str50の末端に0x00(null文字)を追加
    str_90[flag_len] = 0x00;
    str_50[flag_len] = 0x00;

    // 7. str90, str50出力
    printf("%s\n", str_90);
    printf("%s\n", str_50);

    // 8. str90と文字列A, str50と文字列Bを比較 -> 分岐
    if(strcmp(str_90, TARGETA) == 0 && strcmp(str_50, TARGETB) == 0) {
        printf(SUCCESS);
    } else {
        printf(FAILED);
    }

    // 9. 実行終了
    return 0;
}

Misc

Welcome

説明通りCTF4BのDiscordサーバにアクセスすると、フラグ ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord} を得ることができます。

emoemoencode

絵文字が書かれたテキストファイル「emoemoendode.txt」いろいろ弄ってフラグを獲得する問題です。

とりあえず、hexdump を用いて中を見てみます。

すると…

0000000 f0 9f 8d a3 f0 9f 8d b4 f0 9f 8d a6 f0 9f 8c b4
0000010 f0 9f 8d a2 f0 9f 8d bb f0 9f 8d b3 f0 9f 8d b4
0000020 f0 9f 8d a5 f0 9f 8d a7 f0 9f 8d a1 f0 9f 8d ae
0000030 f0 9f 8c b0 f0 9f 8d a7 f0 9f 8d b2 f0 9f 8d a1
0000040 f0 9f 8d b0 f0 9f 8d a8 f0 9f 8d b9 f0 9f 8d 9f
0000050 f0 9f 8d a2 f0 9f 8d b9 f0 9f 8d 9f f0 9f 8d a5
0000060 f0 9f 8d ad f0 9f 8c b0 f0 9f 8c b0 f0 9f 8c b0
0000070 f0 9f 8c b0 f0 9f 8c b0 f0 9f 8c b0 f0 9f 8d aa
0000080 f0 9f 8d a9 f0 9f 8d bd 0a
0000089

という結果を得ることができました。

パッと見ただけでも規則性があることが大体分かりました。

  • 4バイトで1区切り
  • f0 9f 全てで共通なので無視してよさそう
  • 残り2バイトを解読していくとフラグが得られそう
  • 8d, 8c は何らかの種類を示しているだけで文字自体の情報は持っていなさそう

ということで、まずは各文字4バイト目だけに着目して解読をしてみます。

先頭5文字は ctf4b であるはずなので、これをもとにASCII表と見比べつつ対応を探すと、

  • (英小文字) = (値) - 161 + 97
  • (数字) = (値) -176 + 48

という規則があることが分かりました。

合わせて、3バイト目の 8c 8d についてはそれぞれ 「数字」「英小文字」表していることが分かりました。

この規則をとりあえず全ての値に対して適応していくとフラグ ctf4b{stegan0graphy_by_em000000ji} を得ることができました!

解く際に使用したコードを以下に貼ります。(ファイル末尾の改行文字を無視しているので、フラグが出力された後 IndexError で落ちます)

with open("emoemoencode.txt", "rb") as f:
    content = f.read()
    for idx in range(0, len(content), 4):
        chars = content[idx:idx+4]
        bias = 0
        if chars[2]-0x8c == 0:
            bias = -176+48
        elif chars[2]-0x8c == 1:
            bias = -161+97
        # print(chars[2], end=",")
        # print(chars[3], end=",")
        print(chr(chars[3]+bias), end="")

まとめ

今回は Web/Reversing/Misc を解きました。

Crypt/Pwnに全く手を出すことができずに終わったのが悔しいです。 やはり自分の知識&技術不足を痛感したのでもっと精進していくべき…していきます。

また、点数を見て避けてしまっていたことがあったのでチャレンジしていくべきだと感じました。

今回は開始と同時に参加することができなかったので、次はスケジュールもしっかり立てておきます…

最後に、CTFを開催するにあたって様々な準備、サポートをしてくださった運営の方々ありがとうございました!(楽しかったです!)

読んでいただきありがとうございました!

一応貼っておく、みたいなもの

maskを読んでいるときに書いていたメモです、一応貼ります。

f:id:guguru0014:20200524021349j:plain