西数硬盘固件调试与逆向分析【转】

西数硬盘固件调试与逆向分析

一、背景

去年为了分析方程式组织那个入侵硬盘固件的组件,在分析西部数据硬盘固件时遇到点问题,偶尔在hddguru上发现的一次讨论文,对我的分析起到很大帮助。就将此次讨论整理翻译成文,同时也当作为我个人的学习笔记。现分享出来,感兴趣者共学之。

基本思路总结:

1.分析PCB,找到JTAG接口

2.使用调试器连接JTAG调试分析

3.dump Flash,这里作者的FLASH似乎没有设置保护,可以直接就dump了。

4.分析FLASHdump文件.有一些意外的东西发现就是flash中提供有可以通过串口进行访问可以任意读写任意地址,这也算是一个硬盘后门啊,邪恶的可以做多少事儿,但是前提是你必须拿到硬盘,修硬盘的可以方便给你植入病毒了。

5.分析bootloader格式和kernel格式。

6.修复flash。

 

文章是作者一次求助引起的,作者的西数硬盘损坏而引起一次分析探索.求助的问题如下:

Is there 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 EEPROM24p10, 24p20 ?

 

二、硬件分析

看来没有人愿意帮助我,so,我自己找到JTAG引脚。希望有人会感兴趣。JTAG引脚是在PCB的CON1上,看起来好像所有板上都是一样的?!!我测试了2061-701335-c00 Marvell 88i6545 和 2061-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       -for powering JTAG interface or for reference.

CLK out       -for VERY FAST JTAG interface that support CLK out

RST              -for JTAG interface that support RST

 

对于我的Xilinx IIIJTAG接口来说只需要使用到GND,TDI, TMS, CLK in, TDO, Vcc 3.3

开始:

1. 将”feroceon.cfg”文件从openocd\target拷贝到openocd\bin下

2. 拷贝文件jtag.cfg到openocd\bin下。

对于Xilinx IIIJTAG的接口文件如下:

#*******************************
#daemon configuration
telnet_port 4444
gdb_port 3333

#interface
interface parport
parport_cable dlc5
parport_port 0x378
#*******************************

 

3. 把putty.exe拷贝到openocd\bin下,或者使用telnet.

4. 我使用的PCB板(从HDD盘上卸下来的)的EEPROM(ROM)是已经不可用的。我并不确定是否有必要将PCB设置为测试模式(前3个引脚接到GND)

将JTAG接口连接到PCB。

将SATA电源连接PCB。

5. 进入到openocd\bin目录下输入:openocd.exe -f jtag.cfg.txt -f feroceon.cfg

如果没啥问题,你会看到如下:

 

你可以看到设备ID为0x259663d3,由于OPENOCD没有匹配的loader,我就使用feroceon.cfg配置loader来加载。如果你那里发生错误,可以去读OPENOCD的文档。

6. 现在运行telnet。进入到openocd\bin目录输入:putty.exe

打开putty后,输入localhost,端口输入4444链接。如果没什么问题的话会出现如下一个窗口:

7.然后输入halt,你会看到:

到这一步就表示你可以完全控制Marvell 88i6745n了。然后,开始从Marvell chip中dump bootstrap:

dump_image ffff0000.bin 0xffff0000 0x10000

没有意外的话我们可以看到:

 

四、dump文件分析

 

在目录openocd\bin可以看到dump文件ffff0000.bin。

目前我的文件SHA1为5ab6b58869a6cf40aaa60626e8440c0abc186ae8,现在你可以用一个ARM反汇编器来分析代码了。

 

一些地址推测:

 

0xffff0000                    HWRESET vector(复位向量, 处理器复位时执行)
0x04000000-0x04007fff   internal SRAM for STACK.(栈区)
0x00000000                   SDRAM8-32Mb ???
0x1c00xxxx                   ports?(端口映射区?)
0x1c00a6xx                   serialport(串口映射区)

0x1c00a8xx                   I/O port ?(IO映射区?)

 

通过对dump文件的分析得到了如下函数:

 

FFFF1A70:start_tiny_console_thumb
FFFF01B4: start_tiny_console_arm
FFFF1944: send_asciiz_strin
FFFF1A16:receive_and_resend_CMD
FFFF18E2: send_byte
FFFF1A06:receive_byte
等等

 

1.函数start_tiny_console_thumb/start_tiny_console_arm分析

在硬件复位后,CPU测试端口0x1C00A84E(注:这是测试端口的映射地址),如果第13位被设置的话,运行start_tiny_console。

这个函数有3个命令功能 (read,write,jump):

r <32bit address> ;                      readone half word(16 bit) from address
w <32bit address> <16bit data> ;   write one half word(16 bit) to address
j <32bit address> ;                       jump or call code onaddress

 

通过这几个命令功能我们可以在底层对CPU做任何事情,但问题是怎样设置0x1C00A84E的第13位。端口0x1C00A84E是连接到4个引脚(3.5寸盘是8个引脚)的跳线插上,因此第13位必须连接到某个引脚或者某个跳线插的组合。我先使用JTAG调用它来测试了tinyconsole函数,先用3.3v的电压加到板上,然后将COM或USB连接到COM端口。

 

使用超级终端(Hyper Terminal)将你PC上COM端口设置到115200 8N 1并运行。现在,运行openocd(将栈指向0x4005000,设置PC为0xffff1a70-start_tiny_console_thumb,然后运行):

Halt

reg sp_usr 0x4005000
reg pc 0xffff1a70
resume

 

如果不出问题,我们会看到:

超级终端(Hyper Terminal)将会弹出,现在你可以切换到超级终端并输入测试:

(哈哈,现在几乎可以无所不能了^_^,当然事情并没有完,作者不想每次都进行上面的复杂操作)

 

五、kernel loader “分析

 

如果我能找到一种方式运行tiny console(start_tiny_console_thumb/ start_tiny_console_arm)函数,那么我就可以不通过JTAG来访问CPU(硬盘的主控制器)。内部FLASH(EEPROM)镜像地址为0xfff00000, 大小为0x30000。bootstrap查找flash的首块,我称之为“内核加载器(kernel loader)”(应该就是bootloader)。 “kernel loader”的头是在FLASH的地址0x00000000(物理地址为0xfff00000)处,大小为0x20个字节,头是带有校验和的。

 

0x5a;            Header ID
04,0,0 ;          ?
0xd,0xc,0,0 ;   =0x00000c0d size of “kernel loader” +CHK
0xc,0xc,0,0 ;   =0x00000c0c size of “kernel loader”
0x20,1,0,0 ;     =0x00000120start of “kernel loader” data in FLASH (physical addr 0xfff00120)
0x80,0xa,1,0 ; =0x00010a80 physical addr where “kernelloader” have to be loaded
0x80,0xa,1,0 ; =0x00010a80 physical addr of execute start once”kernel loader” is loaded
0,0,0 ;            ?
0xd1 ;            HeaderID CHK 8-bit cheksum of first 0x1fbytes of “kernel loader” header

 

Bootstrap会加载”kernel loader”到地址0x00010a80 ,大小为0x00000c0c。然后计算8位的checksum,并且与下一个字节比较(offset + 0x00000c0c),如果checksum没有问题,bootstrap就会加载”kernel loader”运行(本实验是0x00010a80)。

对于dump文件 “ffff0000.bin” ,” terminal “函数地址为 0xFFFF0A50。这个函数使用和”tiny console”相同的串口,只是协议不同而已。一旦启动便每秒发送0x15到串口并等候合适命令。

因此,如果在你的西部数据板上,内部flash的”kernelloader”部分被损坏,那么你可以将板链接到串口上。如果不出意外,板会开始发送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 的不存在,”Kernel Loader”将会出错,CPU将会启动”terminal”函数来执行。

 

在文件ffff0000.bin中,”terminal”函数地址为0xFFFF0A50

ROM:FFFF0158 LDR R1, =word_1C00A846
ROM:FFFF015C LDRH R0,[R1]
ROM:FFFF0160 MOV R0, R0,LSR#13
ROM:FFFF0164 CMP R0, #4
ROM:FFFF0168 BEQ Kernel_RAM_check

 

由于E61,E62上拉(PULLUP )连接到电阻R13,R6,因此,默认端口1c00a846 有值110x xxxx xxxx xxxx

当向右移位13位后,其值为6,模式6就是一般性内部ROM引导。当E61,E62被接到GND上时,端口1c00a846 有值000x xxxx xxxx xxxx,当向右移位13次后,其值为0。模式0是从外部EEPROM 中引导。

 

六、“Kernel Block”分析及修复

 

首先我们需要知道一些关于FLASH中kernel数据块的事情。他们和kernel Loader一样也有0x20个字节的头部。

;————————————-
1 ;                        blocknr
1 ;                        describetyp? 1,3 = compresed data?
0,0 ;                      maybehigh 16 bits of decompresed size?
0x51,0x70,0,0 ;              =0x00007051 size of block with CHK
0x50,0x70,0,0 ;              =0x00007051 size of block
0x2d,0xd,0,0 ;        =0x00000d2doffset of block data in FLASH (physical addr 0xfff00d2d)
0,0,0,0 ;                =0x00000000physical addr where decompresed block have to be stored
0xff,0xff,0xff,0xff ; =0xffffffff execute address but if it is 0xffffffff then it will notbe executed!
1,0xa,0,0 ;             ?
0x48,0x8c;            =0x8c48 lower 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通信有关)

 

我的kernel block就损坏了,但是通过使用JTAG调试器,我已经找到了修复的方法。如果你有正确的备份并且备份文件的flash loader也没有被损坏,那么我的方法会起作用(如果你的flashloader也损坏了,那么处理起来会有所不同)。

 

使用JTAG获取FLASH的dump文件。并且与之前的备份进行比较,找到损坏的块儿(看前面的块头描述),使用十六进制编辑器从备份文件中提取出相应的块儿,设置你的板为测试模式(从接GND跳线开始的前三个引脚),链接JTAG、SATA线并且启动电源。运行JTAG debugger,Halt target。看损坏块的”offset address”地址偏移加上之前获取的物理地址0xfff00000 。

然后在调试器中设置这个地址为只读模式观察点(断点),设置PC为0xffff0000,然后run.

如果halt CPU成功,在调试器反汇编代码中找到”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 in FLASH
[0x1c00aa08]or 1000h
[0x1c00aa10]<= [32 bit data]++ * 0x10 ;writing 0x10 32it data to flash
[0x1c00aa08]and not 1000h
etc,etc…….

现在你可以随意的使用串口通信来修复flash了。

此条目发表在经验技术, 逆向分析分类目录。将固定链接加入收藏夹。