ラック・セキュリティごった煮ブログ

セキュリティエンジニアがエンジニアの方に向けて、 セキュリティやIT技術に関する情報を発信していくアカウントです。

【お知らせ】2021年5月10日~リニューアルオープン!今後はこちらで新しい記事を公開します。

株式会社ラックのセキュリティエンジニアが、 エンジニアの方向けにセキュリティやIT技術に関する情報を発信するブログです。(編集:株式会社ラック・デジタルペンテスト部)
当ウェブサイトをご利用の際には、こちらの「サイトのご利用条件」をご確認ください。

デジタルペンテスト部提供サービス:ペネトレーションテスト

お家で簡単!?EMFI攻撃を試してみた

デジタルペンテスト部でIoTデバイスペネトレーションテストを担当している飯田です。
今回はお家で簡単にできるEMFI攻撃の実験方法をご紹介します。

EMFI 攻撃とは

聞きなれない方もいるかと思いますので簡単に説明しますと、 EMFI (Electromagnetic Fault Injection) 攻撃 は故障注入攻撃 (Fault Injection Attack) の一種で、攻撃対象となるデバイスに対して EM パルスを照射することで、SRAM セルに代表されるようなフリップフロップを用いた回路に対して誤りを注入する手法です。 これによってプログラムのフローを変更し、セキュリティ機構の回避などが可能となります。

実験

EMFI 攻撃は非常に強力な攻撃手法ですが、一般に高価な実験装置が必要であるため、個人で実験を行うにはハードルが高いと言えるでしょう。
しかし、何とかブログ記事に出来ないかと考えていたところ、千石電商さんで EMFI の実験に最適な圧電素子を見つけたので、これを用いて EMFI 攻撃の実験を試してみることにしました。

用意するもの

実験に必要な物は以下の通りです。
ポリウレタン銅線は家にあった外径0.5mmのものを使用しましたが、必ずしも同じものを用意する必要はありません。

実験装置の製作

圧電素子を用いた高電圧発生部と 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 攻撃の実験方法をご紹介しました。
実験は非常に簡易的なものでしたが、プログラムのフローを変更されうる、非常に恐ろしい攻撃手法であることがわかりますね。 市販製品においては、用途によっては大きな問題となる可能性もあるため、必要に応じて物理アクセスを困難にするような対処が必要かもしれません。