使用 IDA 處理 U-Boot 二進位制流檔案
作者:Hcamael@知道創宇404實驗室
時間:2019年11月29日
原文連結: https://paper.seebug.org/1090/
最近在研究IoT裝置的過程中遇到一種情況。一個IoT裝置,官方不提供韌體包,網上也搜不到相關的韌體包,所以我從flash中直接讀取。因為系統是VxWorks,能看到flash佈局,所以能很容易把uboot/firmware從flash中分解出來。對於firmware的部分前一半左右是通過lzma壓縮,後面的一半,是相隔一定的區間有一部分有lzma壓縮資料。而韌體的符號資訊就在這後半部分。因為不知道後半部分是通過什麼格式和前半部分程式碼段一起放入記憶體的,所以對於我逆向產生了一定的阻礙。所以我就想著看看uboot的邏輯,但是uboot不能直接丟入ida中進行分析,所以有了這篇文章,分析uboot格式,如何使用ida分析uboot。
uboot格式
正常的一個uboot格式應該如下所示:
$ binwalk bootimg.bin DECIMAL HEXADECIMAL DESCRIPTION --------------------------------------------------------------------------------13648 0x3550 CRC32 polynomial table, big endian14908 0x3A3C uImage header, header size: 64 bytes, header CRC: 0x25ED0948, created: 2019-12-02 03:39:51, image size: 54680 bytes, Data Address: 0x80010000, Entry Point: 0x80010000, data CRC: 0x3DFB76CD, OS: Linux, CPU: MIPS, image type: Firmware Image, compression type: lzma, image name: "u-boot image"14972 0x3A7C LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 161184 bytes
而這uboot其實還得分為三部分:
1.從0x00 - 0x346C是屬於bootstrap的部分
2.0x346C-0x34AC有0x40位元組的uboot image的頭部資訊
3.從0x34AC到結尾才是uboot image的主體,經過lzma壓縮後的結果
那麼uboot是怎麼生成的呢?Github上隨便找了一個uboot原始碼: https://github.com/OnionIoT/uboot,編譯安裝了一下,檢視uboot的生成過程。
1.第一步,把bootstrap和uboot原始碼使用gcc編譯成兩個ELF程式,得到
bootstrap
和
uboot
2.第二步,使用objcopy把兩個檔案分別轉換成二進位制流檔案。
$ mips-openwrt-linux-uclibc-objcopy --gap-fill=0xff -O binary bootstrap bootstrap.bin $ mips-openwrt-linux-uclibc-objcopy --gap-fill=0xff -O binary uboot uboot.bin $ binwalk u-boot/bootstrap DECIMAL HEXADECIMAL DESCRIPTION --------------------------------------------------------------------------------0 0x0 ELF, 32-bit MSB executable, MIPS, version 1 (SYSV)13776 0x35D0 CRC32 polynomial table, big endian28826 0x709A Unix path: /uboot/u-boot/cpu/mips/start_bootstrap.S $ binwalk u-boot/bootstrap.bin DECIMAL HEXADECIMAL DESCRIPTION --------------------------------------------------------------------------------13648 0x3550 CRC32 polynomial table, big endian $ binwalk u-boot/u-boot DECIMAL HEXADECIMAL DESCRIPTION --------------------------------------------------------------------------------0 0x0 ELF, 32-bit MSB executable, MIPS, version 1 (SYSV)132160 0x20440 U-Boot version string, "U-Boot 1.1.4 (Dec 2 2019, 11:39:50)"132827 0x206DB HTML document header133794 0x20AA2 HTML document footer134619 0x20DDB HTML document header135508 0x21154 HTML document footer135607 0x211B7 HTML document header137363 0x21893 HTML document footer137463 0x218F7 HTML document header138146 0x21BA2 HTML document footer138247 0x21C07 HTML document header139122 0x21F72 HTML document footer139235 0x21FE3 HTML document header139621 0x22165 HTML document footer139632 0x22170 CRC32 polynomial table, big endian179254 0x2BC36 Unix path: /uboot/u-boot/cpu/mips/start.S $ binwalk u-boot/u-boot.bin DECIMAL HEXADECIMAL DESCRIPTION --------------------------------------------------------------------------------132032 0x203C0 U-Boot version string, "U-Boot 1.1.4 (Dec 2 2019, 11:39:50)"132699 0x2065B HTML document header133666 0x20A22 HTML document footer134491 0x20D5B HTML document header135380 0x210D4 HTML document footer135479 0x21137 HTML document header137235 0x21813 HTML document footer137335 0x21877 HTML document header138018 0x21B22 HTML document footer138119 0x21B87 HTML document header138994 0x21EF2 HTML document footer139107 0x21F63 HTML document header139493 0x220E5 HTML document footer139504 0x220F0 CRC32 polynomial table, big endian
3.把u-boot.bin使用lzma演算法壓縮,得到u-boot.bin.lzma
$ binwalk u-boot/u-boot.bin.lzma DECIMAL HEXADECIMAL DESCRIPTION --------------------------------------------------------------------------------0 0x0 LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 161184 bytes
4.使用mkimage,給u-boot.bin.lzma加上0x40位元組的頭部資訊得到u-boot.lzming
$ binwalk u-boot/u-boot.lzimg DECIMAL HEXADECIMAL DESCRIPTION --------------------------------------------------------------------------------0 0x0 uImage header, header size: 64 bytes, header CRC: 0x25ED0948, created: 2019-12-02 03:39:51, image size: 54680 bytes, Data Address: 0x80010000, Entry Point: 0x80010000, data CRC: 0x3DFB76CD, OS: Linux, CPU: MIPS, image type: Firmware Image, compression type: lzma, image name: "u-boot image"64 0x40 LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 161184 bytes
5.最後把
bootstrap.bin
和
u-boot.lzming
合併到一起,然後根據需要uboot的實際大小,比如需要一個128k的uboot,在末尾使用
0xff
補齊到128k大小
使用ida處理bootstrap二進位制流檔案
在上面的結構中,需要注意幾點:
1.
Data Address: 0x80010000, Entry Point: 0x80010000
表示裝置啟動後,會把後續uboot通過lzma解壓出來的資料存入記憶體地址0x80010000,然後把$pc設定為: 0x80010000,所以uboot最開頭4位元組肯定是指令。
2.
uncompressed size: 161184 bytes
,可以使用dd把LZMA資料單獨取出來,然後使用lzma解壓縮,解壓縮後的大小要跟這個欄位一樣。如果還想確認解壓縮的結果有沒有問題,可以使用CRC演算法驗證。
接下來就是通過dd或者其他程式把二進位制流從uboot中分離出來,再丟到ida中。先來看看bootstrap,首先指定相應的CPU型別,比如對於上例,則需要設定MIPS大端。
隨後我們暫時設定一下起始地址為0x80010000,通電以後CPU第一個執行的地址預設情況下我們是不知道的,不同CPU有不同的起始地址。設定如下圖所示:
bootstrap最開頭也指令,所以按C轉換成指令,如下圖所示:
跳轉到0x80010400, 隨後是一段初始化程式碼,下一步我們需要確定程式基地址,因為是mips,所以我們可以根據$gp來判斷基地址。
如上圖所示,因為bootstrap的大小為0x3a3c bytes,所以可以初步估計基地址為
0x9f000000
,所以下面修改一下基地址:
並且修改在
Options -> General -> Analysis -> Processor specific ......
設定
$gp=0x9F0039A0
0x9F0039A0
地址開始屬於got表的範圍,儲存的是函式地址,所以把
0x9F0039A0
地址往後的資料都轉成word:
到此就處理完畢了,後面就是存逆向的工作了,具體bootstrap程式碼都做了什麼,不是本文的重點,所以暫不管。
使用ida處理uboot流檔案
處理bootstrap,我們再看看uboot,和上面的處理思路大致相同。
1.使用dd或其他程式,把uboot資料先分離出來。 2.使用lzma解壓縮 3.丟到ida,設定CPU型別,設定基地址,因為uboot頭部有明確定義基地址為0x80010000,所以不用再自己判斷基地址 4.同樣把第一句設定為指令
正常情況下,uboot都是這種格式,0x80010008為got表指標,也是$gp的值。
5.根據0x80010008的值,去設定$gp 6.處理got表,該地址往後基本都是函式指標和少部分的字串指標。結尾還有uboot命令的結構體。
到此uboot也算基礎處理完了,後續也都是逆向的工作了,也不是本文的關注的內容。
編寫idapython自動處理uboot
拿uboot的處理流程進行舉例,使用Python編寫一個ida外掛,自動處理uboot二進位制流檔案。
1.我們把0x80010000設定為__start函式
idc.add_func(0x80010000)idc.set_name(0x80010000, "__start")
2.0x80010008是got表指標,因為我們處理了0x80010000,所以got表指標地址也被自動翻譯成了程式碼,我們需要改成word格式。
idc.del_items(0x80010008)idc.MakeDword(0x80010008)got_ptr = idc.Dword(0x80010008)idc.set_name(idc.Dword(0x80010008), ".got.ptr")
3.把got表都轉成Word格式,如果是字串指標,在註釋中體現出來
def got(): assert(got_ptr) for address in range(got_ptr, end_addr, 4): value = idc.Dword(address) if value == 0xFFFFFFFF:2019-12-03 15:36:56 星期二 break idc.MakeDword(address) idaapi.autoWait() if idc.Dword(value) != 0xFFFFFFFF: func_name = idc.get_func_name(value) if not idc.get_func_name(value): idc.create_strlit(value, idc.BADADDR) else: funcs.append(func_name)
基本都這裡就ok了,後面還可以加一些.text段資訊,但不是必要的,最後的原始碼如下:
#!/usr/bin/env python# -*- coding=utf-8 -*-import idcimport idaapiclass Anlysis: def __init__(self): self.start_addr = idc.MinEA() self.end_addr = idc.MaxEA() self.funcs = [] def uboot_header(self): idc.add_func(self.start_addr) idc.set_name(self.start_addr, "__start") idc.del_items(self.start_addr + 0x8) idc.MakeDword(self.start_addr + 0x8) self.got_ptr = idc.Dword(self.start_addr+8) idc.set_name(idc.Dword(self.start_addr+8), ".got.ptr") def got(self): assert(self.got_ptr) for address in range(self.got_ptr, self.end_addr, 4): value = idc.Dword(address) if value == 0xFFFFFFFF: break idc.MakeDword(address) idaapi.autoWait() if idc.Dword(value) != 0xFFFFFFFF: func_name = idc.get_func_name(value) if not idc.get_func_name(value): idc.create_strlit(value, idc.BADADDR) else: self.funcs.append(func_name) def get_max_text_addr(self): assert(self.funcs) max_addr = 0 for func_name in self.funcs: addr = idc.get_name_ea_simple(func_name) end_addr = idc.find_func_end(addr) if end_addr > max_addr: max_addr = end_addr if max_addr % 0x10 == 0: self.max_text_addr = max_addr else: self.max_text_addr = max_addr + 0x10 - (max_addr % 0x10) def add_segment(self, start, end, name, type_): segment = idaapi.segment_t() segment.startEA = start segment.endEA = end segment.bitness = 1 idaapi.add_segm_ex(segment, name, type_, idaapi.ADDSEG_SPARSE | idaapi.ADDSEG_OR_DIE) def start(self): # text seg self.uboot_header() self.got() self.get_max_text_addr() self.add_segment(self.start_addr, self.max_text_addr, ".text", "CODE") # end idc.jumpto(self.start_addr)if __name__ == "__main__": print("Hello World")
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912109/viewspace-2666889/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Git處理二進位制檔案Git
- 前端怎麼處理二進位制檔案下載前端
- mysqlbinlog 處理二進位制日誌檔案的工具MySql
- Python使用struct處理二進位制PythonStruct
- MySQL二進位制檔案(binlog)MySql
- 根據介面返回的二進位制流匯出檔案
- 二進位制檔案視覺化(二)視覺化
- 檔案操作(二進位制拷貝)
- 二進位制檔案記憶體對映記憶體
- 二進位制檔案安裝安裝etcd
- 介面返回二進位制檔案的下載。
- 使用資料流的思想處理檔案
- Vue element ui結合java後臺匯出Excel(二進位制檔案流)VueUIJavaExcel
- Python處理十六進位制與二進位制轉換的問題——binascii自帶庫PythonASCII
- IDA批量處理VirusShare樣本獲得asm檔案與bytes檔案ASM
- 檔案輸入輸出處理(二)-位元組流
- C++ 讀取二進位制檔案到char*C++
- 檢視寶塔mysql二進位制檔案 mysqlbinlogMySql
- 6.3建立自己執行的二進位制檔案
- 【Linux】檢視二進位制檔案內容_hexdumpLinux
- golang: 給二進位制檔案增加版本資訊Golang
- .NET Core Web API使用HttpClient提交檔案的二進位制流(multipart/form-data內容型別)WebAPIHTTPclientORM型別
- 二進位制與二進位制運算
- 進位制詳解:二進位制、八進位制和十六進位制
- od 轉儲 二進位制檔案常用命令
- flutter-讀寫二進位制檔案到裝置Flutter
- 如何將日誌檔案和二進位制檔案快速匯入HDFS?
- 二進位制檔案和符號檔案(PDB)如何校驗是否匹配符號
- 文盤Rust -- 如何把配置檔案打包到二進位制檔案裡Rust
- JavaScript 二進位制、八進位制與十六進位制JavaScript
- 如何檢視錶中的二進位制流
- PHP CURL 上傳二進位制流圖片PHP
- 二進位制
- (二進位制)
- 十進位制——二 (八、十六 )進位制
- 二進位制,八進位制,十進位制,十六進位制的相互轉換
- C語言實現檔案複製功能(包括文字檔案和二進位制檔案)C語言
- 判斷檔案為文字檔案還是二進位制檔案(C語言實現)C語言