西數硬碟固件調試與逆向分析
一、背景
去年(2015年)為了分析方程式組織那個入侵硬碟固件的組件,在分析西部數據硬碟固件時遇到點問題,偶爾在hddguru上發現的一次討論文,對我的分析起到很大幫助。就將此次討論整理翻譯成文,同時也當作為我個人的學習筆記。現分享出來,感興趣者共學之。
基本思路總結:
1.分析PCB,找到JTAG介面
2.使用調試器連接JTAG調試分析
3.dump Flash,這裡作者的FLASH似乎沒有設置保護,可以直接就dump了。
4.分析FLASH dump文件.有一些意外的東西發現就是flash中提供有可以通過串口進行訪問可以任意讀寫任意地址,這也算是一個硬碟後門啊,邪惡的可以做多少事兒,但是前提是你必須拿到硬碟,修硬碟的可以方便給你植入病毒了。
5.分析bootloader格式和kernel格式。
6.修復flash。
文章是作者一次求助引起的,作者的西數硬碟損壞而引起一次分析探索.求助的問題如下:
Isnthere anyone who have pinouts of 88i6745n JTAG?
or PASS for RAR file: http://www.griol.com/ftp/WD/88I6745.rar ?or a way to rewrite broken ROM in 88i6745n ?or a way to boot from external U12 EEPROMn24p10, 24p20 ?二、硬體分析
看來沒有人願意幫助我,so,我自己找到JTAG引腳。希望有人會感興趣。JTAG引腳是在PCB的CON1上,看起來好像所有板上都是一樣的?!!我測試了2061-701335-c00 Marvell 88i6545 和n2061-701499-e00 Marvell 88i6745n,兩款晶元ID都是0x259663d3
下圖連接到2061-701499-e00
鏈接到JTAG的測試點如下:
鏈接到JTAG CON1如下:
三、使用調試器
OK,我感興趣的是重寫Marvell 88i6745n內部損壞的EEPROM(ROM)。看起來很多人都解決了這個問題,但是他們都不願意分享?難道我進錯論壇了嗎,信息不是應該自由共享嗎?(實際上論壇沒有人解決過此類問題)
Anyway,我不得不自己來解決問題,如果有人願意幫助我會更好。
首先我選擇用OPENOCD(http://openocd.berlios.de/)來調試,然後我必須找到JTAG介面。首先看看OPENOCD支持什麼樣的JTAG介面。我使用Xilinx III cable JTAG在家做了一個。
首先鏈接Marvell 88i6745n PCB
如你之前所看到的,必須使用的引腳如下
GND
TDI
TMS
CLK in
TDO
以下引腳可用可不用,依賴於你自己的JTAG介面而定
Vcc 3.3v -nfor powering JTAG interface or for reference.
CLK out -nfor VERY FAST JTAG interface that support CLK out
RST -nfor JTAG interface that support RST
對於我的Xilinx III JTAG介面來說只需要使用到GND, TDI, TMS, CLK in, TDO, Vcc 3.3
開始:
1. 將"feroceon.cfg"文件從openocdtarget拷貝到openocdbin下
2. 拷貝文件jtag.cfg到openocdbin下。
對於Xilinx III JTAG的介面文件如下:
#*******************************
#daemon configurationtelnet_port 4444gdb_port 3333
#interfaceinterface parportparport_cable dlc5parport_port 0x378#*******************************3. 把putty.exe拷貝到openocdbin下,或者使用telnet.
4. 我使用的PCB板(從HDD盤上卸下來的)的EEPROM(ROM)是已經不可用的。我並不確定是否有必要將PCB設置為測試模式(前3個引腳接到GND)
將JTAG介面連接到PCB。
將SATA電源連接PCB。
5. 進入到openocdbin目錄下輸入:openocd.exen-f jtag.cfg.txt -f feroceon.cfg
如果沒啥問題,你會看到如下:
你可以看到設備ID為0x259663d3,由於OPENOCD沒有匹配的loader,我就使用feroceon.cfg配置loader來載入。如果你那裡發生錯誤,可以去讀OPENOCD的文檔。
6. 現在運行telnet。進入到openocdbin目錄輸入:putty.exe
打開putty後,輸入localhost,埠輸入4444鏈接。如果沒什麼問題的話會出現如下一個窗口:
7.然後輸入halt,你會看到:
到這一步就表示你可以完全控制Marvell 88i6745n了。然後,開始從Marvellnchip中dump bootstrap:
dump_imagenffff0000.bin 0xffff0000 0x10000
沒有意外的話我們可以看到:
四、dump文件分析
在目錄openocdbin可以看到dump文件ffff0000.bin。
目前我的文件SHA1為5ab6b58869a6cf40aaa60626e8440c0abc186ae8,現在你可以用一個ARM反彙編器來分析代碼了。
一些地址推測:
0xffff0000 HWnRESET vector(複位向量, 處理器複位時執行)
0x04000000-0x04007fff internal SRAM for STACK.(棧區)0x00000000 SDRAMn8-32Mb ???0x1c00xxxx ports?(埠映射區?)
0x1c00a6xx serialnport(串口映射區)0x1c00a8xx I/Onport ? (IO映射區?)
通過對dump文件的分析得到了如下函數:
FFFF1A70: start_tiny_console_thumb
FFFF01B4: start_tiny_console_armFFFF1944: send_asciiz_strinFFFF1A16: receive_and_resend_CMDFFFF18E2: send_byteFFFF1A06: receive_byte等等
1.函數start_tiny_console_thumb/start_tiny_console_arm分析
在硬體複位後,CPU測試埠0x1C00A84E(註:這是測試埠的映射地址),如果第13位被設置的話,運行start_tiny_console。
這個函數有3個命令功能 (read,write,jump):
rn<32bit address> ; readnone half word(16 bit) from address
w <32bit address> <16bit data> ; write one half word(16 bit) to addressj <32bit address> ; jump or callncode on address通過這幾個命令功能我們可以在底層對CPU做任何事情,但問題是怎樣設置0x1C00A84E的第13位。埠0x1C00A84E是連接到4個引腳(3.5寸盤是8個引腳)的跳線插上,因此第13位必須連接到某個引腳或者某個跳線插的組合。我先使用JTAG調用它來測試了tiny console函數,先用3.3v的電壓加到板上,然後將COM或USB連接到COM埠。
使用超級終端(HypernTerminal)將你PC上COM埠設置到115200 8 N 1並運行。現在,運行openocd(將棧指向0x4005000,設置PC為0xffff1a70- start_tiny_console_thumb,然後運行):
Halt
reg sp_usr 0x4005000
reg pc 0xffff1a70resume如果不出問題,我們會看到:
超級終端(HypernTerminal)將會彈出,現在你可以切換到超級終端並輸入測試:
(哈哈,現在幾乎可以無所不能了^_^,當然事情並沒有完,作者不想每次都進行上面的複雜操作)
五、"kernel loader "分析
如果我能找到一種方式運行tiny console(start_tiny_console_thumb/nstart_tiny_console_arm)函數,那麼我就可以不通過JTAG來訪問CPU(硬碟的主控制器)。內部FLASH(EEPROM)鏡像地址為0xfff00000,n大小為0x30000。bootstrap查找flash的首塊,我稱之為「內核載入器(kernel loader)」(應該就是bootloader)。 "kernelnloader"的頭是在FLASH的地址0x00000000(物理地址為0xfff00000)處,大小為0x20個位元組,頭是帶有校驗和的。
0x5a ; HeadernID
04,0,0 ; ?0xd,0xc,0,0 ; =0x00000c0dnsize of "kernel loader" + CHK0xc,0xc,0,0 ; =0x00000c0cnsize of "kernel loader"0x20,1,0,0 ; =0x00000120nstart of "kernel loader" data in FLASH (physical addr 0xfff00120)0x80,0xa,1,0 ; =0x00010a80nphysical addr where "kernel loader" have to be loaded0x80,0xa,1,0 ; =0x00010a80nphysical addr of execute start once "kernel loader" is loaded0,0,0 ; ?0xd1 ; HeadernID CHK 8-bit cheksum of first 0x1f bytes of "kernel loader" headerBootstrap會載入"kernel loader"到地址0x00010a80 ,大小為0x00000c0c。然後計算8位的checksum,並且與下一個位元組比較(offset + 0x00000c0c),如果checksum沒有問題,bootstrap就會載入"kernelnloader"運行(本實驗是0x00010a80)。
對於dump文件 "ffff0000.bin" ," terminal "函數地址為 0xFFFF0A50。這個函數使用和"tinynconsole"相同的串口,只是協議不同而已。一旦啟動便每秒發送0x15到串口並等候合適命令。
因此,如果在你的西部數據板上,內部flash的"kernel loader"部分被損壞,那麼你可以將板鏈接到串口上。如果不出意外,板會開始發送0x15,然後你可以使用終端來修復內部的flash而不需要JTAG。
以我的情況來看,我的"kernel loader"數據是正確的,因而問題可能出現其他地方。"Tiny Console" 被激活了!
將4k7的電阻鏈接到P1測試點(3.3v)和E6測試點。
將Rx和Tx線連到COM埠。
鏈接SATA電源,運行超級終端。就這樣!
1.如果我將4.7k的電阻練到E61和GND,加電,板將會執行RAM測試函數,在dump文件中,RAM 測試函數地址為0xFFFF01BC
2.如果我們將4.7k電阻鏈接到E62和GND,加電,板將會執行tinyConsole函數,但是這次JTAG和在地址0x00000000的RAM代碼會被激活。
3.如果我們將4.7k電阻鏈接到E61和GND,與方式(AND)鏈接到E62和GND,主板將會從外部EEPROM引導。由於外部EEPROM 的不存在,"KernelnLoader"將會出錯,CPU將會啟動"terminal"函數來執行。
在文件ffff0000.bin中,"terminal"函數地址為0xFFFF0A50
ROM:FFFF0158 LDR R1, =word_1C00A846
nROM:FFFF015C LDRH R0, [R1]nROM:FFFF0160 MOV R0, R0,LSR#13nROM:FFFF0164 CMP R0, #4nROM:FFFF0168 BEQ Kernel_RAM_check由於E61,E62上拉(PULLUP )連接到電阻R13,R6,因此,默認埠1c00a846 有值110x xxxx xxxxnxxxx
當向右移位13位後,其值為6,模式6就是一般性內部ROM引導。當E61,E62被接到GND上時,埠1c00a846 有值000x xxxx xxxx xxxx,當向右移位13次後,其值為0。模式0是從外部EEPROM 中引導。
六、"Kernel Block"分析及修復
首先我們需要知道一些關於FLASH中kernel數據塊的事情。他們和kernel Loader一樣也有0x20個位元組的頭部。
;-------------------------------------
1 ; blocknnr1 ; describentyp? 1,3 = compresed data?0,0 ; maybenhigh 16 bits of decompresed size?0x51,0x70,0,0 ; =0x00007051 size of block with CHK0x50,0x70,0,0 ; =0x00007051 size of block 0x2d,0xd,0,0 ; =0x00000d2dnoffset of block data in FLASH (physical addr 0xfff00d2d)0,0,0,0 ; =0x00000000nphysical addr where decompresed block have to be stored0xff,0xff,0xff,0xff ; =0xffffffff execute address but if it is 0xffffffff then itnwill not be executed!1,0xa,0,0 ; ?0x48,0x8c ; =0x8c48nlower 16 bits of decompresed size. 0 ; ?0x98 ; cheksum;-------------------------------------一旦加電啟動,"Kernel Loader"會做初始化SDRAM、重定向向量基址等等工作。然後會檢測kernel塊頭部,並將塊拷貝到sdram中,從sdram解壓到合適地方。
1.如果"execute address"是0xffffffff,那麼將會處理下一個塊。否則kernel loader將會執行這個地址處的代碼。在我的實驗的中,地址是0x00000000,也即是複位向量。
2.如果塊的checksum錯誤,kernel loader將會陷入死循環(這與SATA通信有關)
我的kernelnblock就損壞了,但是通過使用JTAG調試器,我已經找到了修復的方法。如果你有正確的備份並且備份文件的flash loader也沒有被損壞,那麼我的方法會起作用(如果你的flash loader也損壞了,那麼處理起來會有所不同)。
使用JTAG獲取FLASH的dump文件。並且與之前的備份進行比較,找到損壞的塊兒(看前面的塊頭描述),使用十六進位編輯器從備份文件中提取出相應的塊兒,設置你的板為測試模式(從接GND跳線開始的前三個引腳),鏈接JTAG、SATA線並且啟動電源。運行JTAG debugger,Halt target。看損壞塊的"offsetnaddress"地址偏移加上之前獲取的物理地址0xfff00000 。
然後在調試器中設置這個地址為只讀模式觀察點(斷點),設置PC為0xffff0000,然後run.
如果haltnCPU成功,在調試器反彙編代碼中找到"copy_mem"函數的目的地址,然後再在函數末尾設置斷點,run運行,停下來後,將你之前創建的備份文件載入到目的地址來覆蓋flash的壞數據。
重複這樣做直到每一個塊都得到修復。
最後直接run,驅動器會開始和SATA通信。現在你可以使用免費工具"WDR-demo"將備份的flash寫到板上去。就這樣就ok了。
由於我已經修復了我驅動器,所有我可以找到寫flash的函數了。
右邊是主命令表,所有你可以找到其他的命令。左邊是將0x40個位元組寫到內部flash的函數代碼。
Flash的埠基地址為 0x1c00aa00,以下是對埠的一些處理(偽代碼)
[0x1c00aa08] and 0xff00 or 7
[0x1c00aa08] or 300h[0x1c00aa04] <= 32 bit addres to write innFLASH[0x1c00aa08] or 1000h[0x1c00aa10] <= [32 bit data]++ * 0x10n;writing 0x10 32it data to flash[0x1c00aa08] and not 1000hetc,etc.......現在你可以隨意的使用串口通信來修復flash了。
推薦閱讀:
※如何讓UEFI BIOS支持漢字顯示:漢字編碼與顯示實踐
※UEFI快速上手:如何用VS調試NT32模擬環境
※UEFI到操作系統的虛擬地址轉換