作者:cq674350529
本文首發于安全客,原文鏈接:https://www.anquanke.com/post/id/233361

前言

English version is here, thanks for ecos.wtf team’s translation.

最近在分析Zyxel 某型號設備時,發現該設備的固件無法采用binwalk等工具進行提取。根據binwalk的提示信息,猜測該設備使用的是eCos實時操作系統,其固件是一個單一大文件。由于不知道其加載地址,在使用IDA等工具進行分析時,無法建立正確的交叉引用,直接逆向會比較麻煩。而網上與eCos固件分析相關的資料不多,在沒有相關的芯片文檔或SDK手冊等資料的前提下,從該固件本身出發,通過對固件進行簡單分析,尋找固件中引用的固定地址,最終確定了該固件的加載地址。

binwalk分析

首先使用binwalk工具對固件進行分析,如下。嘗試使用-e選項進行提取時失敗,說明該固件可能就是一個單一大文件。從輸出中可以看到很多與eCos相關的字符串,其中"eCos kernel exception handler, architecture: MIPSEL, exception vector table base address: 0x80000200"指出了該文件的架構(MIPSEL)和異常向量表基地址(0x80000200)。

 binwalk RGS200-12P.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             eCos kernel exception handler, architecture: MIPSEL, exception vector table base address: 0x80000200
128           0x80            eCos kernel exception handler, architecture: MIPSEL, exception vector table base address: 0x80000200
5475588       0x538D04        Unix path: /home/remus/svn/ivs/IVSPL5-ZyXEL_New/src_0603/build/../build/obj/ecos/install/include/cyg/libc/stdlib/atox.inl
5475653       0x538D45        eCos RTOS string reference: "ecos/install/include/cyg/libc/stdlib/atox.inl"
 ...
5945083       0x5AB6FB        eCos RTOS string reference: "ecos_driver_vid_to_if_index!"
5949577       0x5AC889        eCos RTOS string reference: "ecos_driver_inject vid=%u, length=%u"
 ... 
6525239       0x639137        eCos RTOS string reference: "eCos/packages/devs/serial/generic/16x5x/current/src/ser_16x5x.c"
 ...

嘗試使用IDA工具直接加載該文件,設置架構為mipsel、加載地址為0x80000200后,如下。可以看到沒有識別出一個函數,整個segment都是Unexplored狀態,估計是因為加載地址不正確,因此需要想辦法獲取固件的加載地址。

一般,判斷加載地址是否正確的方式包括:1) 成功識別出的函數個數;2)正確的字符串交叉引用個數。

后來發現即使加載基址正確,初始狀態也是這樣,需要在對應的地方手動Make Code才行 。。。可能還需要有合適的loader 進行初始化 ??? 相比而言,Ghidra就可以自動進行分析。

根據相關信息進行查找,文章ecos vector.S 分析II中簡單介紹了eCos異常中斷的初始化及處理等知識,如下,嘗試其中提到的地址0x80000180似乎不對。

# mips cpu 產生exception/interrupt后,cpu會跳到特定的幾個地址上,
# BEV=0時,一般的在0x80000180,當然還有些其他地址,詳細的要去看mips書籍
# 這里有這樣的代碼
FUNC_START(other_vector)
    mfc0    k0,cause            # K0 = exception cause
    nop
    andi    k0,k0,0x7F          # isolate exception code
    la      k1,hal_vsr_table    # address of VSR table
    add     k1,k1,k0            # offset of VSR entry
    lw      k1,0(k1)            # k1 = pointer to VSR
    jr      k1              # go there
    nop                     # (delay slot)
FUNC_END(other_vector)

MLT linker文件mips_tx49.ld中提到了hal_vsr_tablehal_virtual_vector_table等地址,搜索SECTION_rom_vectors (rom,嘗試找到的一些地址后仍然不對。

// MLT linker script for MIPS TX49

/* this version for ROM startup */

    .rom_vectors _vma_ : _lma_ \
    { KEEP (*(.reset_vector)) \
    . = ALIGN(0x200); KEEP (*(.utlb_vector)) \
    . = ALIGN(0x100); . = . + 4; \
    . = ALIGN(0x80); KEEP(*(.other_vector)) \
    . = ALIGN(0x100); KEEP(*(.debug_vector)) } \
    > _region_



// 0-0x200 reserved for vectors
hal_vsr_table = 0x80000200;
hal_virtual_vector_table = 0x80000300;

// search results
// packages/hal/mips/idt79s334a/current/include/pkgconf/mlt_mips_idt32334_refidt334_rom.ldi
SECTION_rom_vectors (rom, 0x80200000, LMA_EQ_VMA)
// ...

bare-metal firmware加載地址分析

一般來說,針對bare-metal firmware,為了確定其加載地址,可以通過查詢對應的芯片文檔或SDK手冊等資料,得到內存空間的映射分布。示例如下,其中Flash memory的范圍為0x08000000~0x0801FFFF

來源: STM32F103C8 memory mapping

此外,對于一些ARM架構的bare-metal firmware,還可以通過中斷向量表來推測加載地址。中斷向量表中的前2項內容分別為Initial SP valueReset,其中Resetreset routine的地址,設備上電/重置時將會從這里開始執行,根據該地址推測可能的加載地址。

In the used cores, an ARM Cortex-M3, the boot process is build around the reset exception. At device boot or reboot the core assumes the vector table at 0x0000.0000. The vector table contains exception routines and the initial value of the stack pointer. On power-on now the microcontroller first loads the initial stack pointer from 0x0000.0000 and then address of the reset vector (0x0000.0004) into the program counter register (R15). The execution continues at this address. (來源)

來源:ARM Cortex-M3 Vector table

在沒有對應的芯片文檔或SDK手冊等資料時,可以嘗試從固件本身出發,通過分析固件中的一些特征來推測可能的加載地址。例如,Magpie通過識別ARM固件中的函數入口表,然后基于函數入口表中的地址去推測可能的加載基址;limkopi.me通過查找指令中引用的固定地址,成功試出了該eCos固件的加載地址。上述方法的本質都是查找固件中存在的固定地址(絕對地址),因為即使加載地址不正確,引用的這些固定地址也不會改變。下面嘗試通過同樣的方法來對Zyxel RGS200-12P設備的固件進行分析。

由于該固件是MIPS架構的,而Magpie的工具是針對ARM架構的,因此并未直接嘗試該工具。

eCos固件加載地址分析

前面使用binwalk工具進行分析時,其輸出結果中包含"eCos kernel exception handler, architecture: MIPSEL, exception vector table base address: 0x80000200"。通過查看binwalkecos對應的magic,如下,表明binwalk在該固件中匹配到一些模式。

# eCos kernel exception handlers
#
# mfc0    $k0, Cause       # Cause of last exception
# nop                      # Some versions of eCos omit the nop
# andi    $k0, 0x7F
# li      $k1, 0xXXXXXXXX
# add     $k1, $k0
# lw      $k1, 0($k1)
# jr      $k1
# nop
0       string      \x00\x68\x1A\x40\x00\x00\x00\x00\x7F\x00\x5A\x33    eCos kernel exception handler, architecture: MIPSEL,
>14     leshort     !0x3C1B                                             {invalid}
>18     leshort     !0x277B                                             {invalid}
>12     uleshort    x                                                   exception vector table base address: 0x%.4X
>16     uleshort    x                                                   \b%.4X

使用IDA工具加載該文件,設置架構為mipsl、加載地址為0x80000000,在最開始處Make Code后,看到了熟悉的eCos kernel exception handler,同時其中包含一個固定地址為0x80000200。由于該固件文件有點大(約10M),僅靠單個地址去猜測加載地址比較費事:(1) 一次完整的分析比較耗時(大概幾分鐘),猜測多個地址的話需要分析好幾次;(2) 手動去確認識別出的函數以及字符串交叉引用是否正確也比較麻煩(可能包含成百上千個函數及字符串交叉引用)。因此還需要查找更多的固定地址以及更有規律的地址,來確定加載地址的區間。

由于對eCos系統不了解,剛開始以為加載地址可能在0x80000000~0x80000200之間 :(,后來發現不對。

ROM:80000000  # Segment type: Pure code
ROM:80000000                 .text # ROM
ROM:80000000                 mfc0    $k0, Cause       # Cause of last exception
ROM:80000004                 nop
ROM:80000008                 andi    $k0, 0x7F
ROM:8000000C                 li      $k1, unk_80000200
ROM:80000014                 add     $k1, $k0
ROM:80000018                 lw      $k1, 0($k1)
ROM:8000001C                 jr      $k1
ROM:80000020                 nop

Hex View窗口中快速瀏覽固件時,發現了一些有規律的內容,如下。其中,存在一些連續的內容(以4字節為單位),其最后2個字節均相同,對應到IDA View窗口中,分別為指向代碼片段的地址和指向字符串的地址。由于此時加載地址不正確,故看到的字符串引用比較奇怪。

當然,文件中還存在一些其他的規律,比如以8字節為單位,以16字節為單位等等。

根據上述規律可以從固件文件中提取出所有的固定地址,一方面可以縮小加載地址所在的范圍,另一方面可以利用這些固定地址去判斷嘗試的加載地址是否正確。Magpie根據代碼片段地址引用處是否是函數的序言指令來判斷加載地址是否正確,由于函數的序言指令需要考慮多種情況,這里采用另一種簡單的方式:根據字符串交叉引用是否正確來進行判斷。

針對該eCos固件,確定其加載地址的方法如下:

(1) 以4字節為單位,判斷鄰近內容的低/高2字節是否相同,提取固件中所有符合規律的固定地址。考慮到大小端差異,在實際比較時以2字節為單位,判斷相鄰淺藍色框(或紅色框)內的內容是否相同。

(2) 提取出所有的固定地址后,先篩掉不合法的地址,然后對剩下的地址進行排序,排序后的結果中的第一個地址為加載地址的上限。同時,排序后的結果中前半部分為指向代碼片段的地址,后半部分為指向字符串的地址。從中選擇一個地址,將指向字符串的地址和指向代碼的地址分開。之后,隨機從字符串地址列表中選取一定數量的地址,作為后續判斷的依據。

模糊的正確,只需要保證分到字符串地址列表中的地址均正確即可,因此可以盡量從列表后半部分取,至于是否有字符串引用地址分到了代碼片段引用地址列表中不重要。

(3) 在確定的加載地址范圍內逐步進行嘗試,同時針對每個嘗試的加載地址,判斷之前選取的每個字符串引用地址指向的字符串是否”正確”,并記錄下正確的個數。對應字符串地址”命中”最多的那個加載地址,很有可能就是正確的加載地址。

判斷字符串引用地址是否正確,可根據該地址是否指向完整字符串的開頭判斷,即對應地址處的前一個字節是否為'\x00'。當然,也存在一些字符串引用地址指向某個完整字符串的中間(“字符串復用”),但大部分的地址還是指向完整字符串的開頭。

根據上述思路,推測出了該eCos固件的加載地址為0x80040000。通過分析部分函數邏輯和字符串交叉引用,驗證該加載地址是正確的。另外,采用該方法對另外幾個eCos固件(包括其他廠商的)進行分析,也可以得出正確的加載地址,說明該方法是可行的。當然,該方法還存在可以改進或優化的地方,不過目前暫時夠用了。

 python find_ecos_load_addr.py
 ...
[+] Top 10 string hit count ...
 load_base: 0x80040000, str_hit_count: 19
 load_base: 0x80019a30, str_hit_count: 11
 load_base: 0x800225a0, str_hit_count: 11
 load_base: 0x80041cd0, str_hit_count: 11
 load_base: 0x800442d0, str_hit_count: 11
 load_base: 0x80019680, str_hit_count: 10
 load_base: 0x80019940, str_hit_count: 10
 load_base: 0x80019af0, str_hit_count: 10
 load_base: 0x80026090, str_hit_count: 10
 load_base: 0x80008b90, str_hit_count: 9
[+] Possible load_base: 0x80040000

binwalk magic添加

設置正確的加載地址后,在對文件進行分析時,在文件頭部發現與VSR table初始化相關的代碼,如下。

.text:80040118                 li      $gp, 0x809A1140
.text:80040120                 li      $a0, 0x8099B7D0
.text:80040128                 move    $sp, $a0
.text:8004012C                 li      $v0, loc_80040224
.text:80040134                 li      $v1, 0x80000200
.text:8004013C                 sw      $v0, 4($v1)
.text:80040140                 sw      $v0, 8($v1)
.text:80040144                 sw      $v0, 0xC($v1)
.text:80040148                 sw      $v0, 0x10($v1)
.text:8004014C                 sw      $v0, 0x14($v1)
.text:80040150                 sw      $v0, 0x18($v1)
.text:80040154                 sw      $v0, 0x1C($v1)
.text:80040158                 sw      $v0, 0x20($v1)
.text:8004015C                 sw      $v0, 0x24($v1)
# ...

參考文章ecos vector.S 分析II中對eCos異常中斷的初始化及處理的介紹,對照上述代碼可知,0x80000200hal_vsr_table的地址,而0x80040224則為__default_exception_vsr的地址。根據前面推測出的加載地址0x80040000,猜測該地址與__default_exception_vsr有關,即根據__default_exception_vsr的地址,考慮地址對齊,可以推測出對應的加載地址。

# mips cpu 產生exception/interrupt后,cpu 會跳到特定的幾個地址上,
# BEV=0時,一般的在0x80000180,當然還有些其他地址,詳細的要去看mips書籍
# 這里有這樣的代碼
FUNC_START(other_vector)
    mfc0    k0,cause            # K0 = exception cause
    nop
    andi    k0,k0,0x7F          # isolate exception code
    la      k1,hal_vsr_table    # address of VSR table
    add     k1,k1,k0            # offset of VSR entry
    lw      k1,0(k1)            # k1 = pointer to VSR
    jr      k1              # go there
    nop                     # (delay slot)
FUNC_END(other_vector)

# 從cause 里取出exception ExcCode,然后到hal_vsr_table 取相應的處理vsr, hal_vsr_table的內容是由 hal_mon_init 填充的

    .macro  hal_mon_init
    la      a0,__default_interrupt_vsr
    la      a1,__default_exception_vsr  # <===
    la      a3,hal_vsr_table        # <===
    sw      a0,0(a3)
    sw      a1,1*4(a3)
    sw      a1,2*4(a3)
    sw      a1,3*4(a3)
    sw      a1,4*4(a3)
    sw      a1,5*4(a3)
    sw      a1,6*4(a3)
    sw      a1,7*4(a3)
    sw      a1,8*4(a3)
    # ...
    .endm
# 這里填充的是__default_interrupt_vsr和__default_exception_vsr,
# ExcCode=0是interrupt,其他的都是exception,就是說產生interrupt會調用__default_interrupt_vsr,產生exception會調用__default_exception_vsr。

根據上述代碼特征,通過在binwalk中添加對應的eCos magic,再次對文件進行分析時即可匹配對應的代碼模式,輸出__default_exception_vsr地址信息,如下,根據該信息即可推測出對應的加載地址。

利用binwalk對另外幾個eCos固件(包括其他廠商的)進行分析,也可以輸出相關的信息,推測出對應的加載地址。

binwalk RGS200-12P.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             eCos kernel exception handler, architecture: MIPSEL, exception vector table base address: 0x80000200
128           0x80            eCos kernel exception handler, architecture: MIPSEL, exception vector table base address: 0x80000200
300           0x12C           eCos vector table initialization handler, architecture: MIPSEL, default exception vector table base address: 0x80040224, hal_vsr_table base address: 0x80000200
 ...

其他

自動分析

使用IDA加載該固件并設置正確的架構、加載地址等參數后,默認情況下IDA不會自動進行分析。相比而言,Ghidra則可以自動進行分析,成功識別出函數并建立字符串的交叉引用。因此,一種方式是對照Ghidra分析的結果,在IDA中進行部分手動Make Code (當然,也可以直接使用Ghidra … );另一種方式是寫一個簡單的eCos loader插件,然后IDA就可以自動進行分析了。

函數名恢復

該單一大文件中不存在導入表及導出表,故無法區分哪些是常見的系統函數,比如memcpy(), strcpy()等。但也有其好處,在代碼中存在很多函數名/日志等信息,根據這些信息可以很容易地對函數名進行恢復。

函數調用約定

對于MIPS32架構的程序,常見的函數調用約定遵循O32 ABI,即$a0-$a3寄存器用于函數參數傳遞,多余的參數通過棧進行傳遞,返回值保存在$v0-$v1寄存器中。而該eCos固件則遵循N32 ABI,最大的不同在于$a0-$a7寄存器用于函數參數傳遞(對應O32 ABI中的$a0-$a3, $t0-$t3)。

IDA中支持更改處理器選項中的ABI模式,但僅修改該參數似乎不起作用。默認情況下"Compiler""Unknown",將其修改為"GNU C++",同時修改ABIn32,之后反編譯代碼中函數參數的顯示就正常了。

小結

本文通過對Zyxel某設備eCos固件進行分析,尋找固件中引用的固定地址,給出了推測固件加載地址的思路,根據該思路成功得到了固件的加載地址。同時,通過對文件進行分析,在文件中發現了與VSR table初始化相關的代碼,根據該代碼可以反推出固件的加載地址,并在binwalk中添加對應的eCos magic來自動匹配該模式。

腳本見 find_ecos_load_addr.py

相關鏈接


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/1608/