作為會議的一部分 0x0A DC7831
在本文中,我們將描述如何在模擬器中運行設備固件,演示與調試器的交互,並對固件執行小型動態分析。
底
很久以前,在一個遙遠的星系
幾年前,我們的實驗室需要研究設備的韌體。 韌體透過引導程式進行壓縮和解壓。 他以一種非常複雜的方式做到了這一點,多次移動記憶體中的資料。 然後韌體本身會主動與外設進行互動。 而這一切都在 MIPS 核心上。
由於客觀原因,可用的模擬器不適合我們,但我們仍然想運行程式碼。 然後我們決定製作我們自己的模擬器,它會做最少的事情並允許我們解壓縮主要韌體。 我們嘗試了一下並且成功了。 我們想,如果我們添加週邊來執行主要韌體會怎麼樣。 這並沒有太痛——而且也很有效。 我們再三考慮,決定製作一個成熟的模擬器。
結果是一個電腦系統模擬器
為什麼是科皮貓?
這是一個文字遊戲。
- 山寨 (英語,名詞 [ˈkɒpɪkæt]) - 模仿者,模仿者
- 貓 (英語,名詞 [ˈkæt]) - 貓,貓 - 該項目創建者之一最喜歡的動物
- 字母「K」來自 Kotlin 程式語言
山寨
在建立模擬器時,設定了非常具體的目標:
- 快速建立新周邊、模組、處理器核心的能力;
- 從各種模組組裝虛擬設備的能力;
- 將任何二進位資料(韌體)載入到虛擬設備記憶體中的能力;
- 使用快照(系統狀態的快照)的能力;
- 透過內建調試器與仿真器互動的能力;
- 很好的現代開發語言。
因此,選擇 Kotlin 來實作、匯流排架構(模組透過虛擬資料匯流排相互通訊)、JSON 作為裝置描述格式、GDB RSP 作為與偵錯器互動的協定。
開發已經進行了兩年多一點,並且正在積極進行中。 在此期間,實作了 MIPS、x86、V850ES、ARM 和 PowerPC 處理器核心。
該項目正在不斷發展,是時候向更廣泛的公眾展示它了。 稍後我們將對項目進行詳細描述,但現在我們將重點放在使用 Kopycat。
對於最不耐煩的人,可以從以下位置下載模擬器的促銷版本
模擬器中的犀牛
讓我們回想一下,早些時候在 SMARTRHINO-2018 會議上,為教授逆向工程技能而創建了一個測試設備「Rhinoceros」。 靜態韌體分析的過程描述於
現在讓我們嘗試添加“揚聲器”並在模擬器中運行韌體。
我們需要:
1)Java 1.8
2)Python和模組
對於Windows:
1)
2)
對於Linux:
1)索卡特
您可以使用 Eclipse、IDA Pro 或 Radare2 作為 GDB 用戶端。
它是如何工作的呢?
為了在模擬器中執行固件,需要「組裝」一個虛擬設備,它是真實設備的模擬。
真實設備(“rhino”)可以在框圖中顯示:
該模擬器具有模組化結構,最終的虛擬設備可以在 JSON 檔案中描述。
JSON 105 行
{
"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 是可以作為韌體載入到虛擬設備中的檔案的名稱。
虛擬設備及其與主作業系統的互動可以用下圖表示:
仿真器目前的測試實例涉及與主作業系統的COM連接埠的交互作用(調試UART和藍牙模組的UART)。 這些可以是設備連接的真實連接埠或虛擬 COM 連接埠(為此,您只需要 com0com/socat).
目前從外部與模擬器互動的方式主要有兩種:
- GDB RSP協定(相應地,支援該協定的工具有Eclipse/IDA/radare2);
- 內部模擬器命令列(Argparse 或 Python)。
虛擬COM連接埠
為了透過終端機與本機電腦上的虛擬裝置的 UART 進行交互,您需要建立一對關聯的虛擬 COM 連接埠。 在我們的例子中,一個連接埠由模擬器使用,第二個連接埠由終端程式(PuTTY 或 screen)使用:
使用com0com
虛擬 COM 連接埠使用 com0com 套件(控制台版本 - C:Program Files (x86)com0comsetupс.exe, 或 GUI 版本 - C:Program Files (x86)com0comsetupg.exe):
勾選複選框 啟用緩衝區溢出 對於所有建立的虛擬端口,否則模擬器將等待 COM 端口的回應。
使用socat
在 UNIX 系統上,虛擬 COM 連接埠由模擬器使用 socat 公用程式自動建立;為此,只需在啟動模擬器時指定連接埠名稱中的前綴即可 socat:
.
內部命令列介面(Argparse 或 Python)
由於 Kopycat 是一個控制台應用程序,因此模擬器提供了兩個命令列介面選項用於與其物件和變數互動:Argparse 和 Python。
Argparse 是 Kopycat 中內建的 CLI,並且始終可供所有人使用。
另一種 CLI 是 Python 解譯器。 要使用它,您需要安裝 Jep Python 模組並配置模擬器以使用 Python(將使用安裝在使用者主系統上的 Python 解釋器)。
安裝 Python 模組 Jep
Linux下Jep可以透過pip安裝:
pip install jep
要在 Windows 上安裝 Jep,必須先安裝 Windows SDK 和對應的 Microsoft Visual Studio。 我們讓您的生活變得更輕鬆
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.
在您系統的模擬器批次檔中(山寨.bat - 對於 Windows, 山寨 - 對於 Linux)到參數列表 DEFAULT_JVM_OPTS
增加一個附加參數 Djava.library.path
— 它必須包含已安裝的 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下執行命令:
binkopycat -g 23946 -n rhino -l user -y library -p firmware=firmwarerhino_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
-g 23646
— 將開放用於存取 GDB 伺服器的 TCP 連接埠;-n rhino
——主系統模組名稱(組裝設備);-l user
— 用於搜尋主模組的函式庫的名稱;-y library
— 搜尋設備中所包含的模組的路徑;firmwarerhino_pass.bin
— 韌體檔案的路徑;- COM26 和 COM28 是虛擬 COM 連接埠。
結果會出現提示 Python >
(或者 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 交互
為了簡化測試,我們使用Rhino韌體作為來源檔案在IDA中進行分析,格式為
您也可以使用沒有元資訊的主韌體。
在 IDA Pro 中啟動 Kopycat 後,在調試器選單中轉到“切換調試器...”並選擇“遠端 GDB 偵錯工具」。 接下來,設定連接:選單 調試器 - 處理選項...
設定值:
- 應用程式 - 任何值
- 主機名稱:127.0.0.1(或執行 Kopycat 的遠端電腦的 IP 位址)
- 端口:23946的
現在調試按鈕可用(F9 鍵):
單擊它可以連接到模擬器中的調試器模組。 IDA 進入偵錯模式,其他視窗變得可用:有關暫存器的資訊、有關堆疊的資訊。
現在我們可以使用調試器的所有標準功能:
- 逐步執行指令(步入 и 跨過去 — 分別為 F7 和 F8 鍵);
- 開始和暫停執行;
- 為程式碼和資料建立斷點(F2 鍵)。
連接到調試器並不意味著運行韌體程式碼。 目前執行位置必須是位址 0x08006A74
— 功能開始 重置處理程序。 如果向下捲動列表,您可以看到函數調用 主。 您可以將遊標放在這一行(位址 0x08006ABE
)並執行操作 運行直到遊標 (F4 鍵)。
接下來可以按F7進入功能 主.
如果您運行命令 繼續處理 (F9 鍵),然後將出現「請稍候」窗口,並帶有一個按鈕 暫停:
當您按下 暫停 韌體程式碼的執行被掛起,並且可以從被中斷的程式碼中的相同位址繼續執行。
如果繼續執行程式碼,您將在連接到虛擬 COM 連接埠的終端機中看到以下行:
「狀態旁路」線的存在表示虛擬藍牙模組已切換到從用戶COM連接埠接收資料的模式。
現在在藍牙終端機(圖中的COM29)就可以依照Rhino協定輸入指令了。 例如,“MEOW”命令將返回字串“mur-mur”給藍牙終端:
不完全模仿我
建立模擬器時,您可以選擇特定設備的細節/模擬等級。 例如,可以透過不同的方式模擬藍牙模組:
- 該設備透過全套命令進行完全模擬;
- 仿真AT指令,從主系統COM埠接收資料流;
- 虛擬設備提供完整的資料重定向到真實設備;
- 作為一個總是返回“OK”的簡單存根。
目前版本的模擬器採用的是第二種方式-虛擬藍牙模組進行配置,配置完成後切換到將資料從主系統的COM口「代理」到模擬器的UART口的模式。
讓我們考慮一下在外圍設備的某些部分未實現的情況下對程式碼進行簡單檢測的可能性。 例如,如果負責控制資料傳輸到 DMA 的定時器尚未建立(檢查在函數中執行) ws2812b_等待位於 0x08006840
),那麼韌體將一直等待標誌位被重置 忙碌位於 0x200004C4
顯示DMA資料線的佔用情況:
我們可以透過手動重置標誌來解決這種情況 忙碌 安裝後立即。 在IDA Pro中,您可以建立一個Python函數並在斷點中呼叫它,然後將值1寫入標誌後將斷點本身放入程式碼中 忙碌.
斷點處理程序
首先,我們在 IDA 中建立一個 Python 函數。 選單 檔案 - 腳本命令...
在左側清單中新增一個片段,為其命名(例如, BPT),
在右側的文字欄位中輸入功能代碼:
def skip_dma():
print "Skipping wait ws2812..."
value = Byte(0x200004C4)
if value == 1:
PatchDbgByte(0x200004C4, 0)
return False
之後我們按下 跑 並關閉腳本視窗。
現在讓我們看看程式碼 0x0800688A
,設定斷點(F2鍵),編輯它(上下文選單 編輯斷點...),不要忘記將腳本類型設為Python:
如果目前標誌值 忙碌 等於1,那麼你應該執行該函數 跳過DMA 在腳本行中:
如果運行韌體來執行,可以在IDA視窗中看到斷點處理程式碼的觸發 產量 按行 Skipping wait ws2812...
。 現在韌體不會等待標誌被重置 忙碌.
與模擬器交互
為了模仿而模仿不太可能帶來喜悅和喜悅。 如果模擬器可以幫助研究人員查看記憶體中的資料或建立線程的交互,那就更有趣了。
我們將向您展示如何動態地建立 RTOS 任務之間的互動。 如果程式碼正在運行,您應該先暫停它的執行。 如果你去函數 藍牙任務條目 到“LED”指令的處理分支(地址 0x080057B8
),然後就可以看到先創建了什麼然後發送到系統隊列 led控制佇列句柄 一些消息。
您應該設定斷點來存取變數 led控制佇列句柄位於 0x20000624
並繼續執行程式碼:
結果,停止將首先發生在地址處 0x080057CA
在呼叫函數之前 osMailAlloc,然後在地址處 0x08005806
在呼叫函數之前 osMailPut,然後過了一會兒 - 到地址 0x08005BD4
(在呼叫函數之前 osMailGet),屬於函數 leds_task_entry (LED 任務),即任務切換,現在 LED 任務會收到控制權。
透過這種簡單的方式,您可以確定 RTOS 任務如何相互互動。
當然,實際上,任務的互動可能更加複雜,但是使用模擬器,追蹤這種互動變得不那麼費力。
使用 Radare2 啟動
您不能忽視像 Radare2 這樣的通用工具。
要使用 r2 連接到模擬器,命令如下所示:
radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elf
現已推出(dc
)並暫停執行(Ctrl+C)。
不幸的是,目前,r2 在使用硬體 gdb 伺服器和記憶體佈局時存在問題;因此,斷點和步驟不起作用(命令 ds
)。 我們希望這個問題能盡快解決。
使用 Eclipse 運行
使用模擬器的選項之一是調試正在開發的設備的韌體。 為了清楚起見,我們還將使用 Rhino 韌體。 您可以下載韌體來源
我們將使用套件中的 Eclipse 作為 IDE
為了讓模擬器載入在Eclipse中直接編譯的韌體,需要加入參數 firmware=null
到模擬器啟動命令:
binkopycat -g 23946 -n rhino -l user -y modules -p firmware=null,tty_dbg=COM26,tty_bt=COM28
設定調試配置
在 Eclipse 中,選擇選單 運行-調試配置... 在打開的窗口中,在部分 GDB硬體偵錯 您需要新增配置,然後在「Main」標籤上指定目前專案和應用程式進行偵錯:
在「偵錯器」標籤上,您需要指定 GDB 命令:
${openstm32_compiler_path}arm-none-eabi-gdb
並輸入連接 GDB 伺服器的參數(主機和連接埠):
在「啟動」標籤上,您必須指定以下參數:
- 啟用複選框 加載圖片 (以便將組裝好的韌體映像載入到模擬器中);
- 啟用複選框 載入符號;
- 新增啟動命令:
set $pc = *0x08000004
(將 PC 暫存器設定為記憶體位址處的值0x08000004
- 地址儲存在那裡 重設處理程序).
請注意,如果你不想從Eclipse下載韌體文件,那麼選項 加載圖片 и 運行命令 無需註明。
單擊“調試”後,您可以在調試器模式下工作:
- 一步步執行程式碼
- 與斷點交互
注意. Eclipse 有,嗯...一些怪癖...你必須忍受它們。 例如,如果啟動偵錯器時出現訊息“No source available for “0x0””,則執行 Step 指令(F5)
取而代之的是結論
模擬本機程式碼是一件非常有趣的事情。 設備開發人員可以在沒有真實設備的情況下調試韌體。 對於研究人員來說,這是進行動態程式碼分析的機會,即使使用設備,這並不總是可行。
我們希望為專家提供一種方便、適度簡單且不需要花費大量精力和時間來設定和運作的工具。
在評論中寫下您使用硬體模擬器的體驗。 我們邀請您進行討論,並很樂意回答問題。
只有註冊用戶才能參與調查。
你用模擬器做什麼?
-
我開發(調試)韌體
-
我正在研究韌體
-
我推出遊戲(Dendi、Sega、PSP)
-
還有什麼(寫在評論裡)
7 位用戶投票。 2 名用戶棄權。
您使用什麼軟體來模擬本機程式碼?
-
QEMU
-
獨角獸引擎
-
變形
-
還有什麼(寫在評論裡)
6 位用戶投票。 2 名用戶棄權。
您希望改進您正在使用的模擬器的哪些方面?
-
我想要速度
-
我想要輕鬆設定/啟動
-
我想要更多與模擬器互動的選項(API、鉤子)
-
我對一切都很滿意
-
還有什麼(寫在評論裡)
8 位用戶投票。 1 位用戶棄權。
來源: www.habr.com