使用 IDA 處理 U-Boot 二進制流文件

做者:Hcamael@知道創宇404實驗室python

時間:2019年11月29日linux

原文連接:https://paper.seebug.org/1090/git


最近在研究IoT設備的過程當中遇到一種狀況。一個IoT設備,官方不提供固件包,網上也搜不到相關的固件包,因此我從flash中直接讀取。由於系統是VxWorks,能看到flash佈局,因此能很容易把uboot/firmware從flash中分解出來。對於firmware的部分前一半左右是經過lzma壓縮,後面的一半,是相隔必定的區間有一部分有lzma壓縮數據。而固件的符號信息就在這後半部分。由於不知道後半部分是經過什麼格式和前半部分代碼段一塊兒放入內存的,因此對於我逆向產生了必定的阻礙。因此我就想着看看uboot的邏輯,可是uboot不能直接丟入ida中進行分析,因此有了這篇文章,分析uboot格式,如何使用ida分析uboot。github

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其實還得分爲三部分:bootstrap

1.從0x00 - 0x346C是屬於bootstrap的部分 
2.0x346C-0x34AC有0x40字節的uboot image的頭部信息 
3.從0x34AC到結尾纔是uboot image的主體,通過lzma壓縮後的結果api

那麼uboot是怎麼生成的呢?Github上隨便找了一個uboot源碼:https://github.com/OnionIoT/uboot,編譯安裝了一下,查看uboot的生成過程。app

1.第一步,把bootstrap和uboot源碼使用gcc編譯成兩個ELF程序,獲得bootstrapuboot 
2.第二步,使用objcopy把兩個文件分別轉換成二進制流文件。ide

$ 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.binu-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大端。

01.png

隨後咱們暫時設置一下起始地址爲0x80010000,通電之後CPU第一個執行的地址默認狀況下咱們是不知道的,不一樣CPU有不一樣的起始地址。設置以下圖所示:

02.png

bootstrap最開頭也指令,因此按C轉換成指令,以下圖所示:

03.png

跳轉到0x80010400, 隨後是一段初始化代碼,下一步咱們須要肯定程序基地址,由於是mips,因此咱們能夠根據$gp來判斷基地址。

04.png

如上圖所示,由於bootstrap的大小爲0x3a3c bytes,因此能夠初步估計基地址爲0x9f000000,因此下面修改一下基地址:

05.png

而且修改在Options -> General -> Analysis -> Processor specific ......設置$gp=0x9F0039A0

06.png

0x9F0039A0地址開始屬於got表的範圍,存儲的是函數地址,因此把0x9F0039A0地址日後的數據都轉成word:

07.png

到此就處理完畢了,後面就是存逆向的工做了,具體bootstrap代碼都作了什麼,不是本文的重點,因此暫無論。

使用ida處理uboot流文件

處理bootstrap,咱們再看看uboot,和上面的處理思路大體相同。

1.使用dd或其餘程序,把uboot數據先分離出來。 2.使用lzma解壓縮 3.丟到ida,設置CPU類型,設置基地址,由於uboot頭部有明肯定義基地址爲0x80010000,因此不用再本身判斷基地址 4.一樣把第一句設置爲指令

08.png

正常狀況下,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")
相關文章
相關標籤/搜索