0x00 TP-Link wr886nv7-V1.1.0 路由器分析 – 韌體初步分析
作者:小黑豬(朱文哲)
嵌入式/TP-Link/WR886_V7
韌體升級包
wr886nv7%203.bin
韌體提取
系統韌體通常會以一定的格式封裝在韌體升級包中。為了提取系統韌體可以先使用Binwalk對wr886nv7.bin進行初步分析。
從上圖可以看到,除了一個比較明顯的uImage header頭以外這個升級包中還有一大堆的LZMA資料資訊。通過使用binwalk或直接使用dd命令能夠對升級包中的uimage映象進行提取。
count長度應該是header + lzma包的長度
dd if=wr886nv7.bin of=uboot 0x366C.img bs=1 skip=13932 count=20852
使用binwalk繼續分析uboot的映象,可以發現這個uboot映象的Data部分使用了LZMA進行壓縮。
解壓uboot的LZMA資料
dd if=uboot new.img of=uboot.lzma bs=1 skip=64
由於uboot image中缺乏符號表且大小隻有52K不到,因此此處暫時不對其進行進一步的分析。
VxWorks 系統韌體分析
在wr886nv7.bin中除了uImage外,還有一個2M多的有趣的lzma壓縮資料。
從升級包中提取該0xA200壓縮資料並解壓。
count 是0xc317e - 0xa200
dd if=wr886nv7.bin of=A200.lzma bs=1 skip=41472 count=757630
使用lzma進行解壓
lzma -d A200.lzma
使用Binwalk檢視解壓出的資料後發現這個檔案很可能就是路由器所執行的系統韌體,VxWorks系統版本5.5.1.
韌體載入地址分析
在對0xA200地址之前的一些資料進行分析後可以看到一個疑似uimage header的資料段,其中有兩處地址指向了0x80001000,這個地址也和很多同類路由器裝置的韌體載入地址相同,因此我們可以嘗試使用該地址作為韌體載入地址進行分析。
使用IDA載入韌體進行分析
在使用IDA載入前,首先我們需要確定CPU架構及載入地址。CPU架構資訊已經在升級包中的uimage header中進行了定義,同時我們也可以使用binwalk -Y 命令進行識別。
載入地址的話,在前面的分析中已經初步可以判定為0x80001000,下一步就是使用IDA對韌體進行載入分析了。在IDA中載入VxWorks韌體。
在IDA中填寫韌體載入地址資訊0x80001000。
成功載入韌體後的IDA介面大致如下圖所示,可以看到預設載入的情況下還是有大量的函式。
嘗試修復函式名
通過初步對載入的VxWorks韌體進行分析後發現,這個韌體中並沒有編入符號表,這對後續研究產生了很大的影響。再次對韌體升級包中的內容進行分析後發現,有一個格式為data的檔案很有趣。
使用binwalk -e 預設提取升級包中的檔案。
binwalk -e wr886nv7.bin
進入目錄後查詢,是否存在包含VxWorks關鍵函式名的檔案。
cd _wr886nv6-20180123.bin.extracted
grep -r bzero ./
在對C4FAB的內容進行檢視後發現有驚喜,檔案中儲存的資料很像獨立的符號表,其中找到了大量的VxWorks關鍵函式名。
在對這個檔案進行了初步的分析後發現這個檔案還是有很明顯的特徵的。
根據這個獨立符號檔案的特徵,我們可以編寫對應的ida_python指令碼來對韌體進行修復。
# coding=utf-8 import idc import idaapi import idautils sym_file = open("PATH OF SYM FILE", 'rb').read() table_data = sym_file[0x08:0x9f80] print(table_data[-8:].encode('hex')) string_table = sym_file[0x9f80:] def get_string(offset): string = "" while True: if string_table[offset] != '\x00': string += string_table[offset] offset += 1 else: break return string def get_sym_data(): sym_data = [] for offset in range(0, len(table_data), 8): table = table_data[offset: offset + 8] flag = table[0] # print('flag: %s' % flag) string_offset = int(table[1:4].encode('hex'), 16) # print('string_offset: %s' % string_offset) string = get_string(string_offset) # print('string: %s' % string) target_address = int(table[-4:].encode('hex'), 16) # print('target_address: %s' % hex(target_address)) sym_data.append([flag, string, target_address]) return sym_data def fix_idb(sym_data): for sym in sym_data: flag, string, target_address = sym idc.MakeName(target_address, string) if flag == '\x54': print("Start fix Function %s at %s" % (string, hex(target_address))) idc.MakeCode(target_address) idc.MakeFunction(target_address, idc.BADADDR) # print('flag: %s' % flag) # print('string: %s' % string) # print('target_address: %s' % hex(target_address)) if __name__ == '__main__': sym_data = get_sym_data() fix_idb(sym_data)
指令碼執行後韌體看起來就清晰了很多。
至此,韌體分析的預處理工作就成功完成了,此時韌體中依然還有很多很函式無法被正確被識別,還需要後續的一些操作進行手動修復。基本修復後的IDA介面會像下圖所示, 具體的修復方法將會在後續的文件中進行說明。
根據韌體及裝置分析後修復的segments資訊