猫の中のサイ-Kopycatエミュレーターでファームウェアを実行する







2月16日の会議0x0A DC7831 DEF CONニジニノヴゴロドで、バイナリコードエミュレーションの基本原則と独自の開発(ハードウェアプラットフォームKopycatのエミュレーター)に関するレポートを発表しました。







この記事では、エミュレーターでのデバイスファームウェアの起動について説明し、デバッガーとの対話を示し、ファームウェアの小さな動的分析を実行します。







背景



昔、遠くの銀河で







数年前、私たちの研究室では、デバイスのファームウェアを調査する必要がありました。 ファームウェアは圧縮され、ブートローダーによって解凍されました。 彼は非常に混乱した方法でこれを行い、メモリ内のデータを数回シフトしました。 はい、そしてファームウェア自体が周辺機器と積極的に対話しました。 そして、これらすべてがMIPSコアにあります。







客観的な理由から、既存のエミュレーターは私たちに合わなかったが、それでもコードを実行したかった。 その後、私たちは独自のエミュレーターを作成することにしました。これにより、最小限になり、メインファームウェアを展開できるようになります。 試しました-判明しました。 メインファームウェアも実行するために周辺機器を追加したらどうなるか考えました。 それほど痛くありませんでした 私たちはもう一度考え、本格的なエミュレータを作ることにしました。







その結果、コンピューティングシステムKopycatのエミュレーターができました









Kopycatを選ぶ理由

言葉の遊びがあります。







  1. copycat (英語、n。[ˈkɒpɪkæt])-copycat、模倣者
  2. (英語、n。[ˈkæt])-猫、猫-プロジェクトの作成者の1人のお気に入りの動物
  3. 文字「K」-Kotlinプログラミング言語から


コピキャット



エミュレーターを作成するときに、絶対に具体的な目標が設定されました。









その結果、実装、バスアーキテクチャ(仮想データバスを介してモジュールが相互に通信する場合)、デバイス記述形式としてJSON、デバッガーと対話するためのプロトコルとしてGDB RSPにKotlinが選択されました。







開発は2年弱前から行われており、積極的に進行中です。 この間、MIPS、x86、V850ES、ARM、PowerPCプロセッサコアが実装されました。







プロジェクトは成長しており、一般の人々に紹介する時が来ました。 プロジェクトの詳細な説明は後で行いますが、ここではKopycatの使用に焦点を当てます。







最も短気な人のために、エミュレータのプロモーション版はここからダウンロードできます







エミュレーターのRhino



以前のSMARTRHINO-2018会議で、リバースエンジニアリングスキルのトレーニング用にテストデバイス「Rhinoceros」が作成されたことを思い出してください。 静的ファームウェア分析のプロセスについては、 この記事で説明しました。







では、「スピーカー」を追加して、エミュレーターでファームウェアを実行してみましょう。







必要なもの:

1)Java 1.8

2)Pythonおよびエミュレーター内でPythonを使用するためのJepモジュール。 Windows用JepモジュールのWHLアセンブリはここからダウンロードできます







Windowsの場合:

1) com0com

2) PuTTY







Linuxの場合:

1)socat







Eclipse、IDA Pro、radare2をGDBクライアントとして使用できます。







どのように機能しますか?



エミュレータでファームウェアを実行するには、実際のデバイスに類似した仮想デバイスを「アセンブル」する必要があります。







実際のデバイス(「rhino」)はブロック図で表示できます。







実デバイス回路






エミュレーターはモジュール構造になっており、最終的な仮想デバイスはJSONファイルに記述できます。







105行のJSON
{ "top": true, // Plugin name should be the same as file name (or full path from library start) "plugin": "rhino", // Directory where plugin places "library": "user", // Plugin parameters (constructor parameters if jar-plugin version) "params": [ { "name": "tty_dbg", "type": "String"}, { "name": "tty_bt", "type": "String"}, { "name": "firmware", "type": "String", "default": "NUL"} ], // Plugin outer ports "ports": [ ], // Plugin internal buses "buses": [ { "name": "mem", "size": "BUS30" }, { "name": "nand", "size": "4" }, { "name": "gpio", "size": "BUS32" } ], // Plugin internal components "modules": [ { "name": "u1_stm32", "plugin": "STM32F042", "library": "mcu", "params": { "firmware:String": "params.firmware" } }, { "name": "usart_debug", "plugin": "UartSerialTerminal", "library": "terminals", "params": { "tty": "params.tty_dbg" } }, { "name": "term_bt", "plugin": "UartSerialTerminal", "library": "terminals", "params": { "tty": "params.tty_bt" } }, { "name": "bluetooth", "plugin": "BT", "library": "mcu" }, { "name": "led_0", "plugin": "LED", "library": "mcu" }, { "name": "led_1", "plugin": "LED", "library": "mcu" }, { "name": "led_2", "plugin": "LED", "library": "mcu" }, { "name": "led_3", "plugin": "LED", "library": "mcu" }, { "name": "led_4", "plugin": "LED", "library": "mcu" }, { "name": "led_5", "plugin": "LED", "library": "mcu" }, { "name": "led_6", "plugin": "LED", "library": "mcu" }, { "name": "led_7", "plugin": "LED", "library": "mcu" }, { "name": "led_8", "plugin": "LED", "library": "mcu" }, { "name": "led_9", "plugin": "LED", "library": "mcu" }, { "name": "led_10", "plugin": "LED", "library": "mcu" }, { "name": "led_11", "plugin": "LED", "library": "mcu" }, { "name": "led_12", "plugin": "LED", "library": "mcu" }, { "name": "led_13", "plugin": "LED", "library": "mcu" }, { "name": "led_14", "plugin": "LED", "library": "mcu" }, { "name": "led_15", "plugin": "LED", "library": "mcu" } ], // Plugin connection between components "connections": [ [ "u1_stm32.ports.usart1_m", "usart_debug.ports.term_s"], [ "u1_stm32.ports.usart1_s", "usart_debug.ports.term_m"], [ "u1_stm32.ports.usart2_m", "bluetooth.ports.usart_m"], [ "u1_stm32.ports.usart2_s", "bluetooth.ports.usart_s"], [ "bluetooth.ports.bt_s", "term_bt.ports.term_m"], [ "bluetooth.ports.bt_m", "term_bt.ports.term_s"], [ "led_0.ports.pin", "u1_stm32.buses.pin_output_a", "0x00"], [ "led_1.ports.pin", "u1_stm32.buses.pin_output_a", "0x01"], [ "led_2.ports.pin", "u1_stm32.buses.pin_output_a", "0x02"], [ "led_3.ports.pin", "u1_stm32.buses.pin_output_a", "0x03"], [ "led_4.ports.pin", "u1_stm32.buses.pin_output_a", "0x04"], [ "led_5.ports.pin", "u1_stm32.buses.pin_output_a", "0x05"], [ "led_6.ports.pin", "u1_stm32.buses.pin_output_a", "0x06"], [ "led_7.ports.pin", "u1_stm32.buses.pin_output_a", "0x07"], [ "led_8.ports.pin", "u1_stm32.buses.pin_output_a", "0x08"], [ "led_9.ports.pin", "u1_stm32.buses.pin_output_a", "0x09"], [ "led_10.ports.pin", "u1_stm32.buses.pin_output_a", "0x0A"], [ "led_11.ports.pin", "u1_stm32.buses.pin_output_a", "0x0B"], [ "led_12.ports.pin", "u1_stm32.buses.pin_output_a", "0x0C"], [ "led_13.ports.pin", "u1_stm32.buses.pin_output_a", "0x0D"], [ "led_14.ports.pin", "u1_stm32.buses.pin_output_a", "0x0E"], [ "led_15.ports.pin", "u1_stm32.buses.pin_output_a", "0x0F"] ] }
      
      





paramsセクションのファームウェアパラメーターに注意してください。これは、ファームウェアとして仮想デバイスにダウンロードできるファイルの名前です。







仮想デバイスとメインオペレーティングシステムとの相互作用は、次のように表すことができます。







サーキットエミュレートデバイス






エミュレータの現在のテストインスタンスには、メインOSのCOMポートとの対話が含まれます(デバッグUARTおよびBluetoothモジュールのUART)。 デバイスが接続されている実際のポートまたは仮想COMポート(このために必要なのはcom0com / socatだけです







現在、外部からエミュレータと対話する主な方法は2つあります。









仮想COMポート



ターミナルを介してローカルマシン上の仮想デバイスのUARTと対話するには、接続された仮想COMポートをいくつか作成する必要があります。 この場合、1つのポートはエミュレーターを使用し、2番目のポートはターミナルプログラム(PuTTYまたは画面)を使用します。







仮想COMポート






com0comを使用する



仮想COMポートは、com0comキットのセットアップユーティリティで構成されます(コンソールバージョンはC:\ Program Files(x86)\ com0com \setup.exe、またはGUIバージョンはC:\ Program Files(x86)\ com0com \ setupg.exe ) :







仮想COMポートを構成する






作成されたすべての仮想ポートのバッファーオーバーランを有効にするチェックボックスオンにします。そうしないと、エミュレーターがCOMポートからの応答を待機します。







socatを使用する



UNIXシステムでは、エミュレータによってsocatユーティリティを使用して仮想COMポートが自動的に作成されます。このため、エミュレータの起動時にポート名にsocat:



プレフィックスを指定するだけで十分です。







内部コマンドラインインターフェイス(ArgparseまたはPython)



Kopycatはコンソールアプリケーションであるため、エミュレーターはコマンドラインインターフェイスにオブジェクトと変数と対話するための2つのオプションを提供します:ArgparseとPython。







ArgparseはKopycatに組み込まれたCLIであり、誰でもいつでも利用できます。







別のCLIはPythonインタープリターです。 これを使用するには、Jep Pythonモジュールをインストールし、Pythonで動作するようにエミュレーターを構成する必要があります(ユーザーのメインシステムにインストールされているPythonインタープリターが使用されます)。







Python Jepモジュールのインストール



Linuxでは、Jepはpipを介してインストールできます。







 pip install jep
      
      





JepをWindowsにインストールするには、最初にWindows SDKと対応するMicrosoft Visual Studioをインストールする必要があります。 タスクを少し簡略化し、現在のバージョンのPython for Windows JEPのWHLアセンブリを作成したため、モジュールをファイルからインストールできます。







 pip install jep-3.8.2-cp27-cp27m-win_amd64.whl
      
      





Jepのインストールを確認するには、コマンドラインを実行する必要があります。







 python -c "import jep"
      
      





応答として、メッセージを受信する必要があります。







 ImportError: Jep is not supported in standalone Python, it must be embedded in Java.
      
      





システムのエミュレータバッチファイル(Windowsの場合はkopycat.bat 、Linuxの場合はkopycat )で、追加のパラメーターDjava.library.path



DEFAULT_JVM_OPTS



パラメーターのリストに追加します。インストール済みのJepモジュールへのパスが含まれている必要があります。







その結果、Windowsの場合、次のような行を取得する必要があります。







 set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep"
      
      





Kopycatの起動



エミュレーターはコンソールJVMアプリケーションです。 起動は、オペレーティングシステムのコマンドラインスクリプト(sh / cmd)を介して実行されます。







Windowsで実行するコマンド:







 bin\kopycat -g 23946 -n rhino -l user -y library -p firmware=firmware\rhino_pass.bin,tty_dbg=COM26,tty_bt=COM28
      
      





socatユーティリティを使用してLinuxで実行するコマンド:







 ./bin/kopycat -g 23946 -n rhino -l user -y library -p firmware=./firmware/rhino_pass.bin,tty_dbg=socat:./COM26,tty_bt=socat:./COM28
      
      







結果はPython >



(またはArgparse >



Argparse >









 18:07:59 INFO [eFactoryBuilder.create ]: Module top successfully created as top 18:07:59 INFO [ Module.initializeAndRes]: Setup core to top.u1_stm32.cortexm0.arm for top 18:07:59 INFO [ Module.initializeAndRes]: Setup debugger to top.u1_stm32.dbg for top 18:07:59 WARN [ Module.initializeAndRes]: Tracer wasn't found in top... 18:07:59 INFO [ Module.initializeAndRes]: Initializing ports and buses... 18:07:59 WARN [ Module.initializePortsA]: ATTENTION: Some ports has warning use printModulesPortsWarnings to see it... 18:07:59 FINE [ ARMv6CPU.reset ]: Set entry point address to 08006A75 18:07:59 INFO [ Module.initializeAndRes]: Module top is successfully initialized and reset as a top cell! 18:07:59 INFO [ Kopycat.open ]: Starting virtualization of board top[rhino] with arm[ARMv6Core] 18:07:59 INFO [ GDBServer.debuggerModule ]: Set new debugger module top.u1_stm32.dbg for GDB_SERVER(port=23946,alive=true) Python >
      
      





IDA Proとの相互作用



テストを簡素化するために、IDAでの分析のソースファイルとして、RhinoファームウェアをELFファイルとして使用します (メタ情報はそこに保存されます)。







メタ情報なしでメインファームウェアを使用することもできます。







IDA ProでKopycatを起動した後、[デバッガ]メニューの[デバッガーの切り替え... ]項目に移動し、[ リモートGDBデバッガー]を選択します。 次に、接続を構成します: デバッガーメニュー-プロセスオプション...







値を設定します。









GDBサーバーへの接続の構成






デバッグ開始ボタンが利用可能になりました(F9キー):













クリックしてください-エミュレーターのデバッガーモジュールに接続します。 IDAはデバッグモードになり、追加のウィンドウが使用可能になります。レジスタに関する情報、スタックに関する情報です。







これで、デバッガーを操作するすべての標準機能を使用できます。









デバッガに接続することは、ファームウェアコードを開始することを意味しません。 現在の実行位置は、アドレス0x08006A74



- Reset_Handler関数の開始位置でなければなりません。 下のリストを下にスクロールすると、 メイン関数の呼び出しを確認できます。 この行(アドレス0x08006ABE



)にカーソルを置き、 カーソル操作まで実行 (F4キー)を実行できます







画像







次に、F7を押してメイン機能を開始できます。







プロセス継続コマンド(F9キー)を実行すると、「お待ちください」ウィンドウが表示され、 一時停止ボタンが1つ表示されます。













Suspendを押すと、ファームウェアコードの実行が中断され、中断されたコードの同じアドレスから続行できます。







コードの実行を続行すると、仮想COMポートに接続された端末で次の行が表示されます。







画像







画像







文字列「state bypass」の存在は、仮想BluetoothモジュールがユーザーのCOMポートからデータを受信するモードに切り替わったことを示します。







Bluetoothターミナル(図-COM29)では、Rhinoプロトコルに従ってコマンドを入力できます。 たとえば、文字列「mur-mur」は、Bluetooth端末の「MEOW」コマンドに戻ります。











完全にエミュレートしない



エミュレータを構築するとき、デバイスの詳細度/エミュレーションを選択できます。 そのため、たとえば、Bluetoothモジュールはさまざまな方法でエミュレートできます。









エミュレータの現在のバージョンでは、2番目のアプローチが使用されます。仮想Bluetoothモジュールは構成を実行し、その後、メインシステムのCOMポートからエミュレータUARTポートにデータの「プロキシ」モードに切り替えます。













周辺の一部が実装されていない場合、コードの単純なインストルメンテーションの可能性を考慮してください。 たとえば、DMAでのデータ転送を制御するタイマーが作成されていない場合(検証は0x08006840にあるws2812b_wait関数で実行され0x08006840



)、ファームウェアは常に0x200004C4



にある0x200004C4



がDMAデータラインをリセットするのを0x200004C4



ます:









これを回避するには、 ビジーフラグを設定後すぐに手動でリセットします。 IDA Proでは、Python関数を作成し、ブレークポイントで呼び出すことができます。ブレークポイント自体は、 busyフラグに値1を書き込んだ後にコードに設定されます。







ブレークポイントハンドラー



最初に、IDAでPython関数を作成します。 [ファイル ]メニュー-スクリプトコマンド...







左側のリストに新しいスニペットを追加し、名前(たとえば、 BPT )を付けて、

右側のテキストボックスに機能コードを入力します。







 def skip_dma(): print "Skipping wait ws2812..." value = Byte(0x200004C4) if value == 1: PatchDbgByte(0x200004C4, 0) return False
      
      











その後、[ 実行 ]をクリックして、スクリプトウィンドウを閉じます。







0x0800688A



のコードに0x0800688A



、ブレークポイント(F2キー)を設定し、編集( ブレークポイントの編集...コンテキストメニュー)し、スクリプトタイプの設定を忘れないでください0x0800688A





















busyフラグの現在の値が1の場合、 skip_dma関数はスクリプト行で実行する必要があります。









ファームウェアを実行する場合、ブレークポイントハンドラーコードは、IDAの出力ウィンドウのSkipping wait ws2812...



行に表示されSkipping wait ws2812...



これで、ファームウェアはビジーフラグのリセットを待たなくなります。







エミュレーターの相互作用



エミュレーションのためのエミュレーションは、喜びと喜びをもたらすことはほとんどありません。 エミュレータが、研究者がメモリ内のデータを確認したり、フローの相互作用を確立したりするのに役立つ場合は、はるかに興味深いです。







RTOSタスクの相互作用を動的に確立する方法を示します。 まず、実行中のコードの実行を一時停止します。 「LED」コマンド処理ブランチ(アドレス0x080057B8



)でbluetooth_task_entry関数に切り替えると、最初に作成されたものを確認でき、メッセージがledControlQueueHandleシステムキューに送信されます。







画像







0x20000624にあるledControlQueueHandle変数にアクセスするようにブレークポイントを設定し、コードの実行を続行する必要があります。













その結果、 osMailAlloc関数を呼び出す前にアドレス0x080057CA



停止し、 osMailPut関数を呼び出す前にアドレス0x080057CA



停止し、次にleds_task_entry関数(LEDタスク)に属するアドレス0x08005BD4



osMailGet関数を呼び出す前)で停止します。タスクの切り替えが発生し、コントロールがLEDタスクを受信しました。







画像







このような簡単な方法で、RTOSタスクが相互にやり取りする方法を確立できます。







もちろん、実際には、タスクの相互作用はより複雑になる可能性がありますが、エミュレータを使用してこの相互作用を追跡することはそれほど難しくありません。







ここでは、エミュレーターの起動とIDA Proとの対話の短いビデオを見ることができます。







Radare2で起動



Radare2などの汎用ツールは無視できません。







r2を使用してエミュレータに接続するには、コマンドは次のようになります。







 radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elf
      
      





開始( dc



)および実行の一時停止(Ctrl + C)が利用可能になりました。







残念ながら、現在r2では、ハードウェアgdbサーバーとメモリマークアップで作業するときに問題があります。これにより、ブレークポイントとステップ( ds



コマンド)が機能しません。 これが近い将来修正されることを願っています。







Eclipseで起動する



エミュレータを使用するためのオプションの1つは、開発中のデバイスのファームウェアのデバッグです。 明確にするために、Rhinoファームウェアも使用します。 ここからファームウェアソースをダウンロードできます。







STM32スイートSystem Workbenchの EclipseをIDEとして使用します。







Eclipseで直接アセンブルされたファームウェアをエミュレーターにロードするには、 firmware=null



起動コマンドにfirmware=null



パラメーターを追加する必要がありfirmware=null









 bin\kopycat -g 23946 -n rhino -l user -y library -p firmware=null,tty_dbg=COM26,tty_bt=COM28
      
      





デバッグ構成



Eclipseで、[ Run-Debug Configurations ... ]メニューを選択します開いたウィンドウの[ GDB Hardware Debugging]セクションで、新しい構成を追加し、[Main]タブでデバッグする現在のプロジェクトとアプリケーションを指定する必要があります。









[デバッガ]タブで、GDBコマンドを指定する必要があります。

${openstm32_compiler_path}\arm-none-eabi-gdb









また、GDBサーバーに接続するためのパラメーター(ホストとポート)も入力します。









[スタートアップ]タブで次のパラメーターを指定する必要があります。









Eclipseからファームウェアファイルをダウンロードしない場合は、 Load imageおよびRun commandsパラメーターを指定する必要がないことに注意しください









[デバッグ]をクリックすると、デバッグモードで作業できます。









ご注意 Eclipseには、うーん...いくつかの機能があります...そしてあなたはそれらと一緒に暮らさなければなりません。 たとえば、デバッガーの起動時に「No source available for」0x0「」というメッセージが表示された場合は、Stepコマンド(F5)を実行します













結論の代わりに



ネイティブコードのエミュレーションは非常に興味深いものです。 デバイス開発者にとって、実際のデバイスなしでファームウェアをデバッグすることが可能になります。 研究者向け-動的コード分析を実行する機能。これは、デバイスを使用しても常に可能とは限りません。







便利で適度にシンプルで、設定と起動に多くの時間と労力をかけなかったツールを専門家に提供したいと考えています。







ハードウェアエミュレータを使用した経験についてのコメントを書いてください。 私たちはあなたを議論に招待し、質問に喜んでお答えします。








All Articles