Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
NDIAS Automotive IoT CTF問題解説 buried_key
Search
ishii
July 03, 2026
260
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
NDIAS Automotive IoT CTF問題解説 buried_key
ishii
July 03, 2026
More Decks by ishii
See All by ishii
DEFCON 34 CHVCTF Badge問
ishii141
0
200
Japan VCC 2025 問題解説
ishii141
0
250
Deep Dive into RAMN CTF
ishii141
0
790
Featured
See All Featured
We Are The Robots
honzajavorek
0
400
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
5
820
First, design no harm
axbom
PRO
2
1.3k
The Invisible Side of Design
smashingmag
301
52k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.4k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
590
GraphQLとの向き合い方2022年版
quramy
50
15k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
460
Un-Boring Meetings
codingconduct
0
460
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.6k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
750
Transcript
NDIAS Automotive IoT CTF問題解説 Challenge: Buried Key genre: Crypto _ishii
1
Challenge: Buried Key • 問題文 • あるバイナリが AES-128 ベースのホワイトボックス暗号化を行っている。 •
鍵を取り出してフラグを復号せよ。 • 配布ファイル : buried_key • Linux x86-64 ELF binary (statically linked) • ゴール:以下の暗号文を AES-128-ECB(PKCS7 パディング)で復号せよ。 ba720d90a039d801adf22f86d2901b91 8f36fbbfa6459accb1c5315d1bd4cb39 (32バイト=2ブロック) 1. バイナリから鍵を抜く 2. AES-128-ECB 復号(PKCS7) FLAG{…} $ ./buried_key <32 hex chars> 2
初動調査: 暗号化 oracle っぽい • 見える性質: • 32 hex chars
を受け取る • 16 byte を処理する • 16 byte を hex で返す • ここまでは「AESっぽい」 • ただし、まだ鍵もモードも分からない。 $ ./buried_key 00000000000000000000000000000000 88f98d5e605eac3ee60fb40913896498 $ ./buried_key 00112233445566778899aabbccddeeff 0657dd0ddf3394181a952ad847ac6f94 3
decomp / symbol で見る:Whitebox AES っぽい • main関数をデコンパイルすると以下の処理が見える • 以下の表が見える(not
stripped なので): • この時点で立つ仮説: • 普通のAESライブラリ呼び出しではない。 鍵が値として置かれているのではなく、表に焼かれた Whitebox AES では? argv[1] -> 32 hex check -> 16 byte parse -> 9 rounds: TboxTyi / XorTables -> final: Tbox10 / FinalDec -> hex output FinalDec Tbox10 TboxTyi XorTables SR_INV 4
普通のAESとWhiteboxの違い • Whitebox は 攻撃者がバイナリ全体を持ち、メモリを読めても鍵が取り出しにくくする 暗号化コード 鍵 暗号文 鍵が値として存在 →
メモリを見れば抜ける 鍵を埋め込んだ ルックアップテーブル 鍵は表に焼いてあり、 値として存在していない 普通のAES Whitebox AES 暗号文 表引き 5
AESの暗号化処理 • 16バイトの平文を 4×4 の「状態(state)」に並べ、同じ加工を10回くり返す。 ラウンド1 ラウンド 10 State 暗号文
ラウンド2 ... ラウンド9 1. SubBytes : バイトを置換 2. ShiftRows : 位置を動かす 3. MixColumns : 列に行列を掛ける 4. AddRoundKey : 鍵をXOR ラウンド 1~9 1. SubBytes : バイトを置換 2. ShiftRows : 位置を動かす 3. AddRoundKey :鍵をXOR ラウンド 10 6
SubBytes • Sbox を使用してバイトを置換 https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf 35 96 Sboxを引く 1. SubBytes
2. ShiftRows 3. MixColumns 4. AddRoundKey Sbox State State´ 7
ShiftRows • 各行を、左へ 0,1,2,3 バイトずらす https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf 1. SubBytes 2. ShiftRows
3. MixColumns 4. AddRoundKey 8
MixColumns • 各列に固定行列を掛ける (GF(2^8) 上) • GF(2^8) Galoir field •
8ビット係数の多項式として扱って計算する、という意味。整数の掛け算とは別物 1. SubBytes 2. ShiftRows 3. MixColumns 4. AddRoundKey = 04 d4 bf 5d 30 State´ State 0xd4 = 11010100₂ = 𝑥7+ 𝑥6+𝑥4+𝑥2 02*0xd4 = 𝑥(𝑥7+ 𝑥6+𝑥4+𝑥2) = 𝑥8+ 𝑥7+𝑥5+𝑥3 = 𝑥7+ 𝑥5+𝑥4+𝑥+1 = 10110011₂ = 0xb3 0x04 = 02*0xd4 ⊕ 03*0xbf ⊕ 0x5d ⊕ 0x30 9
AddRoundKey • Stateと、その回のラウンドキー(RK)を、同じ位置どうしで XOR する • RKはAESの共通鍵(128bit) k0 から生成されるもの •
k0をラウンド0~10(128bit * 11)のラウンドキーに拡張 04 10 14 1. SubBytes 2. ShiftRows 3. MixColumns 4. AddRoundKey ⊕ State State´ RK 10
AddRoundKey • 共通鍵(128bit) k0 から11個のラウンドキー (16 byte × 11 =
176 byte)を順に作る • 鍵スケジュールという 1. SubBytes 2. ShiftRows 3. MixColumns 4. AddRoundKey k0 = 2b7e1516 28aed2a6 abf71588 09cf4f3c (= w0 w1 w2 w3 = RK0) w4 を作る: RotWord(w3=09cf4f3c) = cf4f3c09 … 1バイト左回転 SubWord(cf4f3c09) = 8a84eb01 … 各バイトを SBox で置換 Rcon[1] = 01000000 … 標準 w4 = w0 ⊕ 8a84eb01 ⊕ Rcon[1] = 2b7e1516 ⊕ 8a84eb01 ⊕ 01000000 = a0fafe17 w5 = w4 ⊕ w1 = 88542cb1 w6 = w5 ⊕ w2 = 23a33939 w7 = w6 ⊕ w3 = 2a6c7605 RK1 = a0fafe17 88542cb1 23a33939 2a6c7605 RK2..10も同様に作成 11 Rcon
普通のAES Whitebox ルックアップテーブルのイメージ • ルックアップテーブルT = SboxにRKを組み込んだもの • T[byte_index][input_byte] =
Sbox[input_byte ^ RK[byte_index]] を事前に計算 State 2b RK 1. SubBytes :Sbox 2. ShiftRows 3. Mixcolumns 4. AddRoundKey 1. SubBytes :Sbox 2. ShiftRows 3. Mixcolumns 4. AddRoundKey T uint8_t T[16][256]; // T[byte_index][input_byte] 例: 左上の要素の場合 T[0][0x01] = Sbox[0x01 ^ 0x2b] = Sbox[0x2a] = 0xe5 12 01 ⊕ e5 State´ Sbox e5 T 0 … 16 255 鍵は表に焼いてあり、 値として存在しない
Whitebox AES への3つの攻撃 攻撃 何をするか 代表的なツール DFA (Differential fault analysis)
表を壊して実行し、暗号文の差分から鍵を復 元する Deadpool[1]、phoenixAES DCA (Differential Computation Analysis) 実行中の中間値を記録し、統計で鍵を当てる Daredevil[1]、Tracer[1] テーブル解析(BGE) 表を分解して鍵を取り出す 自作スクリプト DCAとBGEは符号化があるとが難易度が上がる T[i][x] = Sbox[x ^ RK[i]]だと簡単にRKを戻せるため 符号化という入出力にランダムな全単射を入れることがある [1]https://github.com/sidechannelmarvels ※本問題も符号化が存在する。 問題文:バイナリの入出力には内部変換が含まれるため、バイナリ の出力は通常の AES-128 とは一致しません。 13
DFA 本編 • buried_keyを DFAを使って鍵の復元をする 14
DFA (Differential fault analysis) • AES white-box 実装に fault を入れて、
正常暗号文 C と fault 暗号文 C' の差分 から RK を復元する • DFAではDeadpool、phoenixAESというツールが有用 • CTFの問題を解くだけであれば、数学的な原理は知る必要はあまりない • ただどの表を壊す必要があるのかを理解しておくとよい 暗号化処理 ⊕ 入力 正常暗号文 C fault暗号文 C′ 暗号化処理 入力 fault を入れる: 表を1バイト書き換える 15 GOAL:RK復元
DFA の目標 • 最終ゴール: RK10…を復元 • 最初の一歩: ラウンド9でfaultを入れる ラウンド1 ラウンド
10 暗号文 ラウンド2 ... ラウンド9 基本的にはツールがやってくれる Deadpool: ラウンド9にfaultを入れたバイナリを作成、暗号文を集める phoenixAES: 集めた暗号文からRKを算出する AESの暗号化処理 ※RK10が復元できるとRK9 RK9が復元できるとRK8 … が可能になる faultを入れる 16 入力
Faultを入れると暗号文はどう変わるのか • DFAではラウンド 9 の MixColumnsの入力Stateに fault ε を入れる •
最後の暗号文 C’は4バイトだけ変化する 1. SubBytes 2. ShiftRows 3. Mixcolumns 4. AddRoundKey ラウンド 9 ε × 2ε ε ε 3ε 1. SubBytes 2. ShiftRows 3. AddRoundKey ラウンド 10 2ε ε ε 3ε MixColumns Mixcolumns 後 暗号文C’ ShiftRows 列に拡散 この4バイトだけ変化す るとRKが復元できる 17
Faultを入れる場所を決定する • ラウンド 9 の MixColumnsの入力に fault ε を入れたい •
Buried_keyの処理と Faultを入れる候補のテーブル FinalDec Tbox10 TboxTyi XorTables SR_INV argv[1] -> 32 hex check -> 16 byte parse -> 9 rounds: TboxTyi / XorTables -> 10 round: Tbox10 / FinalDec -> hex output XorTables(uint8)→ 良い 1エントリ = 中間の1バイト 書き換えると state の1バイトだけが狂う =クリーンな単一バイトのfault。 TboxTyi(uint32)→ 悪い 1エントリは MixColumns 後の4バイトの塊 1バイト書き換えると4バイト同時に狂う。 18 2つのテーブルにdeadpool をかけてみればよい
Deadpool + phoenixAESでRKを復元 • Deadpoolはtargetファイルを書き換えて実行し、暗号文を集める • phoenixAESが集めた暗号文からRKを復元する import deadpool_dfa import
phoenixAES engine = deadpool_dfa. Acquisition ( goldendata = golden, # 正常コピー(壊さない基準) targetdata = target, # 毎回壊されるファイル targetbin = "./buried_key", # 起動する実行ファイル dfa = phoenixAES, # 良し悪しの判定 + 最後の鍵計算 # addresses = tables_range, # 壊す範囲を指定 ) tracefiles=engine.run() for tracefile in tracefiles: if phoenixAES.crack(tracefile): print(phoenixAES.crack(tracefile)) # RKを出力 break https://github.com/SideChannelMarvels/Deadpool/blob/master/README_dfa.md 19
Deadpool + phoenixAESでRKを復元 • Deadpoolが暗号文を集める • phoenixAESが集めた暗号文からRKを復元する $ ./buried_key 74657374746573747465737474657374
a0bec87433681b16166a39329ca5581f #暗号文 $ solver.py … (testtesttesttest) Lvl 013 [0x000F9938-0x000F9940[ xor 0x11 74657374746573747465737474657374 -> 85BEC87433681B0E166AD8329CE5581F GoodEncFault Column:0 Logged Lvl 013 [0x000F9938-0x000F9940[ xor 0x5B 74657374746573747465737474657374 -> 00BEC87433681BF4166A7F329C08581F GoodEncFault Column:0 Logged Lvl 013 [0x000F9938-0x000F9940[ xor 0x2F 74657374746573747465737474657374 -> 32BEC87433681B83166AA4329C01581F GoodEncFault Column:0 Logged Lvl 013 [0x000F9938-0x000F9940[ xor 0xF3 74657374746573747465737474657374 -> 41BEC87433681B47166A5A329CB5581F GoodEncFault Column:0 Logged … # Column: 1~3も同様に実施 RK[10] = 24943C23338A6D3A6BD98491F71F370E 0 13 10 7 暗号文 RK[10]が復元出来たら、鍵スケジュールでk0まで一気に復元ができる 20
鍵スケジュールでk0を逆算 • RK[10] が分かればk0 を鍵スケジュールを逆算することが可能。 • 次のRKは、前の鍵を「rotate → Sbox →
Rcon と XOR」で作る。逆向きも同じ式。 • RK[10] = 24943C23338A6D3A6BD98491F71F370E • k0まで逆算すると k0 = E4499AC8E6FED4732F374D316A42B134 21
暗号文を復号する → 失敗 • 暗号文をAES-128-ECB / PKCS7 で復号する。 • 暗号文:
ba720d90a039d801adf22f86d2901b918f36fbbfa6459accb1c5315d1bd4cb39 • 復号してみると明らかにFLAGではない • RK10から標準Rconで鍵スケジュールを逆算したが、標準Rconではない可能性がある。 • → DFAでRK9..RK3まで復元して、Rconを確認してみる。 00000000 c1 42 26 bb a9 c9 4e bc 43 f5 c7 f7 f4 6e 10 48 |.B&...N.C....n.H| 00000010 91 47 59 b7 27 74 a1 71 68 0f b5 85 51 57 70 db |.GY.'t.qh...QWp.| 22
残りのRKをDFAで復元する • RK[10] が分かれば最終ラウンドを計算でほどき、1つ手前に DFA。RK[3] まで8回くり返す。 • AES鍵 k0まではDFAでは復元できない RK[10]
RK[9] … RK[3] RK[2] RK[1] RK[0] =k0 DFA でとれる DFA で取れない engine = deadpool_dfa. Acquisition ( goldendata = golden, # 正常コピー(壊さない基準) targetdata = target, # 毎回壊されるファイル targetbin = "./buried_key", # 起動する実行ファイル dfa = phoenixAES, # 良し悪しの判定 + 最後の鍵計算 # addresses = xortables_range, # 壊す範囲を XorTables に限定 ) tracefiles=engine.run(lastroundkeys=復元済みRK) for tracefile in tracefiles: if phoenixAES.crack(tracefile): print(phoenixAES.crack(tracefile, lastroundkeys=復元済みRK)) break RK3 = fd4e7a78f61c3846850692dbcc1692a7 RK4 = 14012633e21d1e75671b8caeab0d1e09 RK5 = 84732751666e39240175b58aaa78ab83 RK6 = b611cbfdd07ff2d9d10a47537b72ecd0 RK7 = f1dfbbdc21a04905f0aa0e568bd8e286 RK8 = 9e47ffe1bfe7b6e44f4db8b2c4955a34 RK9 = a8f9e7fd171e51195853e9ab9cc6b39f RK10 = 24943c23338a6d3a6bd98491f71f370e 23
Rcon を鍵から逆算する • 既知の RK[3..10] を入れれば Rcon が出る • W
はRKの 4バイト word。 Rcon[r] = ( W[4r] ⊕ W[4r-4] ⊕ SubWord(RotWord(W[4r-1])) ) 既知の RK[3..10] から Rcon[4..10] が出る: Rcon[4..10] = AE 47 8E 07 0E 1C 38 これは xtime(GF(2^8) の ×2)の連鎖。さかのぼると: 53 → A6 → 57 → AE → 47 → 8E → 07 → 0E → 1C → 38 → Rcon[1] = 0x53 (標準は 0x01) RK10 = 24943c23 338a6d3a 6bd98491 f71f370e (= W[40] W[41] W[42] W[43] RK9 = a8f9e7fd 171e5119 5853e9ab 9cc6b39f (= W[36] W[37] W[38] W[39] 24
本物の Rcon で k0 まで戻す • Rcon[1] = 0x53 を初項に、鍵スケジュールを最後まで逆算する。
• W[0..3]、つまり最初の鍵 k0 が出る。 • 暗号文をAES-128-ECB / PKCS7 で復号する。 • 暗号文: ba720d90a039d801adf22f86d2901b918f36fbbfa6459accb1c5315d1bd4cb39 k0 = deadbeefcafebabe1337c0de42424242 FLAG{custom_rcon_is_the_key} 25
まとめ • Buried Key • WhiteboxからAES鍵 k0を復元し、与えられた暗号文をAES-128-ECB / PKCS7 で復号
• DFAによる解法 • 山1:DFA • Deadpool_dfaを使用して、表にfaultを入れ、暗号文の差からRKを剥がす (RK[10] → RK[3]) • 山2:Rcon • RK[10] … RK[3] から、非標準の Rcon(0x53)を復元。 • その後、非標準の Rconを使用して 、鍵スケジュールを逆算し k0を復元 RK10を復号する詳しい導出 26
暗号文からRKを復元する • 正常暗号文 C : • 𝑥について整理する. k=RK10と仮定する • 𝑥と𝑥’の差分を考える
𝐶 = 𝑆 𝑥 ⊕ 𝑅𝐾10 ※ShiftRowsを省略 𝐶’ = 𝑆 𝑥’ ⊕ 𝑅𝐾10 𝐶 = 𝑆 𝑥 ⊕ 𝑅𝐾10 𝐶 ⊕ 𝑘 = 𝑆 𝑥 ⊕ 𝑅𝐾10 ⊕ 𝑘 𝑥 = 𝑆−1 𝐶 ⊕ 𝑘 • Fault暗号文 C’ : 𝑥 ⊕ 𝑥’ = 𝑆−1 𝐶 ⊕ 𝑘 ⊕ 𝑆−1 𝐶’ ⊕ 𝑘 1. SubBytes :Sbox 2. ShiftRows 3. AddRoundKey ラウンド 10 x 暗号文C x’ 暗号文 C’ ⊕ 𝑘する Sboxの逆引き 𝑥 ⊕ 𝑥’がわかれば k を計算できそう 2ε ε ε 3ε 出力: 入力State: 𝑥 ⊕ 𝑥’ には綺麗な差分構造が出る 27
𝑥 ⊕ 𝑥’ がMix 後の差分 𝑚 ⊕ 𝑚’ になる •
𝑥をMix後の値で考える 𝑥 ⊕ 𝑥’ = 𝑚 ⊕ 𝑅𝐾9 ⊕ 𝑚’ ⊕ 𝑅𝐾9 = 𝑚 ⊕ 𝑚’ 3. Mixcolumns 4. AddRoundKey ラウンド 9 m m’ x x’ Mixcolumns 後の差分 𝒎 ⊕ 𝒎’ 𝑥 = 𝑚 ⊕ 𝑅𝐾9, 𝑥’ = 𝑚’ ⊕ 𝑅𝐾9, 𝑥 ⊕ 𝑥’ = 𝑆−1 𝐶 ⊕ 𝑘 ⊕ 𝑆−1 𝐶’ ⊕ 𝑘 差分をとる 𝑚 ⊕ 𝑚’ = 𝑆−1 𝐶 ⊕ 𝑘 ⊕ 𝑆−1 𝐶’ ⊕ 𝑘 ε × 2ε ε ε 3ε MixColumns 列に拡散 28
各鍵候補について差分を計算する • 𝑚 ⊕ 𝑚’ = 𝐷𝑖 (k) とおく •
𝐷𝑖 (k) = 𝑆−1 𝐶𝑖 ⊕ k ⊕ 𝑆−1 𝐶’𝑖 ⊕ k 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 2ε ε ε 3ε Mixcolumns 後の差分 𝒎 ⊕ 𝒎’ for k0 in 0..255: d0 = D0 (k0) ε = d0÷2 // GF(2^8) 上の割り算 εに対して: D13 (k13) == ε になる k13 を探す D10 (k10) == ε になる k10 を探す D7 (k7) == 3ε になる k7 を探す 見つかった組(k0, k13, k10, k7)を候補として残す 𝑘が正しい鍵なら 具体的に考えてみる: 𝑘0 = 0 としてd0を計算 d0 = 0x0e 2ε = 0x0e ε = 0x07 𝑘13 =0…255 を入れてみて成り立つものがあれば、鍵候補 D13 (k) = 𝑆−1 𝐶𝑖 ⊕ k ⊕ 𝑆−1 𝐶’𝑖 ⊕ k = 0x07 もし、存在しなければ 𝑘0 = 0 の仮定が間違い → 𝑘0 = 1 と仮定して再度計算する i=0,13,10,7が 変化しているとき 𝐷0 𝐷13 𝐷10 𝐷7 このパターンが出るkを総当たりする 29
RK10全体を復元する 0 4 8 12 1 5 9 13 2
6 10 14 3 7 11 15 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fault ε を入れる εを入れる場所を変える fault暗号文 C’ RK10 RK10全体を 復元可能 30
補足 • 𝑥について整理する. k=RK10と仮定する • k=RKだと綺麗な差分構造が出る • k=RK10ではないとき 𝐶 =
𝑆 𝑥 ⊕ 𝑅𝐾10 𝐶 ⊕ 𝑘 = 𝑆 𝑥 ⊕ 𝑅𝐾10 ⊕ 𝑘 𝑆−1 𝐶 ⊕ 𝑘 = 𝑥 ⊕ 𝑘する Sboxの逆引き 𝑆−1 𝐶 ⊕ 𝑘 = 𝑆−1(𝑆 𝑥 ⊕ 𝑅𝐾10 ⊕ k) ≠ 𝑥 𝑆−1 𝐶 ⊕ 𝑘 ⊕ 𝑆−1 𝐶’ ⊕ 𝑘 = 𝑥 ⊕ 𝑥’ 2ε ε ε 3ε 𝑆−1 𝐶 ⊕ 𝑘 ⊕ 𝑆−1 𝐶’ ⊕ 𝑘 = 𝑆−1(𝑆 𝑥 ⊕ δ) ⊕ 𝑆−1(𝑆 𝑥’ ⊕ δ) ≠ 𝑥 ⊕ 𝑥’ 綺麗な差分構造が出ない 31