ネイティブコードの実行によるPLCファームウェアの抽出

ネイティブコードの実行によるPLCファームウェアの抽出

本記事では、正規にアップロードされたPLCプログラムを用いて、通常は入手不可能な産業用コントローラのメモリマッピングされたファームウェアイメージを抽出するための、安全な手法について解説します。デバイス独自のプログラミングインターフェースのみを使用し、ハードウェアに一切触れることなく、実行中のファームウェアの完全なコピーを復元することに成功しました。専用の機器も、物理的なアクセスも不要で、必要なのはメンテナンス用のアクセス権限のみです。ベンダーが制御ロジックのアップロード用に提供しているのと同じ通信チャネルだけで、デバイスのメモリを読み取るのに十分でした。

焦点はファームウェアの抽出戦略にある。メモリの分離がない状態でネイティブコードを実行すると、正当な利用と不正アクセスの境界が消失してしまう。

また、この手法の検証を行っている最中に、一見堅牢に見えるファームウェアの保護メカニズム――明らかに暗号化された不透明なバイナリブロブ――を発見しましたが、実際には、わずかな検証で解明できてしまう、ごく薄いカスタムエンコーディングの層に過ぎないことが判明しました。これは、ファームウェアの保護において「難解性」に依存することの限界について、広く知られている事実を改めて裏付けるものです。

ファームウェアが不透明なブロブとして配布されている場合、デバイスの動作を分析するには、実行中のファームウェアイメージの平文コピーが必要となります。多くの環境では、フラッシュメモリへの物理的なアクセスはデータを破壊してしまうか、あるいは現実的ではありません。そこで、この制御されたアプローチでは、正規のプログラムダウンロードおよび実行パスを利用して、フラッシュメモリの内容を管理チャネルを介してストリーミングで送信する、小さな読み取り専用ルーチンを実行します。

この手法は、研究者がベンダーの管理プロトコルを介してコードをアップロード・実行できることを前提としていますが、ハードウェアへの改ざんや侵襲的な調査は一切必要としません。

すべてのコードおよびアセンブリの例は、意図的に無害化されています。その目的は、防御側への情報提供と、ベンダーによる責任ある是正措置を支援することにあります。

要約

解析対象のPLCが入手できた。ベンダーのWebサイトに掲載されていたファームウェアは暗号化されているように見えた。このユニットは、生のPowerPCブロブを実行している。アップロードプロトコルをリバースエンジニアリングし、ブロブのフォーマットを解析した後、小型のフラッシュダンプツールを書き込んでフラッシュメモリをダンプしたところ、意外なことに、ファームウェアは暗号化されておらず、独自の変換処理によって軽くエンコードされているだけだった。

ファームウェアの初公開

デバイスのベンダーのウェブサイトに最新のファームウェアが掲載されていたため、分析を開始するために新しいファームウェアイメージをダウンロードしました。

ファームウェアファイルは、CRLFで行末区切りが設定されたASCIIテキストで、Base64エンコードされているように見えました。そこで、CRLFを取り除き、Base64デコードを試みました。 base64 -d 無効な入力が吐き出されました。ファイルを改めて確認したところ、問題の原因となっている文字、つまり「-「…もしかすると、そのデータはbase64URLエンコードされていたのかもしれない? そこで、Pythonを使ってデコードしてみることにした。」

import base64

def decode_base64(input_file, output_file):
with open(input_file, 'r') as f:
encoded_string = f.read().strip()
padding = len(encoded_string) % 4
if padding != 0:
encoded_string += '=' * (4 - padding)
decoded_data = base64.urlsafe_b64decode(encoded_string)
with open(output_file, 'wb') as f:
f.write(decoded_data)
print(f"Decoded to {output_file}")
input_file = 'firmware'
output_file = 'output.bin'
decode_base64(input_file, output_file)

うまくいった!出力されたのは、読み取れる文字列を含まないバイナリデータだった。Binwalkでは何も抽出できず、エントロピーも高かったため、暗号化されているようだった。

ハードウェア的な方法でフラッシュメモリを消去するのは選択肢にはなりませんでした。ここではどのデバイスにもダメージを与えたくないのですから。まあ、少なくとも現時点では、ですが。

ネットワークプロトコルとプログラム形式の逆転

ベンダーのツールによるPLCプログラムのアップロードをスニッフィングすることで、すでにいくつかのPCAPデータを取得していました。ネットワークプロトコルのリバースエンジニアリングにあたっては、反復的なテストが作業の大部分を担いました。パッシブなパケットキャプチャを分析し、パケットのフレーミングやシーケンスのパターンを解明しました。その過程で、すぐに2つの点が際立っていました:

  • そのベンダーのツールは、生のPPCマシンコードをアップロードしているようでした。VMもバイトコードラッパーもありませんでした。
  • PLCプログラムの出力を送信するための管理用チャネルがあった。

アップロード、コードの実行、およびPLCプログラムの出力を読み取るために必要なプロトコルのごく一部を再実装しました。その後、コンパイル済みのコードを含むパケットの解析を開始しました。

かなりの量の比較テストを行った結果、実行可能ファイルの形式を特定し、どの部分がコード、アドレス、チェックサムであるかを突き止めました。関数の検出は、標準的なPPCプロローグをスキャンすることで行いました。この方法はサンプルに対して確実に機能しました。私たちは、プロローグが検出された箇所を関数の開始点とし、次に検出された箇所を前の関数の終了点として扱うという単純な処理を行いました。 抽出された実行可能イメージは、純粋なPowerPCブロブではありませんでした。これらはイメージヘッダーで始まり、その後にPPCビッグエンディアンコード、メタデータ、シンボル/名前テーブル、そして短い固定フッターが続いていました。

フォーマットを十分に理解できたことで、PLCが受け入れるPPCペイロードを作成できるようになりました。つまり、正当なプログラムのように見えた場合、PLCは私たちのカスタムコードを実行してくれるということです。

ファームウェアの抽出戦略

  1. (ユーザーコードからのレジスタ検査を通じて)メモリマッピングされたフラッシュ領域を特定する。
  2. 必要に応じて、ネイティブPLCプログラムを転送し、その出力を収集するために、最小限のベンダーアップロードプロトコルを再実装してください。
  3. フラッシュを反復処理し、管理チャネルを介して読み取りデータをストリーミングする、コンパクトな読み取り専用ペイロードをアップロードします。
  4. ストリーミングデータを収集します。

フラッシュはどこ?

この手法については、Freescale社のMPC852T PowerQUICC CPUを搭載したPLCと、Spansion社のS29AL032DのようなパラレルNORフラッシュメモリを例に挙げて説明します。このフラッシュメモリは、通常、MPC8xxメモリコントローラを介してメモリマッピングされた外部メモリとして使用されます。

つまり、当社のPLCはフラッシュメモリをMPC852Tの外部メモリバンクのいずれかに接続しており、CPUはボードの設計によって決定される特定の固定アドレスでそれを認識します。

ベースアドレスが判明すれば、標準的なPPCのロード命令を使ってフラッシュメモリの内容を読み出すことができるようになる。その手法とは、フラッシュメモリ全体をダンプするよう仕組まれたPLCプログラムをアップロードして実行するというものだった。

MPC852Tは複数のメモリバンクに対応しています。アドレスは、ボード設計者がどのバンクを使用したか、およびBRx/ORxがどのように設定されたかによって異なります。

BRx(ベースレジスタ x)および ORx(オプションレジスタ x)はメモリコントローラレジスタであり、BRx はバンクのベースアドレスと属性を指定し、ORx はサイズ/マスクおよびタイミングを定義します。これらはメモリマップされた内部レジスタであるため、これらを読み出すには、内部メモリマップレジスタ(IMMR)のベースアドレスを把握した上で、メモリコントローラレジスタ用の適切なオフセットを加算する必要があります。 IMMRは、チップの内部メモリマップレジスタブロックのベースアドレスを定義するために使用されるレジスタです。

では、手順は以下の通りです:

  • IMMRを読む
  • BR0~BR7を点検する
  • 「有効」とマークされ、妥当なベースアドレスとサイズを持つバンクを探す
  • 候補塩基の位置でいくつかの読み取りを行い、検証を行う。
; IMMRを読み込む
mfspr r3, 638

; 下位ビットをマスクして、r3にIMMR_baseを格納する
lis r4, 0xFFFF
and r3, r3, r4

; バンクレジスタを読み込む
lwz r5, 0x100(r3) # BR0
lwz r6, 0x104(r3) # OR0

lwz r7, 0x108(r3) # BR1
lwz r8, 0x10C(r3) # OR1

lwz r9, 0x110(r3) # BR2
lwz r10,0x114(r3) # OR2

私たちは、によって返される値をマスクする必要がありました。 mfspr IMMRは純粋なベースアドレスを保持していないため、この命令では注意が必要です。IMMRは内部モジュールマッピング値を保持しており、オンチップレジスタブロックは64 KiB境界にのみ配置されるため、上位ビットのみがベースアドレスとなります。 メモリコントローラのレジスタは、その内部ブロックからの固定オフセットに位置しています。マニュアルでは、BR0がオフセット0x100、OR0が0x104、BR1がオフセット0x108などと定義されています。

各メモリコントローラのバンクレジスタを確認し、BRx/ORxの各ペアを調べ、フラッシュと一致するバンクを特定する必要がありました。

BRxで確認すべき事項:

  • V = 有効。有効になっていない銀行は無視します。
  • MS = GPCM。並列NOR/EPROM方式のデバイスは、通常、UPM/SDRAM方式のインターフェースではなく、汎用チップセレクトマシン(GPCM)に接続されます。
  • BA = ベースアドレス。これはフラッシュのベースアドレスです。

BRレジスタの値を解析する上で参考になったのは、U-Bootのソースコードにあるmpc8xx.hでした。これは、NXPのマニュアルでビットの番号付け規則を確認するよりも、間違いなく分かりやすかったです。お礼は結構です。

当社のBR0は、すでに0x40000801という、もっともらしい並列NORバンクとまったく同じ外観を呈しており、その結果、以下のようになります:

  • V = 1
  • MS = 0x00000000 = GPCM
  • BA = 0x40000000

フラッシュ・ダンパーの作成

ベースフラッシュアドレスが判明したため、このアドレスからメモリを読み取り、ベンダーツールの管理チャネルを介してフラッシュの内容をストリーミングし、その出力をファイルに保存するための簡単なダンププログラムを作成する必要がありました。

ベンダーのツールを使用して、PLCプログラムのループごとに1024バイトの固定文字列を出力するだけのダミーPLCプログラムをコンパイルしました。 生成された実行イメージに、同じサイズの小さなパッチを適用することに成功しました。つまり、コードセクションのサイズ変更は発生せず、イメージヘッダーのチェックサムのみを再計算すれば済みました。このパッチでは、既存の出力パスを流用しましたが、各反復処理においてベースアドレスから読み込み、1024バイトの出力バッファにデータを格納し、既存の出力メカニズムを通じてデータを返却するようにしました。これにより、PLCプログラムループの各反復ごとに値がインクリメントされる仕組みとなっています。

安全上の制約:

  • 読み取り専用
  • 管理プロトコルに支障をきたさないよう、レート制限および時間制御が施されている

PLCはルーチンを実行し、フラッシュダンプの全データをストリーミングで返した。

ファームウェアの分析

ベンダーのウェブサイトに掲載されていたデータは、不透明でエントロピーが高く、明確なヘッダーが見当たらなかった。ダンプされたファームウェアを分析した結果、実際のデコードルーチンを突き止めた。Base64の文字セットはRFC 4648とは2点で異なっており、文字の順序が逆になっていた。具体的には、a-z=0-25、A-Z=26-51となっており、最後の記号は「-" (0x2d) の代わりに "/" (0x2f)。数字(0~9=52~61)および "+" (62) は標準仕様と同一であった。パディングなし(「=(「」は一度も使用されなかった)。各行は64文字幅で、CRLFで終了していた。

これは、ささやかながらもやりがいのある解明でした。ベンダーは、独自のエンコーディングの下にファームウェアを隠そうとしていたのです。もしその目的が隠蔽にあるのなら、いずれその正体が暴かれることを覚悟すべきでしょう。

技術的な制約

このワークフローは、PLCプログラムがネイティブコードにコンパイルされ、メモリの分離なしに実行されるシナリオに適用可能です。このPLCでは、アプリケーションコードとファームウェアメモリの間に分離がありません。この手法には、実行ファイルのアップロードパスと十分な権限が必要であり、デバイスのアーキテクチャやメモリコントローラの設計に依存します。

結びの言葉

正規のPLCプログラムのアップロードを利用してファームウェアを読み出すことは、研究やトラブルシューティングのためにファームウェアイメージを取得するための、実用的かつ非破壊的な手法です。この手法により、運用リスクを最小限に抑えつつ、再現性のあるセキュリティ分析が可能になります。

アプリケーションロジックとファームウェアメモリの間に強制的な分離が設けられていないことは、危険を伴う可能性があります。PLCプログラムがネイティブコードにコンパイルされ、メモリの分離が強制されない状態で実行される場合、つまりアプリケーションコードとファームウェアメモリが同じ信頼境界を共有している場合、PLCプログラムの欠陥や、それに影響を与える能力によって、基盤となるファームウェアや環境そのものの完全性が損なわれる可能性があります。

資産の所有者や管理者にとって、ここから得られるより広範な教訓は、産業環境においては、エンジニアリング用ワークステーション、管理プロトコル、およびコントローラがどのように相互作用しているかを可視化する必要があるということです。正当なメンテナンス経路を監視し、デバイスの動作を把握し、OT にわたる異常な活動を相互に関連づけることは、従来のITセキュリティの前提が必ずしも当てはまらない環境において、リスクを低減するための不可欠なステップです。