分享

如何逆向嵌入式设备的NAND Flash

 刀首木 2017-01-07

注:中安网域所发文章均为原创或原创翻译,如需转载请保留文章出处与文章的完整性。



关于NAND Flash技术:

    Flash技术是大约在1980年由日本的Fujio Masuoka博士在东芝工作时发明的。在上世纪90年代的时候,flash技术只适用在一些大型电子产业上,而现在,flash技术的使用已经无处不在,特别是在我们的各种嵌入式产品中,而现在的Flash主要有两种:一种是NOR Flash,另一种就是NAND Flash。

    首先,NOR Flash的出现,彻底改变了原先由EPROM(电可编程序只读存储器)和EEPROM(电可擦只读存储器)一统天下的局面,NOR Flash的特点是芯片内执行,这样应用程序可以直接在flash闪存内运行,不必再把代码读到系统RAM中。NOR的传输效率很高,在1~4MB的小容量时具有很高的成本效益,但是很低的擦除和写入速度大大影响了它的性能。相比之下,NAND Flash有着非常快的擦除与写入速度,但却又有着其它的一些限制。其中的一个限制是,NAND Flash上对数据的操作是页级别的,也就是当你想要读取或者写入数据到某块NAND Flash时,是不能单单的只对其中的1个或某几个字节进行操作的,必须要以页为单位,而不同的NAND Flash芯片的页的大小也各有不相同,从几百字节一页到几千字节一页的都有。

    在本文当中,将提出一种方法来逆向NAND Flash上存储的数据或代码。特别是当NAND Flash上存储的是某个嵌入式设备的固件,而你又想去逆向与分析这个嵌入式设备时,这会非常的有用。当然了,只要是IC芯片,就会有不同的封装格式,而本文所使用的是TSOP(Thin Small Outline Package)格式的48脚的NAND Flash芯片,这种类型的芯片是非常常用的并且能在许多嵌入式设备或电子市场上买到。

 

直接通过JTAG调试接口提取NAND Flash的数据:

    JTAG技术可以说是逆向嵌入式系统时最常用到的一种方法,因为大多数厂家都会预留一个JTAG接口到产品的PCB板子上,用于调试产品,从而解决产品的一些性能问题(如BUG的修复、产品的性能提升研究)。反正,如果你所逆向的嵌入式产品上留有了JTAG口,你可以通过JTAG来提取固件以及进行相关调试。但出于对安全问题的考虑,现在有些厂商会选择去掉PCB板上的JTAG口,所以,如果你拿到的板子上没有预留JTAG口,又或者你实在是对不上JTAG口上的引脚时,你就可以使用下面的非运行时调试的方法了。

 

取NAND Flash芯片:

   第一步,简单来说就是要将NAND Flash芯片通过焊接手段取下来。那么你就需要去准备一台下面这样的吹焊机(普通的刀焊机也可以,但取芯片最好还是用吹焊的):焊接过程是非常简单的。吹焊机提供一个热鼓风机。使用热风去吹时,焊料合金通常会在180至190°C(360至370°F)熔化,但还是推荐设置温度略高于此。还有,在使用高温去接触芯片之前,你最好使用一些隔热胶带来贴住NAND Flash芯片周围的器件,如下图:这样的好处一个是可以保护其他芯片,防止PCB板被烧坏,另一个是防止其他小部件不小心被焊到。操作时,先用热空气在NAND Flash所在区域均匀地吹,然后等到Flash芯片松动了的时候,你就可以使用钳或类似的工具,以芯片从板子上取走。


NAND Flash的读写:

    现在你已经有一块NAND Flash芯片在手上了,那下一步就是对这块NAND Flash进行数据读取。而对Flash芯片的读写其实方法是很多的,你可以买一个比较好一点的一键式的编程器,也可以像本文一样买一块FTDI FT2232H闪存读写板子(成本较低)FT2232H是一种通用型的芯片,可以与Flash芯片进行交互,并最终将从Flash上读出来的数据通过USB接口导出到个人电脑上,反之也一样。而使用FTDI FT2232H来读写NAND Flash的具体方法是由国外的开源项目Sprites Mod提供的。Sprites Mod项目中关于使用FTDI FT2232H的内容部分的链接如下:

http:///?art=ftdinand&page=2


FTDI FT2232H

本文使用的FTDI FT2232H板子的样子如下:FTDI FT2232H板子提供多种模式,其中 “MCU Host Bus Emulation Mode”是最适合我们的现在的工作目的的。在这个模式里面,FTDI芯片板子将会仿真成8048/8051 MCU host总线,再通过在FTDI的上位机软件这边发送如下表中的FTDI命令,就能够通过I/O线对NAND Flash芯片进行读写操作了。更多的关于FTDI的细节可以到FTDI提供的官方文档上查询,链接如下:

http://www./Support/Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf



FT2232H板子与NAND Flash的引脚相连:

下图是本文所取出的三星的NAND Flash芯片的引脚图(也算是典型的NAND Flash引脚图)


再附上FT2232H芯片与NAND Flash芯片的引脚连接的总体图如下:

下表是FT2232H引脚与NAND Flash芯片的数据线引脚连接的细节。其中ADBUS0ADBUS7引脚是用来做数据传输的,需要将他们分别连接到NAND Flash芯片的I/O 0I/O 7引脚上。

而数据类型引脚的连接细节又如下:

如上可见CLEALE引脚分别用于命令数据的锁存和地址数据的锁存,而WP引脚则是写保护机制的开关。

然后还有:

如上,可见REWE引脚是用于表示读与写操作的预备状态的。当FT2232H芯片准备好要读数据时,它会通过BDBUS2(RD#)引脚发送一个下降的信号,让另一方知道它即将要发送一个新数据。当BDBUS3(WR#)输出是上升信号时,这就意味着新数据已经准备好从FT2232H写入到NAND Flash。当RE引脚未置位时,这又意味着Flash芯片繁忙中,也即是正在进行数据传输中。而CE引脚位是经常不被置位的。

当然,电源线与地线的连接还是不要忘了。

除了这些之外,NAND Flash芯片上的CE(Cheap Enable)引脚(9)应该被接地,这也意味着该芯片上的普通操作总是被允许的。

 

NAND Flash芯片的命令设置:

首先下表写明了对NAND Flash芯片经常要用到的一些基本命令,而这些命令对于我们用来读写NAND Flash来说已经足够了,而且,这些基本命令在不同品牌的NAND Flash上基本是相同的。



开始从NAND Flash中读取数据:

根据上面的连接方式,我们来完成最后的实际连接并开始从NAND Flash中读取数据。如下图,将FT2232H芯片板子上的相应的要连接到NAND Flash芯片上的引脚先通过导线连到一块48脚芯片烧录底座上,然后再将一开始取下的NAND Flash放到芯片底座上即可。

至此,你需要的硬件设施有:一个FTDI FT2232H板子,一根USB线,一个TSOP48烧录底座和一些导线。

硬件方面完成了所有连接后,就开始转向软件部分了。首先,有一套开源工具可用于对通过FTDI连到电脑上的NAND Flash进行读写,官方称为NandTool,链接如下:

https://github.com/bkerler/NANDReader_FTDI

而本文所使用的是将C++开发的NandTool移植成pythonFlashTool,功能一样,链接:

https://github.com/ohjeongwook/DumpFlash/

那么,当你把相应软件都准备好了,就可以开始了,首先,你可以像如下这样先查询一下你的NAND Flash的信息:然后,可以使用-r选项来开始读取NAND Flash中的内容:PS:在读NAND Flash的时候,还可以再配上-s选项,这样速度会快一些:

成功的话,从NAND Flash上读下来的内容便被保存到当前目录的flash.dmp

 

NAND Flash的错误检测与处理机制:

页,是NAND Flash上的数据的最小单位,而不是字节,如果想修改NAND Flash上的某个字节的内容的话,那你写入时就得写入该字节所在的整个页的数据(包括那些没有发生改变的数据)。而NAND Flash是一个大容量存储性的物理芯片,也难免会因为一些其他因素而发生一些数据上的错误,对于错误处理方面,NAND Flash有两个处理机制:ECC(Error Correction Code)和损坏块标记。而ECC与损坏快标记这两个信息都是被保存在每一个页的最后一个空闲行,该空闲行称为OOB区域。如下:

关于ECC

ECC是一种纠正某个页上的某个错误比特的一种方法,基于Hamming编码法,由Richard Hamming1950年发明。而ECC的原理简单来说就是利用了校验和。对于现代的Flash技术,很多不同的Flash芯片都采用了不同的ECC校验和算法,但其实这些不同的ECC校验和算法差别都不大,一般这些微小的差别就在于是否使用了XOR运算,位移指令上,ECC大体的计算方式都是一样的。所以重要的是,你需要搞清楚大体的ECC校验和是怎么算出来的。以本文所演示的Samsung K9F1208 这款NAND Flash芯片为例,如下图,我们可以看到它的页大小为512字节一页,且下面的每一个比特就是一个最小单元,而每一行象征着的就是该页中的每一个字节。而图右边的p8` p8 p16` p16 p32`...p2048` p2048这些就是它们由对应的行所算出来的校验和:

比如,p8`这个校验和就是由下图所有的标成了红色的数据通过XOR而算出来的。


再比如,p16`就是由byte[0],byte[1],byte[4],byte[5]...一直到byte[508],byte[509]通过XOR算出来的(有的芯片不一定是使用XOR),如下图。其他p8,p16`,p16,p32`,p32,p2048,p2048`的原理也相同。


python代码表示算法如下:

算完了行的校验和,那列的校验和其实也相似,如下图,p2是通过每一行的第2,3,6,7比特算出来的。


python代码表示列的校验和的算法如下:


最终,你需要利用上面算出的行与列校验和来算出最终要写到OBB上的3ECC值。在不同的NAND Flash芯片中行和列的校验和算法都差不多,但最终的这3ECC值的计算方式就不太相同了,下图展示的是本文所演示的Samsung K9F1208的计算方式:



损坏块标记:

损坏块标记技术其实除了NAND Flash,也常常运用在硬盘技术上。当flash上的数据错误已经超过了ECC的处理能力的时候,错误区域所在的块就会被标记为损坏的。而这些被标记为损坏的块将会被孤立并不再会被使用。为了标记损坏块,每个块中的第一页和最后一页都会用来做损坏块记录(这种是根据ONFI标准来的),也有一些厂商他们使用自己的方案来记录损坏块。下图展示了前面提过的链接中的DumpFlash项目中一种检测块损坏标记的方法。其原理是检测每一个块的第一页和第二页的OOB区域中的第六个字节的内容是否为0xFF,如果不是,则可以认为当前块是一个损坏的块。这种方法被多个厂商所采用,包括比较出名的SamsungMicron


开始对从NAND Flash中读取的数据进行逆向分析:

在嵌入式设备中,经常会有一些固件本身比较大的产品,厂商喜欢把这些固件放到NAND Flash中,而不是放到小容量的NOR Flash芯片中,而本文的案例也正是这样。如下图,从NAND Flash中提取出来的二进制文件,正是一个完整的嵌入式设备的固件,并且该固件是一个基于U-BOOT启动的固件,相信了解U-BOOT的朋友们会觉得该固件格式非常明了与熟悉。而该固件的开头,也即原本NAND Flash上地址为0x00000000的地方,一般都是NAND Flash启动的入口起点。

如上,当第一阶段的boot loader代码运行完后,第二阶段的U-Boot代码将会被加载到NAND Flash上的其他任意或预设的一片RAM区域处运行,再随后,第二阶段的U-Boot又会加载并运行内核,解压与挂载JFFS2文件系统。


1 st stage boot loader:

第一阶段的boot loader做的是一些底层的初始化操作:

初始化后便会去加载下一阶段的boot loader,下图展示了一些比较有趣的字符串,看上去像是一些引导过程跳至第二阶段的boot loader(U-BOOT loader)时日志记录用的信息:

U-Boot loader:

当第一阶段的bootloader完成相应工作后,第二阶段的loader U-BOOT将完成更多的各种复杂的操作。而U-BOOT本身就是一种在嵌入式设备中非常流行的开源的引导方式,基于U-BOOT的嵌入式设备的固件,一般在第二阶段的U-BOOT loader之后,跟着的便是该嵌入式产品所采用的OS内核,然后再是采用的文件系统。

U-Boot images:

对于基于U-BOOT的固件,我们习惯把第二阶段的U-BOOT loader后的固件部分称为U-Boot imagesU-Boot images通常都有一个magic特征码 DWORD 0x27051956,下图展示了U-Boot images的镜像头部定义:

上面的头部定义对应于本文读出来的固件的U-Boot images,如下,其中比较重要的是U-Boot镜像的长度为0x28A03B,这是不包括这0x40字节的U-Boot镜像头的,所以实际上U-Boot镜像的总大小为0x28A07B


又因为一个页的大小为0x200字节,且由于每个页还有额外的0x10大小的OOB数据,所以实际上从NAND Flash中读出来的固件的页的大小会是0x210,那么U-Boot镜像的所占的实际大小为:

page count = 0x1450  ,extra data = 0x7B ,page count * (page size + oob size) + extra data = 0x1450 * (0x200 + 0x10) + 0x7b = 0x29E57B

U-Boot镜像的起始地址又是0x31800,那么U-Boot镜像在从NAND Flash读出来的整个固件中的结束位置便是0x31800 + 0x31800 = 0x2CFD7B。于是,你便可以像如下这样从整个NAND Flash dump下来的固件文件中提取U-Boot镜像部分:


然后丢到IDA中,IDA能支持加载U-Boot镜像:

但是,由于这次的U-Boot镜像的镜像类型是0x4(mutil-file)型,IDA的支持并不是很完美。0x40字节的U-Boot镜像头之后,便是每一个U-Boot镜像块,在本文演示的U-Boot镜像中,一共有3U-Boot镜像块(RamdiskkernelJFFS2文件系统)。下图展示了0x40字节的镜像头后的三个相连的镜像块的前两个的长度,JFFS2文件系统这个镜像块因为是最后一个了,所以不需要:

你也可以像下面这样使用mkimage命令来检查U-Boot镜像的内容:


 

Ramdisk image:

通过压缩标志检查,不难看出U-Boot images中的第一个镜像块Ramdisk是经gzip压缩的,解压后,使用File命令如下,可以看到这个镜像块文件是一个ext2文件系统:

其实这一个镜像块区域一开始空的,只是嵌入式系统运行时,由于要解压与加载Linux内核镜像,所以从NAND Flashdump下来时,这里并不是空的。当然了,你也可以使用MTD来重新挂载它。首先,加载MTD关联的内核模块:


然后,你可以使用dd命令来拷贝这个镜像文件到MTD 块设备上:


拷贝完成后,可以使用普通的mount 命令来挂载新的linux系统到当前的这个Ramdisk镜像块文件上了:

Kernel image:

内核镜像块同样也是经gzip压缩了,解压后,通过搜索来确定内核镜像的入口点,然后丢入IDA,让IDA从入口点开始反汇编:



JFFS2:

从整体布局上看,JFFS2文件系统是固件中要分析的核心。boot loaders的代码通常都是基于比较通用的代码,而很多有趣的自定义文件都是放置在了固件的文件系统中(本文的是JFFS2文件系统)。要从dump出来的NAND Flash镜像文件中识别JFFS2文件系统也很容易,通常,当厂家给NAND Flash安装固件并出厂时,必然会用JFFS2文件系统工具去对NAND Flash相应的空间区域进行格式化,被JFFS2格式化后,JFFS2NAND Flash中所占区域的每一页的OOB域都会被放置一个特殊的擦除码(erasemarkers),这些擦除码表明着该NAND Flash块是用于放置JFFS2的,不需要再进行额外的初始化了,如下图:

如上,可以通过搜索的方法,定位到第一个有erasemarker的页,也便就是JFFS2的开头了。

在找到JFFS2文件系统的开头后,你就可以提取整个JFFS2U-Boot镜像块了。推荐使用DumpFlash项目下的DumpFlash.py (或者直接使用dd),配上起始地址、结束地址、-r选项,还有-o选项来指定一个输出的文件。比如本文中JFFS2文件系统的偏移范围是整个dump出来的固件文件中的0x0262C200 ~ 0x03084600,则:

python DumpFlash.py -r 0x0262c200 0x03084600 -o jffs2.dmp flash.dmp


使用MTD来挂载JFFS2文件系统:

现在,你已经可以把提取出来的JFFS2镜像文件挂载到一个Linux系统上了。方法与上面挂载Ramdisk image一样,首先,你需要在你的linux系统上创建一个MTD设备,然后加载与之关联的linux内核模块,如mtdrammtdblockjffs2,如下图:

接下来就是使用dd来将提取的jffs2.dmp导到MTD设备/dev/mtdblock0:

不出什么意外的话,提取出来的JFFS2便能挂载成功,于是你就可以任意的修改提取出来的JFFS2文件系统了:

我们都知道,JFFS2是一个日志型的文件系统,具有断电可靠性,非常的适合用在嵌入式系统中,为了自动化的解析JFFS2文件系统,你可以使用DumpFlash项目下的DumpJFFS2项目来处理JFFS2文件系统镜像文件的一些底层属性。比如,你可以使用DumpJFFS2来直接将整个JFFS2文件系统镜像文件解压出来,而不需要你去挂载它,这样的话我们可以很方便的去修改JFFS2中的各种文件,当然了,使用现在比较流行binwalk也是个不错的选择。

 

将固件写回NAND Flash

如果你把JFFS2挂载到了MTD设备上,并在上面对JFFS2文件系统中的文件进行了相关修改后,当你想把你修改过的JFFS2重新压缩回一个二进制镜像文件时,你可以使用下面的命令,把修改过的挂载在MTD上的JFFS2dump下来,并且这还能够自动帮你修正与增添新JFFS2镜像中的OOB数据域(包括重新计算ECC,写入erasemarker)

python DumpFlash.py -R -o mtdblock0.oob.dmp mtdblock0.dmp

有了新的修改过的JFFS2镜像后(mtdblock0.oob.dmp),你就可以把这个镜像重新写回到NAND Flash芯片上了,使用下面的命令即可:

因为你修改的只是JFFS2镜像部分,其他的没有修改,所以不必把整个新固件覆盖到NAND Flash芯片上,只需要用新的JFFS2镜像覆盖原NAND Flash中固件的原JFFS2镜像部分即可,上面的0x12820就是本文的JFFS2镜像在原始NAND Flash固件中起始页数,0x12820 * (0x200 + 0x10) = 0x262C200才是JFFS2镜像在原始固件中的起始偏移地址。

 

重新把NAND Flash芯片焊回去:

当你的新固件写回了NAND Flash芯片后,就是时候把NAND Flash芯片重新焊回到它原来的PCB板子上了。把芯片焊回去跟把芯片取下来很不一样,因为芯片的引脚多了并且引脚与芯片本来就很小,所以很不好焊。除了叫你焊的时候小心点,本文还是建议你去准备一点焊锡膏,在焊接前涂到芯片的每个引脚上,像下图一样。这样的话会有助于芯片引脚与PCB版上的引脚粘合,并且也保护引脚。

 

总结:

像这样“直接”的与Flash芯片进行交互,读写Flash芯片中的数据是非常有效并准确的,特别是在JTAG不能使用的时候。而实际上,为了保护自己的知识产权,现在也越来越多的厂家开始使用一些电路或硬件上的手法混淆他们的嵌入式产品的PCB板子上的JTAG接口了,甚至有一些保护欲强的厂家更是去掉了JTAG接口。

除此之外,值得一提的是,现在也有很多厂商会去考虑如何检测或防止他们产品的固件被修改,特别是固件上的文件系统镜像这一部分。所以,一个比较好,也比较有挑战性的方法就是把逆向与修改的目标转移到固件的其他部分,比如boot loader,内核这些,这样的话你的运行级别会更高,厂家要应对之也会非常的困难。

 

参考资料:

us-14-Oh-Reverse-Engineering-Flash-Memory-For-Fun-And-Benefit-WP

Author:Jeong Wook (Matt) Oh





    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多