デジタルペンテスト部でIoTデバイスペネトレーションテストを担当している飯田です。
今回はお家で簡単にできるEMFI攻撃の実験方法をご紹介します。
EMFI 攻撃とは
聞きなれない方もいるかと思いますので簡単に説明しますと、 EMFI (Electromagnetic Fault Injection) 攻撃 は故障注入攻撃 (Fault Injection Attack) の一種で、攻撃対象となるデバイスに対して EM パルスを照射することで、SRAM セルに代表されるようなフリップフロップを用いた回路に対して誤りを注入する手法です。 これによってプログラムのフローを変更し、セキュリティ機構の回避などが可能となります。
実験
EMFI 攻撃は非常に強力な攻撃手法ですが、一般に高価な実験装置が必要であるため、個人で実験を行うにはハードルが高いと言えるでしょう。
しかし、何とかブログ記事に出来ないかと考えていたところ、千石電商さんで EMFI の実験に最適な圧電素子を見つけたので、これを用いて EMFI 攻撃の実験を試してみることにしました。
用意するもの
実験に必要な物は以下の通りです。
ポリウレタン銅線は家にあった外径0.5mmのものを使用しましたが、必ずしも同じものを用意する必要はありません。
- Arduino UNO 互換ボード
- 圧電素子 (千石電商さんで購入)
- フェライトロッド (ラジオデパート内の店舗で購入)
- ポリウレタン銅線 外径0.5mm
- SMAコネクタ SMA-J (横向き) C-02569 (秋月電子通商さんで購入)
- SMAコネクタ SMA-P (ネジ止めタイプ) C-01936 (秋月電子通商さんで購入)
- ホットボンド 適量
- 半田ごて
- 金属ヤスリ
- ダイヤモンドカッター
実験装置の製作
圧電素子を用いた高電圧発生部と EMFI プローブを製作します。
今回は EMFI プローブを1つしか作りませんが、様々な巻き数で巻き方向の異なるものを製作し比較実験してみると面白いかもしれません。
高電圧発生部
まず、圧電素子の金属部分を軽くヤスリで擦り、はんだのノリを良くします。
次に、SMA-Jのコネクタの反対側にはんだを盛ります。
最後に、SMA-Jと圧電素子をはんだ付けして完成です。
何だか怪しい見た目をしていますね(?)
EMFI プローブ
まず、フェライトロッドをダイヤモンドカッターで適当な長さに切断します。
次に、ポリウレタン銅線をフェライトロッドに適当な回数巻き、両端をそれぞれSMA-PのピンとGNDに接続します。
最後に、ホットボンドで各部を固定して完成です。
うまくコイルを巻けなくて何度かやり直しましたが、何とか完成しました。
実験用スケッチ
以下に示すスケッチを Arduino IDE (実験では2.0.3を利用) でコンパイルしてボードに書き込みましょう。
このプログラムはリセット時に「R」、変数 cnt が 0 の場合に 「.」、 0以外の場合は「Fault!!!」というメッセージと共に変数 cnt を出力します。
EMFI 攻撃によって通常発生しえない「Fault!!!」が出力されたら成功です。
スケッチを見る限りでは、変数 cnt は for 文を抜けたら常に0になるはずですが、果たして本当にそうでしょうか...?
#define LED 13 volatile unsigned long cnt; void setup() { Serial.begin(115200); pinMode(LED, OUTPUT); digitalWrite(LED, LOW); Serial.print("R"); } void loop() { cnt = 500000; for (unsigned long i = 0; i < 500000; i++) { cnt -= 1; } if (cnt == 0) { Serial.print("."); } else { // ここには到達しないはず...? Serial.println("\nFault!!!"); Serial.print(cnt); digitalWrite(LED, HIGH); delay(1000); } }
このまま実験をしても良いのですが、どのようなプログラムになっているか確認したかったので、Sketch → Export Compiled Binary よりコンパイルされたバイナリを出力してみました。
Arduino IDE には avr-objdump.exe が付属しているので、これを用いて逆アセンブルを行いました。
# スケッチが格納されたディレクトリにいる前提 > C:\Users\<username>\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\bin\avr-objdump.exe -D .\build\arduino.avr.uno\<スケッチ名>.ino.elf
逆アセンブルした結果は以下の通りで、スケッチの変数 cnt は SRAM に保持されていることが確認できます。
そのため、EMFI 攻撃によって SRAM に誤りを注入できれば、通常発生しえない cnt が0以外の場合の処理が実行されそうです。
# スペースの都合上、出力結果を編集しています 00000560 <main>: (省略) 6ba: ldi r24, 0x20 ; 32 定数(500000=0x7a120)をロード r24~r27 6bc: ldi r25, 0xA1 ; 161 6be: ldi r26, 0x07 ; 7 6c0: ldi r27, 0x00 ; 0 6c2: sts 0x0124, r24 ; 0x800124 <__data_end> SRAMに定数をコピー 6c6: sts 0x0125, r25 ; 0x800125 <__data_end+0x1> 6ca: sts 0x0126, r26 ; 0x800126 <__data_end+0x2> 6ce: sts 0x0127, r27 ; 0x800127 <__data_end+0x3> 6d2: lds r20, 0x0124 ; 0x800124 <__data_end> ループ処理開始 SRAMからr20~23にコピー (cnt) 6d6: lds r21, 0x0125 ; 0x800125 <__data_end+0x1> 6da: lds r22, 0x0126 ; 0x800126 <__data_end+0x2> 6de: lds r23, 0x0127 ; 0x800127 <__data_end+0x3> 6e2: subi r20, 0x01 ; 1 cntを減算 6e4: sbc r21, r1 6e6: sbc r22, r1 6e8: sbc r23, r1 6ea: sts 0x0124, r20 ; 0x800124 <__data_end> r20~23をSRAMへコピー (cnt) 6ee: sts 0x0125, r21 ; 0x800125 <__data_end+0x1> 6f2: sts 0x0126, r22 ; 0x800126 <__data_end+0x2> 6f6: sts 0x0127, r23 ; 0x800127 <__data_end+0x3> 6fa: sbiw r24, 0x01 ; 1 6fc: sbc r26, r1 6fe: sbc r27, r1 700: brne .-48 ; 0x6d2 <main+0x172> ループ処理終了判定 702: lds r24, 0x0124 ; 0x800124 <__data_end> SRAMからr24~27にコピー (cnt) 706: lds r25, 0x0125 ; 0x800125 <__data_end+0x1> 70a: lds r26, 0x0126 ; 0x800126 <__data_end+0x2> 70e: lds r27, 0x0127 ; 0x800127 <__data_end+0x3> 712: or r24, r25 714: or r24, r26 716: or r24, r27 718: brne .+28 ; 0x736 <main+0x1d6> cntが0以外なら736へジャンプ (通常は発生し得ない) 71a: ldi r24, 0x14 ; 20 cntが0の場合の処理 71c: ldi r25, 0x01 ; 1 71e: call 0x402 ; 0x402 <_ZN5Print5writeEPKc.part.2.constprop.15> 722: cp r2, r1 724: cpc r3, r1 726: breq .-110 ; 0x6ba <main+0x15a> 728: call 0x2bc ; 0x2bc <_Z17Serial0_availablev> 72c: and r24, r24 72e: breq .-118 ; 0x6ba <main+0x15a> 730: call 0 ; 0x0 <__vectors> 734: rjmp .-124 ; 0x6ba <main+0x15a> 6baへジャンプ 736: ldi r24, 0x16 ; 22 cntが0以外の場合の処理 738: ldi r25, 0x01 ; 1 73a: call 0x402 ; 0x402 <_ZN5Print5writeEPKc.part.2.constprop.15> 73e: ldi r24, 0x20 ; 32 740: ldi r25, 0x01 ; 1 742: call 0x402 ; 0x402 <_ZN5Print5writeEPKc.part.2.constprop.15> (省略)
攻撃
高電圧発生部に EMFI プローブを取り付け、プローブをマイコンに向けて圧電素子をカチカチします。
10分くらい試行錯誤したところ、特定の位置でカチカチすると「Fault!!!」というメッセージが出ることが確認できました。実験成功です。
SRAM に誤りが注入され、変数 cnt の値が書き換わったことで、通常発生しえない処理に飛んだと考えられます。
また、チップ開封を行ったわけではないため推測ですが、「Fault!!!」というメッセージが出たときのプローブ位置は SRAM セルが存在する付近だと想定されます。
終わりに
今回はお家で簡単にできる EMFI 攻撃の実験方法をご紹介しました。
実験は非常に簡易的なものでしたが、プログラムのフローを変更されうる、非常に恐ろしい攻撃手法であることがわかりますね。
市販製品においては、用途によっては大きな問題となる可能性もあるため、必要に応じて物理アクセスを困難にするような対処が必要かもしれません。