STM32固件逆向
概括
用的很久之前做的一個電風扇的課設來做實驗。

用到了外部中斷,定時器中斷,pwm。MCU是STM32F103ZET6。

在keil的設置中可以看到固件的起始地址,默認情況下為0x8000000。另外,keil生成的是hex文件,hex文件是帶有基地址信息的bin文件,如果我們要生成bin文件,需要進行如下設置:

另外,燒錄到mcu中的固件是不帶符號表等調試信息的,即hex和bin文件都不帶調試信息,因為mcu的內存有限,但keil會單獨生成一個帶有調試信息的axf文件,我們可以以這個axf文件為基準,對照著恢復bin文件或者hex文件,下面我們來觀察一下這三個文件的區別:
首先是axf文件


幾乎和源代碼一摸一樣,除了一些外設名用地址代替了。
再看到hex文件:

首先需要將架構修改為arm小端序,再修改一下架構版本,stm32f103zet6是cortex-m3內核的,armv7架構。

修改完后加載hex文件。

IDA會自動識別出基地址,能識別出不少函數了,但偽代碼是真的難看。

基本上是看不出個什么東西來。
最后是bin文件。

bin文件去除了地址信息的hex文件,需要手動設置基地址。
手動設置固件基地址
我們的分析將以bin文件為目標。
將基地址重設為0x8000000,這個地址怎么來的?可以看stm32f103zet6的datasheet。

從0x8000000開始就是flash的空間。
重新設置加載地址之后再將其按照代碼解析就變成了hex文件的樣子。

但此時存在很多標紅的地址,這是因為這些地址在IDA中并沒有設置,因此IDA將其解析為非法地址,標紅了,我們現在要做的就是手動添加一些段。
添加SRAM
段
在axf文件中,IDA解析出了如下幾個段:

單片機內存被總分為flash(rom)和sram(ram),flash里面的數據掉電可保存,sram中的數據掉電就丟失,sram的執行速度要快于flash,flash容量大于sram上方的最低內存地址,最高地址,都是在flash和sram中。
我們正常下載程序都是下載存儲進flash里面,這也是為什么斷電可保存的原因。
單片機的程序存儲分為code(代碼存儲區)、RO-data(只讀數據存儲區)、RW-data(讀寫數據存儲區) 和 ZI-data(零初始化數據區)
Flash 存儲 code和RO-data
Sram 存儲 RW-data 和ZI-data
在datasheet中一樣可以找到sram的起始地址,為0x20000000,結束地址為0x2000ffff,我們自己添加段不必像IDA自動分析的那樣詳細,一個sram的段即可。
也可以在IDA啟動時設置好固件加載地址和sram的開始以及結束地址。
添加了sram段之后,紅色的非法地址消失了,如下:

恢復符號表
下一步,我們需要恢復符號表,由于bin文件不存在調試信息,所以IDA生成的偽代碼幾乎沒眼看,為了方便我們的逆向,我們需要恢復函數名以及導入一些結構體。在這里就需要用到axf文件。
在前面我們看到axf文件幾乎和源代碼是一樣的效果,里面有豐富的調試信息,當然,我們不使用這個課設的axf文件來恢復bin,這樣就太刻意了。
由于我這個課設使用的是庫函數來開發的,所以就不能使用stm32cube生成的hal庫來恢復。具體該怎么恢復?我們采用bindiff來恢復符號表。
如果有閑工夫或者是對stm32的開發非常上手,就可以自己寫一個demo,盡可能多的使用到各種庫函數,然后編譯出一個axf文件。我這里的話,由于好久沒有用stm32了,開發起來有些生疏,所以就不自己手寫了,我選擇撿現成的項目。我學習stm32的時候買的是xx原子家的開發板,他們提供的例程也是非常豐富的,有五十多個,由易到難,涵蓋了stm32開發的方方面面,因此我就直接拿這些例程中的一部分,編譯出axf文件。比如下面這個內存分配的例程:

又比如這個pwm的例程:

可以多選幾個例程,能涵蓋更多的庫函數,將這些axf文件用IDA打開,然后生成idb文件。然后在我們的目標bin文件中,使用bindiff加載idb文件。

選擇一個idb文件,然后會出現這樣一個比較界面:

選取similarity大的函數導入到bin文件中。

導入之后實際上就能恢復大部分的函數名了。

但實際上,由于我這個課設的開發中使用了一些xx原子封裝好的初始化函數(相當于xx原子對原本的庫函數做了二次開發),例如uart_init,delay_init這兩個函數均是二次封裝后的函數,所以恢復出來的函數會比一般情況下多一點。
多使用幾個idb文件進行恢復后,已經可以看了。

五
導入SVD文件,恢復外設結構
但這還不夠,我們還需要導入SVD文件,啥是SVD文件捏?

在IDA7.5以后,就自帶SVD文件加載插件了,如下圖:

打開之后如下:

我們可以自行下載相應的SVD文件,或者加載GitHub上的倉庫,我這里選擇自行下載然后在本地加載。下載鏈接是這個:cmsis-svd
選中想要加載的svd文件之后,IDA就會自動恢復bin文件中的外設結構,體現在偽代碼中就是這樣:

這樣:

當然,也不是所有的外設都能恢復,和axf文件中的肯定是有差別的,但對我們的逆向已經起到了很大的作用了。
到目前位置我們已經做了如下幾步:加載bin文件->設置固件加載基地址->添加sram段->恢復符號表->恢復外設信息。程序還原工作已經接近尾聲,還剩下最后一步,恢復結構體。
恢復結構體
打開某個例程的axf文件,可以查看下其中使用的結構體。

這些結構體中,有TIM_TimeBaseInitTypeDef,NVIC_InitTypeDef,GPIO_InitTypeDef,USART_InitTypeDef,GPIO_TypeDef等是我們經常使用的,用來進行定時器初始化,中斷初始化,IO口初始化等等。
我們生成c header file。

然后在bin文件中解析生成的頭文件:

然后在local type中就能看到不少結構體了。

光導入了結構體還不夠,我們還得手動將IDA的偽代碼中的變量重新設置類型,怎么確定某個變量的類型是什么呢?需要結合庫函數的變量類型來設置。
以GPIO_Init這個函數為例:

我們打開固件庫使用手冊,找到GPIO_Init的原型。

可以看到原型為:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
第二個參數為GPIO_InitTypeDef類型的,因此我們將偽代碼中的v10的類型修改為GPIO_InitTypeDef,剩下的結構體我們也一樣可以使用這樣的方法來恢復,只要有固件庫函數,我們就能夠恢復對應的結構體,效果如下:

效果還不錯。