GDAblog-亚洲首款交互式Android反编译器

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

 

一、背景

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

基本思路总结:

1.分析PCB,找到JTAG接口

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

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

4.分析FLASH dump文件.有一些意外的东西发现就是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 EEPROM 24p10, 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 III JTAG接口来说只需要使用到GND, TDI, TMS, CLK in, TDO, Vcc 3.3

开始:

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

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

对于Xilinx III JTAG的接口文件如下:

#*******************************
#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 HW RESET vector(复位向量, 处理器复位时执行)
0x04000000-0x04007fff internal SRAM for STACK.(栈区)
0x00000000 SDRAM 8-32Mb ???
0x1c00xxxx ports?(端口映射区?)
0x1c00a6xx serial port(串口映射区)

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> ; read one 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 on address

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

使用超级终端(Hyper Terminal)将你PC上COM端口设置到115200 8 N 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 ; =0x00000120 start of "kernel loader" data in FLASH (physical addr 0xfff00120)
0x80,0xa,1,0 ; =0x00010a80 physical addr where "kernel loader" have to be loaded
0x80,0xa,1,0 ; =0x00010a80 physical addr of execute start once "kernel loader" is loaded
0,0,0 ; ?
0xd1 ; Header ID CHK 8-bit cheksum of first 0x1f bytes 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的"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 的不存在,"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 ; block nr
1 ; describe typ? 1,3 = compresed data?
0,0 ; maybe high 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 ; =0x00000d2d offset of block data in FLASH (physical addr 0xfff00d2d)
0,0,0,0 ; =0x00000000 physical addr where decompresed block have to be stored
0xff,0xff,0xff,0xff ; =0xffffffff execute address but if it is 0xffffffff then it will not be 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也没有被损坏,那么我的方法会起作用(如果你的flash loader也损坏了,那么处理起来会有所不同)。

使用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了。