https://ctftime.org/event/2473
誘っていただき、TeamOneとして出てました。自分が良く取り組んだ問題について書いていきます。メンバーのWriteupはこことここ。決勝進出!
- [Stego] Walk in the Park
- [xNexux] Can bus anomaly #1
- [OSINT] 1 or 2?
- [Misc] Lost in the echo
- [Stego] ivi
- [Misc] Siggy
- [Stego] Stego 1
[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.. ....
l4g
やom4n
のようなフラグの断片のようなものが見えてくる。
グッと睨むと
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
といい感じに出てくるので後は以下のような感じで変換すればフラグが出てくる。
[Stego] ivi
ディスクイメージが与えられるので色々頑張ってフラグを持って来る問題。解法ログをちゃんと残していなくていまいち解法を覚えていないが、確か以下のような流れ。
- 削除されたファイルからとあるパスワードを取得し
- rolled.jpgといういつもの人の画像をbinwalkすると暗号化zipが手に入り
- 暗号化zipを手順1のパスワードで解凍するとパスワードが手に入り
- 手順3のパスワードでLUKS暗号化領域を開き
- 1206112547-29099.txtという座標が書かれたファイルが手に入るので、
- 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つを繋げたY3liM3JUVHJ1Q2tf
をbase64デコードすると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
青い空を見上げればいつもそこに白い猫に食わせて、ステガノグラフィー解析でポチポチやるとフラグが出てくる。