はまやんはまやんはまやん

hamayanhamayan's blog

Automotive CTF Japan 予選 Writeup

https://ctftime.org/event/2473

誘っていただき、TeamOneとして出てました。自分が良く取り組んだ問題について書いていきます。メンバーのWriteupはここここ。決勝進出!

[Stego] Walk in the Park

park.binというファイルが与えられるのでステガノする問題。

この問題では、問題タイトルと問題文から解法を推理する必要がある。

Walk in the Park
Don't waste too much time!

第一ステップ

まずはタイトルから推理して、binWalk in the park.binをする。

$ binwalk -e park.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
57322         0xDFEA          uImage header, header size: 64 bytes, header CRC: 0x3DE33638, created: 2022-04-26 19:08:39, image size: 383504341 bytes, Data Address: 0xC136CE7E, Entry Point: 0xD0124D5E, data CRC: 0x1A688395, OS: Esix, CPU: PowerPC, image type: Firmware Image, image name: "null"
116932        0x1C8C4         gzip compressed data, has original file name: "null", from Acorn RISCOS, last modified: 2025-05-26 10:01:27
166972        0x28C3C         uImage header, header size: 64 bytes, header CRC: 0x4A0D4D83, created: 2026-01-18 17:56:49, image size: 19289263 bytes, Data Address: 0x1FD34521, Entry Point: 0x1697520B, data CRC: 0x82B8DCA1, OS: Esix, CPU: PowerPC, image type: Firmware Image, image name: "null"
204221        0x31DBD         BSD 2.x filesystem, size: -1252334617600 bytes, total blocks: -1222983025, free blocks: 0, last modified: 2031-03-30 03:08:45

4つ出てくる。特筆すべき点がnameがnullになっているという点。

第二ステップ

次のヒントはDon't waste too much time!である。binwalkの結果を見るとどれも時間が書かれていた。これをunixtimeに変換してasciiに変換してみよう。以下のようなスクリプトを使う。

import datetime

def datetime_to_unix_bytes(date_str:str, timezone) -> bytes:
    dt = datetime.datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
    unix_time = int(dt.timestamp()) + timezone
    unix_time_bytes = unix_time.to_bytes(4, byteorder='big')
    
    return unix_time_bytes

ans = datetime_to_unix_bytes("2022-04-26 19:08:39", 0) + datetime_to_unix_bytes("2025-05-26 10:01:27", 0) + datetime_to_unix_bytes("2026-01-18 17:56:49", 0) + datetime_to_unix_bytes("2031-03-30 03:08:45", 0)
print(ans)

するとb'bg\xc4\xa7h3\xbdgil\xa0Qs0\xbd\xad'という出力が得られた。bから始まっていますね。何か良い予感する。

第三ステップ

時刻を見るとタイムゾーンが気になるのが人の性。ローカルタイムになっているのではないかということで、タイムゾーンガチャをしてみよう。以下のように適当に増やして回してみる。

import datetime

def datetime_to_unix_bytes(date_str:str, timezone) -> bytes:
    dt = datetime.datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
    unix_time = int(dt.timestamp()) + timezone
    unix_time_bytes = unix_time.to_bytes(4, byteorder='big')
    
    return unix_time_bytes

for dt in range(24*60*60):
    ans = datetime_to_unix_bytes("2022-04-26 19:08:39", dt) + datetime_to_unix_bytes("2025-05-26 10:01:27", dt) + datetime_to_unix_bytes("2026-01-18 17:56:49", dt) + datetime_to_unix_bytes("2031-03-30 03:08:45", dt)
    try:
        if ans.decode().startswith('bh{'):
            print(dt, ans)
    except:
        pass

これを実行すると、以下のような結果が得られる。

46767 b'bh{Vh4t\x16imW\x00s1t\\'
46768 b'bh{Wh4t\x17imW\x01s1t]'
46769 b'bh{Xh4t\x18imW\x02s1t^'
46770 b'bh{Yh4t\x19imW\x03s1t_'
46771 b'bh{Zh4t\x1aimW\x04s1t`'
46772 b'bh{[h4t\x1bimW\x05s1ta'
46773 b'bh{\\h4t\x1cimW\x06s1tb'
46774 b'bh{]h4t\x1dimW\x07s1tc'
46775 b'bh{^h4t\x1eimW\x08s1td'
46776 b'bh{_h4t\x1fimW\ts1te'
46777 b'bh{`h4t imW\ns1tf'
46778 b'bh{ah4t!imW\x0bs1tg'
46779 b'bh{bh4t"imW\x0cs1th'
46780 b'bh{ch4t#imW\rs1ti'
46781 b'bh{dh4t$imW\x0es1tj'
46782 b'bh{eh4t%imW\x0fs1tk'
46783 b'bh{fh4t&imW\x10s1tl'
46784 b"bh{gh4t'imW\x11s1tm"
46785 b'bh{hh4t(imW\x12s1tn'
46786 b'bh{ih4t)imW\x13s1to'
46787 b'bh{jh4t*imW\x14s1tp'
46788 b'bh{kh4t+imW\x15s1tq'
46789 b'bh{lh4t,imW\x16s1tr'
46790 b'bh{mh4t-imW\x17s1ts'
46791 b'bh{nh4t.imW\x18s1tt'
46792 b'bh{oh4t/imW\x19s1tu'
46793 b'bh{ph4t0imW\x1as1tv'
46794 b'bh{qh4t1imW\x1bs1tw'
46795 b'bh{rh4t2imW\x1cs1tx'
46796 b'bh{sh4t3imW\x1ds1ty'
46797 b'bh{th4t4imW\x1es1tz'
46798 b'bh{uh4t5imW\x1fs1t{'
46799 b'bh{vh4t6imW s1t|'
46800 b'bh{wh4t7imW!s1t}'
46801 b'bh{xh4t8imW"s1t~'
46802 b'bh{yh4t9imW#s1t\x7f'

とてもいい感じ。見ると46800の時に正解のようなフラグができている。bh{wh4t7imW!s1t}。提出してみるが、不正解。

第四ステップ

フラグを見ると、どう見てもwhat time is itにしたい雰囲気を感じる。4文字ずつ生成されることを考えると、

bh{w
h4t7
imW!
s1t}

という感じになるが、3番目だけどうもおかしい。そうですね、3番目だけタイムゾーンが違う。3番目のタイムゾーンを全探索しなおす。さすがに1時間単位だろうと思うので以下のように書いて様子を見る。

import datetime

def datetime_to_unix_bytes(date_str:str, timezone) -> bytes:
    dt = datetime.datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
    unix_time = int(dt.timestamp()) + timezone
    unix_time_bytes = unix_time.to_bytes(4, byteorder='big')
    
    return unix_time_bytes

dt1 = 46800

for dt2 in range(24):
    ans1 = datetime_to_unix_bytes("2022-04-26 19:08:39", dt1) + datetime_to_unix_bytes("2025-05-26 10:01:27", dt1)
    ans2 = datetime_to_unix_bytes("2026-01-18 17:56:49", dt2*60*60)
    ans3 = datetime_to_unix_bytes("2031-03-30 03:08:45", dt1)
    ans = ans1 + ans2 + ans3
    print(dt2, ans)

これの結果が以下。

0 b'bh{wh4t7il\xa0Qs1t}'
1 b'bh{wh4t7il\xaeas1t}'
2 b'bh{wh4t7il\xbcqs1t}'
3 b'bh{wh4t7il\xca\x81s1t}'
4 b'bh{wh4t7il\xd8\x91s1t}'
5 b'bh{wh4t7il\xe6\xa1s1t}'
6 b'bh{wh4t7il\xf4\xb1s1t}'
7 b'bh{wh4t7im\x02\xc1s1t}'
8 b'bh{wh4t7im\x10\xd1s1t}'
9 b'bh{wh4t7im\x1e\xe1s1t}'
10 b'bh{wh4t7im,\xf1s1t}'
11 b'bh{wh4t7im;\x01s1t}'
12 b'bh{wh4t7imI\x11s1t}'
13 b'bh{wh4t7imW!s1t}'
14 b'bh{wh4t7ime1s1t}'
15 b'bh{wh4t7imsAs1t}'
16 b'bh{wh4t7im\x81Qs1t}'
17 b'bh{wh4t7im\x8fas1t}'
18 b'bh{wh4t7im\x9dqs1t}'
19 b'bh{wh4t7im\xab\x81s1t}'
20 b'bh{wh4t7im\xb9\x91s1t}'
21 b'bh{wh4t7im\xc7\xa1s1t}'
22 b'bh{wh4t7im\xd5\xb1s1t}'
23 b'bh{wh4t7im\xe3\xc1s1t}'

いいですね。一つだけ浮いて見えるフラグがありますね。bh{wh4t7ime1s1t}が正解。

[xNexux] Can bus anomaly #1

xNexusというVSOCプラットフォーム(つまりは車向けのSIEMとかXDRみたいなやつ)が与えられ、ログが色々出ているので設問に答える問題。設問は以下。

Analyze CAN Bus Data anomalies and find the pattern. Answer should be enclosed in the standard format flag.

問題文通りに進めていく

xNexusを巡回するとpayload_fingerprint_violation_reasonという検知がログに大量に残っていた。Analyze CAN Bus Data anomaliesはこれのことですね。となると次はfind the pattern部分であるが、眺めていくと以下のようなパターンがあることが分かった。

CanID         Data
0x00000760    0314ff0000000000
0x00000768    6f6d346e00000000
0x00000768    0354ff000000346c
0x00000094    0279660000000000
0x00000094    0000400000000000
0x00000094    6c346700000000
0x00000094    0000800000000000
0x00000094    1337c00000beff00

パターンがどの部分を指すのかは分かった。あとは、答えをいつものフラグ形式を使って回答するだけのシンプルな問題。

Ascii変換

いつものようにとりあえずasciiに変換してみよう。

CanID         Data
0x00000760    0314ff00 00000000   .... ....
0x00000768    6f6d346e 00000000   om4n ....
0x00000768    0354ff00 0000346c   .T.. ..4l
0x00000094    02796600 00000000   .yf. ....
0x00000094    00004000 00000000   ..@. ....
0x00000094    6c346700 000000     l4g. ...
0x00000094    00008000 00000000   .... ....
0x00000094    1337c000 00beff00   .7.. ....

l4gom4nのようなフラグの断片のようなものが見えてくる。

グッと睨むと

bh{4nom4lyfl4g} フラグが出てきます。正攻法はあるかもしれないが分からなかった。

[OSINT] 1 or 2?

問題文は以下。

What is the make and color of our other vehicle we owned? One is grey.
Write answer in format: bh{color_make}, for example: bh{yellow_cadillac}

誰かが2台車を持っていて、1台はグレーだが、もう1台は何色でどこのメーカーでしょうかという問題。

OSINTと言えばSNS

コンテストサイトのトップページにLinkedInのリンクがあった。これを開くとBlock Harbor Cybersecurityへのリンクがある。投稿を見ていくと、このようなポストが見つかる。

1台はグレーで、1台は赤の車が並んで撮影されていた。手前の車を画像検索すると、Ford Mustangであることが分かるのでbh{red_ford}

[Misc] Lost in the echo

ctf.srというロジックアナライザ―ファイルが手に入るので、デコードする問題。PulseViewで開く。2つ通信の塊が記録されている。

1つ目の塊

周波数を計算すると9615bpsで、LIN通信っぽい見た目をしていたので、LIN通信でデコードにしてBaudを9615にすると色々出てきた。UART RX dataを全部ダンプしてくる。

9729251-9749221 UART: RX data: 20
9749220-9751717 UART: RX data: Stop bit
9751711-9754208 UART: RX data: Start bit
9754207-9774177 UART: RX data: 4C
9774176-9776673 UART: RX data: Stop bit
9776668-9779165 UART: RX data: Start bit
9779164-9799134 UART: RX data: 6F
9799133-9801630 UART: RX data: Stop bit
9801624-9804121 UART: RX data: Start bit

UARTなのでstart/stop bitがあり、データが送信されている。データを全部持ってきてasciiにすると以下のようになる。

 Loaded Succesfully
CPU clock speed:   792MHz
Encoding the secret with shift 13
Copying the secret codes to vault
echo "OU{HNEG3AP...


Detected noise on the line.. Falling back to lower transmission speed

ほう。周波数を調整しようという話をしていますね。

2つ目の塊

こちらも周波数を計算すると1200bpsで同様の手順でasciiにすると

Switched to lower tranmission speed
Enabling more "secure" transmission
Encoding the secret to be a little more safe

01001111 01010101 01111011 01001000 01001110 01000101 01000111 00110011 01000001 01010000 00110000 01010001 00110011 01001110 01000001 01010001 01010001 00110011 01010000 00110000 01010001 00110011 01111101

といい感じに出てくるので後は以下のような感じで変換すればフラグが出てくる。

https://gchq.github.io/CyberChef/#recipe=From_Binary('Space',8)ROT13(true,true,false,13)&input=MDEwMDExMTEgMDEwMTAxMDEgMDExMTEwMTEgMDEwMDEwMDAgMDEwMDExMTAgMDEwMDAxMDEgMDEwMDAxMTEgMDAxMTAwMTEgMDEwMDAwMDEgMDEwMTAwMDAgMDAxMTAwMDAgMDEwMTAwMDEgMDAxMTAwMTEgMDEwMDExMTAgMDEwMDAwMDEgMDEwMTAwMDEgMDEwMTAwMDEgMDAxMTAwMTEgMDEwMTAwMDAgMDAxMTAwMDAgMDEwMTAwMDEgMDAxMTAwMTEgMDExMTExMDE&ieol=CRLF&oeol=CRLF

[Stego] ivi

ディスクイメージが与えられるので色々頑張ってフラグを持って来る問題。解法ログをちゃんと残していなくていまいち解法を覚えていないが、確か以下のような流れ。

  1. 削除されたファイルからとあるパスワードを取得し
  2. rolled.jpgといういつもの人の画像をbinwalkすると暗号化zipが手に入り
  3. 暗号化zipを手順1のパスワードで解凍するとパスワードが手に入り
  4. 手順3のパスワードでLUKS暗号化領域を開き
  5. 1206112547-29099.txtという座標が書かれたファイルが手に入るので、
  6. Google Mapで座標を全部ピン止めしてみるとROUND_THE_WORLDという文字が浮かび上がってきて、それがフラグ

[Misc] Siggy

cybertruck.pngというファイルが与えられるのでステガノする問題。

Part 1

exiftoolで見てみるとフラグの前半部分が見つかる。

$ exiftool cybertruck.png
...
Interlace                       : Noninterlaced
Exif Byte Order                 : Little-endian (Intel, II)
Camera Model Name               : Y3liM3JU
Interoperability Index          : Unknown (VHJ1Q2tf)
SRGB Rendering                  : Perceptual
Image Size                      : 734x734
Megapixels                      : 0.539

Camera Model NameとInteroperability Indexに妙な文字列が入っている。2つを繋げたY3liM3JUVHJ1Q2tfbase64デコードするとcyb3rTTruCk_だった。

Part 2

この車の画像はTeslaのCYBERTRUCKの画像である。オリジナルが無いか探してみると、ここにそれっぽいのがあった。縦のサイズが734pxで一致している。

画像比較してみよう、ということでオリジナルの画像を与えられているcybertruck.pngに合うように加工をしてxor和を取る。

convert cybertruck.png internelt.png -fx "(((255*u)&(255*(1-v)))|((255*(1-u))&(255*v)))/255" out.png

出てきたout.pngを青い空を見上げればいつもそこに白い猫に食わせて、ステガノグラフィー解析でポチポチやっていくと、RGBそれぞれの下位0ビットを抽出するとQRコードが浮かびあがってきた。 RのQRコードは壊れていたが、GとBは同じ結果を得ることができて、1s_we1rdが得られる。

よって

(フラグミスはあったので調整をして)bh{cyb3rTruCk_1s_we1rd}が答え。

[Stego] Stego 1

青い空を見上げればいつもそこに白い猫に食わせて、ステガノグラフィー解析でポチポチやるとフラグが出てくる。