Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DEFCON 34 CHVCTF Badge問

Avatar for ishii ishii
September 11, 2026
200

DEFCON 34 CHVCTF Badge問

Avatar for ishii

ishii

September 11, 2026

Transcript

  1. 今日の構成 1. バッジの構成 2. DTC Shuffle:VINと故障説明のファイル参照 3. The Speakeasy Door:

    ポートノッキングみたいな問題 4. CAN Cshim:Cで実装された処理のBoFエクスプロイト 5. まとめ 2
  2. バッジの構成 バッジ基板 PC USB RP2040 SWD接点 MicroPython C拡張 問題1 問題3

    問題2 • USBをつなぐとPCからは以下のように見える • REPL: /dev/ttyACM0 • SLCAN: /dev/ttyACM1 - Micro Pythonと会話が可能 - CANフレームをシリアル接続でやり取りする
  3. 配布バッジの調査:問題の予想 テーマ 読み取れた問題の方向性 配布版で見つかったフラグ DTC 診断機能の認証と、故障説明ファイルの参照 DUMMY{pract1c3_flag_dtc} Knock CANメッセージを送るタイミングを合わせる DUMMY{t1m1n6_15_k3y}

    C-shim C言語で実装された受信処理・メモリ管理を調べる DUMMY{b4r3_m3t4l_c0rt3x} • DTCは、普通のファイルであるflag_dtc.binに保存。 • Knockは、LessonKnock.mpyというPythonのコンパイル済みファイル内。 • C-Shimは、ファーム本体のネイティブコード側に。Python側のファイルだけでは全体が分からない。 7
  4. コンテストバッジと配布バッジの違い • 配布バッジ: 購入可能、会場でも手元でレッスンを実行できる • ファームウェアに入っているのはダミーフラグ • コンテストバッジ: 会場でUSB接続のみ •

    本番フラグが入っている • CTF問題のアルゴリズムも一部改変 • 配布版の処理は手掛かりになるが、実Flagを得るにはコンテストバッジで解析も必要 9
  5. CTF中に非想定解が禁止に変わった 開始直後 運営が把握 REPL接続可 REPLを封鎖 数チームが 1,2時間で完答 得点を取消し Flag取得方法が変更 運営へ解法を説明

    • 本物のFlagは運営から口頭で伝えられる運用に • そのためには、解法を自分の言葉で説明する必要があった 12
  6. DTC Shuffle:問題文 • You now know how to read DTCs

    and how to change the ECU's VIN. • This badge loads the DTC descriptions from an internal storage mechanism based on that VIN. • Can you figure out how the ECU is loading the text, and retrieve the restricted CTF flag? • 要旨:VINに応じた故障説明の読み込み方を調べ、制限されたflagを取得する。 • VIN(車両識別番号): DID 0xF190として扱われる • 配布バッジにはlib_lessons_LessonKnock.mpy という手順書?チュートリアル?が同梱 13
  7. DTC: VINの処理を調査 if vin.startswith("CST"): return vin[3:].strip() • VIN に CST../flag

    が入っていると • CSTが除去されて、brandが ../flag になる • Path = /dtc/../flag_dtc.bin = /flag_dtc.bin path = "/dtc/" + brand.lower() + "_dtc.bin" • パストラバーサル脆弱性が存在する 次にどうやってVINを書き換えるかを考える 15
  8. DTC: VINの書き換えを調査 • VINはUDSの DID(データID) 0xF190に保存されている • 一般に UDS WriteDataByIdentifier

    というサービスで書き換える • WriteDataByIdentifier のリクエストフォーマット 2E F1 90 <新しいVIN> • 2E: サービスID • F1 90:VINを表すDID • 以降:新しいVIN 16
  9. DTC: VINの書き換えを調査 PC 2E F1 90 <新しいVIN> バッジ 成功の場合: 6E

    F1 90 0x33はSecurityAccessDenied 実際の応答: 7F 2E 33 SecurityAccess突破が必要 17
  10. DTC: Security Access PC Seed要求: 27 01 Seed応答 67 01

    7B 6B Key送信: 27 01 XX XX バッジ 0x7B6BがSeedにあたる 今回は2ByteのSeed Keyを生成するアルゴリズムが必要 18
  11. DTC: Security Access アルゴリズムの解法 • 本番ではREPLが有効なうちに抜いた.mpy を解析してアルゴリズムを特定 • AIに.mpyを渡してソルバを作らせた def

    ror8(x, n): return ((x >> n) | (x << (8 - n))) & 0xff def compute_key(seed): lo = seed & 0xff hi = (seed >> 8) & 0xff key_lo = ror8(lo, 3) ^ hi key_hi = ror8(hi, 5) ^ lo return (key_hi << 8) | key_lo 19
  12. DTC:DTC 取得の流れ • DTC(診断トラブルコード) は UDSサービスID 0x19 (ReadDTCInformation) で取得が可能 •

    DTC一覧を要求を実行 • 送信: 19 02 01 00 00 00 00 00 • 応答: 59 02 FF 13 37 08 対象DTCが0x1337 だとわかる DTC番号 • DTCを取得コマンド 19 06 が拡張データ取得のリクエスト • 送信: 19 06 13 37 FF 00 00 00 20
  13. DTC:Flag ファイルの読み出し PC バッジ Seed 要求/応答 keyを返答 VINの更新 CST../flag パストラバーサル発生

    内部ファイル /flag_dtc.bin DTC 要求 Flagを応答 応答: 59 06 ... flag{d1agn0st1c_tr4v3rs4l} 21
  14. The Speakeasy Door:問題文 • The door is locked. To get

    in, you must knock exactly right. • The bouncer gives you a new randomly generated token every time you ask. • The lesson on the public badges is "LessonKnock". • You must provide the proof of the solution without breaking REPL. • 要旨:毎回変わるtokenを手掛かりに、正しいノックを行う。 • 補足 • ポートノッキング:決められた順番で閉じている通信ポートへアクセスすると、ファイアウォー ルを一時的に開ける仕組み • 本問はそれをCAN向けにアレンジしたCTF問題 23
  15. Knock:何が見えて、何を考えたか 1. 開始要求に対して、新しいtokenが返る • 開始要求: CAN ID 0x0005EED 送信 •

    4バイトのtoken: CAN ID 0x0005EED 受信 2. tokenからt1、t2、t3を計算 開始・token受信 t1待機 1回目 0x0ACCE55 へノック t2待機 2回目 0x0ACCE55 へノック t3待機 3回目 0x0ACCE55 へノック 24
  16. Knockの状態遷移 • 各ノック後には0x0005EF0で判定 1回目待ち 受理 2回目待ち 受理 00 = 正しいタイミング

    01 = 早すぎる 02 = 遅すぎる FF = セッション期限切れ 3回目待ち 受理 開く → 0x7FF:からフラグを取得 早い/遅い
  17. Knock: タイミングのアルゴリズムの同定 • コンテストバッジのREPLから対象の.mpyは抜けていたため、アルゴリズムはわかっていた • 問題: 「本番ファームを抜いたことを根拠にしないで、どう式を導出するか」 • C-Shimという次の問題のCAN ID

    0x666が UDS 0x23 ReadMemoryByAddressを提供していた • 読める領域を全部ダンプを試みる CAN ID: 0x666 DATA: 23 14 20 00 00 00 3C 23 14 20 00 00 00 3C ReadMemoryByAddress アドレス・長さの形式 読み始めるアドレス 0x20000000 読む長さ 60byte 認証なしでRP2040のSRAMを読めた 26
  18. Knock: SRAM内にFlagが露出 • ReadMemoryByAddress で抜いた SRAMを解析するとFlagがあった • MicroPythonの定数としてheapに常駐 0x20023E20: flag{know_not_the_knocker_but_the_knocked}

    運営に説明に行く • 運営はREPLしか禁止していないため、通ると自信満々で行ったら • 運営「 SRAMから直接読む解法は想定してないから駄目」 ← 私「えっ」 27
  19. Knock: SRAM内を探索 • MicroPythonは.mpyを読み込むと関数のバイトコードをヒープへ展開 • 次の関数がRAMから見つかった。 • LessonKnock._nibble_swap @ 0x20024380

    • LessonKnock._compute_timings @ 0x200243B0 バイトコードからアルゴリズムを特定したという体にした • 特定したアルゴリズム使ってKnockをクリアしフラグ取得した Flagのアドレス: 0x20023E20 アドレスが近すぎるため、これも非想定解として却下されるのかなと思いつつ。。。 28
  20. CAN Cshim:問題文 • Sometimes Python is just too slow for

    our beefy firmware payloads. • So, we did what any responsible engineer does: we wrote a custom Cextension to handle UDS downloads natively! • The challenge can be found on the public badges in 'LessonCShimDemo'. • You must show proof of solving this problem. • 要旨:C拡張に実装したUDSダウンローダを調べ、解法を示す。 30
  21. CAN Cshim:配布バッジを解析 Flagの位置を特定 • 配布バッジのREPLからmpyではなくネイティブ側ファームウェアを取得済み 1. Flag位置の特定: 0x10044CAA DUMMY{b4r3_m3t4l_c0rt3x} 2.

    0x10044CAA を呼び出す処理を探す • ReadDataByIdentifier(0x22) F190 でFlagが返されることを特定 if (sid == 0x22 && did == 0xF190) { if (*(uint8_t *)0x20003ECF != 1) return negative_response(0x22, 0x33); return response(0x62, 0xF190, (char *)0x10044CAA); } 31
  22. CAN Cshim:配布バッジを解析 : Flagの呼び出しを調査 • Flagを取得するためには *(uint8_t *)0x20003ECF = 1

    になる必要がある • これを1にするような処理は存在しなかった AIを使いつつ使えそうな脆弱性がないかを探索させる if (sid == 0x22 && did == 0xF190) { if (*(uint8_t *)0x20003ECF != 1) return negative_response(0x22, 0x33); return response(0x62, 0xF190, (char *)0x10044CAA); } 32
  23. CAN Cshim:配布バッジを解析 関連処理の調査 • Cハンドラを解析すると、0x31に次の処理があった。 fp = ctx->function_pointer; if (ctx->arm_byte

    != 0xAA) return error; fp(); • ctxという構造体を書き換えられればRP2040のCPUが直接実行する機械語を操作できる 33
  24. CAN Cshim:配布バッジを解析 関連処理の調査 • 0x34は ctx のメモリアドレスを返す処理をしていた。 PC バッジ UDS

    0x34 要求 Ctx メモリアドレス 何が嬉しいか 今後 ctxを汚染できたときにfunction_pointer に ctx.dataのアドレスを渡せばそこを実行できる Ctx の構造体 struct Context { uint32_t offset; uint8_t *gate_pointer; uint8_t data[…] ; ... uint8_t arm_byte; ... void (*function_pointer)(); 34 };
  25. CAN Cshim:配布バッジを解析 • UDS 0x36 CANフレーム を ctxにmemcpyしている 0x36 seq

    [payload...] memcpy しかも、この memcpyにBOF があった Ctx の構造体 struct Context { uint32_t offset; uint8_t *gate_pointer; uint8_t data[...] ; ... uint8_t arm_byte; ... void (*function_pointer)(); }; // +0x00 // +0x04 // +0x08 //+0x8A // +0x8C 35
  26. CAN Cshim:BoFの詳細 • UDS 0x36 CANフレームをctxにコピー時に追記されてしまう ctx 1回目 2回目 次の受信

    空き 受信データ 残りが減る 受信データ 残り容量 根本原因:境界チェックがないためctx内の後続フィールドまで突き抜ける 36
  27. CAN Cshim:エクスプロイトの方針 fp = ctx->function_pointer; • Data にarm thumbコードを配置 •

    *(uint8_t *)0x20003ECF = 1 if (ctx->arm_byte != 0xAA) return error; fp(); • Arm_byte = 1 • Function_pointer がctx.dataを指す 以上を満たすUDS 0x36 CAN フレームを作成して送信 すればよい struct Context { uint32_t offset; uint8_t *gate_pointer; uint8_t data[...] ; ... uint8_t arm_byte; ... void (*function_pointer)(); }; // +0x00 // +0x04 // +0x08 // +0x8A // +0x8C 37