在本文中,我們將以ANYTONE 878UVII對講機中的固件為例,為大家演示如何對ARM固件映像進行逆向分析。不過,本文中的大部分內容,對於ARM架構來說都是通用的。
本文假設讀者已經熟悉IDA Pro,並且至少分析過一些普通的二進制文件。如果您還不熟悉IDA,只需在網上搜索一下,就能找到許多非常優秀的入門教程,大家可以先通過它們來掌握相關的基礎知識。

就本文來說,我們只需IDA Pro和ANYTONE 878UVII對講機的固件映像就能搞定我們的實驗。並且,所需的映像還可以從分銷商網站下載。實際上,下載哪個版本並不重要,但本文是將以2.04版本為例進行介紹。
在下載的更新包中,我們可以找到FW文件夾,其中包含三個文件:CDI、SPI和CDD文件。其中,CDD是最大的文件,它實際上就是我們要分析的固件映像。
這次我們的運氣不錯,因為這個固件映像並沒有加密,否則,事情就會麻煩一些。它只是內部閃存中的映像,甚至連文件頭都沒有。並且,該文件的元數據被拆分為單獨的文件。所以,我們可以直接在IDA Pro中加載CDD文件。

ANYTONE 878系列對講機使用的是GigaDevice GD32 ARM Cortex-M4微控制器:通過拆開對講機,我們就能看到這些芯片的型號。
除了拆對講機外,實際上還有另一種更方便的方法:查詢FCC。如果您的設備符合FCC的要求,網上應該有關於它的公開信息。這時,我們可以直接在FCC或獨立的數據庫中搜索製造商的信息。大多數情況下,我們會找到一份帶有「內部照片」的文件。這個文件通常能夠提供我們感興趣的信息,比如芯片型號等,這樣,我們就不用拆機了。
重要的是,我們建議大家下載CPU的數據表,並保存起來供後面使用:後面步驟中需要設置的參數,都可以從中找到。

首先要做的是,把CDD拖到打開的IDA Pro窗口中,或者通過文件菜單打開它。IDA會檢測出這是一個二進制文件。然後,將「Processor type」指定為 「ARM little-endian」,具體如下圖所示。
現在,先別按「Ok」按鈕,因為還要對處理器選項進行一些設置。我們知道,這種設備使用的處理器是基於ARMv7E-M架構的。因此,我們必須對處理器選項做相應的修改。最佳設置如下圖所示;為此,需要按下「Processor options」菜單中的「Edit ARM architecture Options」按鈕,這樣就可以找到中間的窗格了。
由於這個項目與Thumb指令集高度相關,所以也建議在「ARM specific options」中勾選「No automatic ARM THUMB switching」選項。雖然這一點並沒有顯示在上面的截圖中,但對本項目來說的確是一個非常有用的設置。

現在,我們已經完成了基本的CPU設置。接下來,我們需要將加載的固件映像重新定位到正確的偏移量處。這個固件映像將被加載到IDA數據庫的ROM部分。由於CPU不會從文件中的0x00處開始加載映像,所以,我們必須重新定位。如果跳過這一步,交叉引用將被破壞,反匯編文件將無法正常工作。我們的目標設備中使用的ARM CPU將要求映像從偏移量0x8004000處開始。這裡其實就是映射到物理ROM的內存位置,所以,我們需要將文件映射到這個地址。
在單擊「Load new file」對話框中的Ok按鈕之後,將會出現如下所示的對話框。通常情況下,RAM的大小和ROM的大小並不需要調整。它們現在已經正確地自動填充好了。
接下來要做的事情,就是創建一個RAM分區。為此,可以勾選「Create RAM section」,分配的RAM將從0x20000000位置開始,長度為0x17FFF。

如果讀者是第一次接觸這方面的內容,通常會有這樣的疑問:這些值是如何確定的?答案很簡單,我們可以從之前下載的數據手冊中找到它們。
從第17頁的內存映射部分,我們可以找到主閃存(固件文件)的加載地址。而在第16頁中,我們可以找到SRAM偏移量和這段內存的長度。
很簡單吧?上面所做的只是將文件/映像重新定位到從我們的數據表中獲取的正確位置。關於主閃存有一個小技巧,第一個0x4000似乎是由引導程序獲取的,所以,我們的二進制文件必須位於0x8004000處。

對於第一次使用IDA的讀者來說,感覺可能非常奇怪:它並沒有像其他軟件一樣進行自動分析,也沒有展示程序代碼,相反,它只是給出了大量的十六進制字符。難道是我們哪裡做錯了嗎?很可能不是。如果您正在使用IDA Pro分析固件映像,這是非常正常的現象。這裡的難點在於,我們必須自己從頭開始進行分析。
但這也沒有想象的那麼難。首先,讓我們考察文件的開頭位置。這是ARM CPU開始執行代碼的地方。在這個偏移量處,一個被稱為向量表的結構被定位,它在ARM Cortex通用用戶指南中有很好的詳細描述。
正如我們在用戶指南的圖形中所看到的,偏移量0x0000(0x08004000)處包含初始堆棧指針。CPU將在這個地址加載接下來的四個字節,並將其用作指向未來堆棧的指針。

接下來的字節是各種處理程序,最重要的是復位處理程序(reset handler)。它正是CPU要啟動或重新啟動時將會跳轉到的地方。
它又是一個4字節的地址,對於我們的映像來說,這個地址很容易解析。正如鏈接的ARM用戶指南文章所告訴我們的,如果地址的最低有效位為1,則處理程序為Thumb。
在我們的例子中,該地址的最後一個字節是0xF9,二進制形式為11111001B。我們可以看到,這裡的最低有效位確實是1。因此,我們需要將復位處理程序的入口點改為Thumb。實際上,復位處理程序的實際偏移量也由於該位的值而移動了一個字節。
0xF9 = 11111001b (with Thumb indicator)
0xF8 = 11111000b (without)
單擊這個偏移量,就會跳轉到復位處理程序的地址減1個字節的地方。現在,請按「Alt+G」,這時會打開一個對話框,我們需要將下面的部分定義為Thumb(CODE16)。
這個項目主要涉及Thumb指令集,因此,您也可以從ROM段的第一個字節開始使用Thumb代碼。請記住,這一點並非適用於所有的ARM項目。但對於這個項目來說,這是沒問題的。
將當前偏移量改為CODE16後,只需按「C」,就能在該偏移量處創建代碼了。現在,我們就應該可以看到復位處理程序的代碼了。

上面介紹的方法雖然能用,但是通過手動方式來創建所有的代碼是相對繁瑣的。別擔心,我們可以藉助於腳本來完成這些任務。實際上,Maddie Stone已經為IDA Pro創建了許多非常方便的腳本,能夠給我們帶來極大的便利。
由於她的腳本不能用於較新的IDA版本(在寫這篇文章時,最新的版本為7.7),所以,我們專門把適用於IDA 7.x版本的腳本上傳到了Github上,讀者可以從https://github.com/alexander-pick/IDAPythonEmbeddedToolkit下載。為了支持基於ARM的項目,我已經對這些代碼做了相應的處理。
首先,我們可以使用腳本define_code_functions.py,在0x08004000到0x080963DC大致範圍內創建代碼。如果腳本詢問是否要撤銷現有的代碼,請選擇No。
IDA Pro應該可以正常工作了,此時的ROM部分應該開始變得更有趣了。
接下來,我們可以使用make_strings.py腳本,在ROM的其餘部分創建字符串。這時,你會在其中發現許多我們感興趣的字符串。

分析這個固件時,我們會發現一個奇怪的現象。由於ANYTONE的開發人員為多國語言創建了固件,所以,他們使用了引用表。因此,這可能導致我們會遺漏某些字符串的引用。之所以會發生這種情況,是因為這些字符串是根據選擇的語言來動態加載的。遺憾的是,基於IDA的靜態分析是無法解決這個問題的。
不過,引導過程中的一些字符串是直接嵌入的,所以它們解析起來問題不大。因此,我們可以從這些字符串開始下手。
為了做進一步的分析,我們需要能夠識別一些基本的OS函數,即操作嵌入字符串的函數,比如「print」或「read」函數等。當然,類似「memcpy」這樣的函數在各種操作系統中都是非常常見的。
非常值得注意的是「print_string」函數(一旦識別出來,我就把它重命名為這個名字)。它接受一些坐標和一個字符串作為參數,並將字符串顯示在屏幕上給定的位置處。這個函數在啟動菜單中被大量使用。
從固件鏡像中的字符串可以識別出設備使用的RTOS(實時操作系統)是μC/OS-II。μC/OS-II是一個用ANSI C編寫的免費實時操作系統。關於該系統的進一步介紹,以及相關文檔,讀者可以在這裡找到;而相關代碼則可以從這裡下載。感興趣的讀者可以參考這些資料,它們應該對您有很大的幫助。

像這樣基於ARM的CPU通常使用特殊的內存區域來處理地址總線、GPIO、I/O或簡單的定時器和時鐘,具體請參閱數據表。實際上,在第14頁中,大家可以找到我們用來指定加載偏移量的內存映射。該映射還包含要添加到數據庫中的特殊內存區域。
打開IDA中的內存區域視圖(segments view)並將它們添加到數據庫中,結果應該與下圖類似。如果你想偷懶,則可以使用這個IDC(https://github.com/alexander-pick/useful-script-and-code/blob/master/GD32F303xx_segments.idc)來完成這個過程。
現在,請重新運行自動分析(Options -> General -> Reanalyse program)以創建交叉引用。
一旦你完成了上面的步驟,就可以查看感興趣的內存區域,看看是否有對它們的交叉引用。這些可以幫助您找到使用特定總線、GPIO或I/O的函數。
如果您查找操作UART的函數,只需檢查UART區域,就會找到對它的引用。這在沒有或只有很少字符串作為引用的情況下是特別有用的。

我認為,到目前為止,您應該已經具備了自己研究這一主題所需的一切。請隨時給我留言。如果您還有什麼問題,儘管問。我總是很高興看到人們分享有趣的發現。
閱讀愉快!
參考及來源:https://do1alx.de/2022/reverse-engineering-radios-arm-binary-images-in-ida-pro/