所以,最后我自己想通了。我会试着描述这个过程。
首先,关于 NAND 的一些背景知识:它被组织成页面,这些页面被分组为块。您可以一次读取或写入一页,但擦除(将所有位变为 1(将字节变为 FF))一次只能完成一个块(写入只能将位从 1 更改为 0,而不能将其他位更改)方法,因此要写入新数据,通常必须先擦除该块)。通常,芯片每页还有一些额外的存储空间来存储 ECC(纠错码)和/或任意“备用”数据(也称为 OOB:带外),这些数据不被视为有用存储的一部分芯片,但仍然可以使用低级函数进行读写。
在浏览十六进制转储时,我在一堆 FF 之后遇到了这个区域:
0000081FE0: FF FF FF FF FF FF FF FF │ FF FF FF FF FF FF FF FF
0000081FF0: FF FF FF FF FF FF FF FF │ FF FF FF FF FF FF FF FF
0000082000: 6C 4F 41 64 00 00 00 00 │ 00 00 00 00 00 00 00 00 lOAd
0000082010: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
0000082020: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
0000082030: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
0000082040: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
0000082050: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
它在每个 0x200 字节之后有通常的 OOB 外观的 16 字节块,但随后有所不同:
83040: 70 41 52 74 18 00 00 00 │ 48 00 00 00 90 00 00 00 pARt↑ H
83050: A0 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
83060: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
....
84080: 75 42 54 74 20 00 00 00 │ 02 00 00 00 00 00 00 00 uBTt ☻
84090: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
840A0: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
840B0: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00
这是什么?听起来像U-Boot。让我们从 Sony 的网站上查看 U-boot 源...和宾果游戏!
/* for Loader Management */
#define MNG_SIG_LOADER 0x64414F6C /* "lOAd" */
#define TBL_SIG_PART 0x74524170 /* "pARt" */
#define TBL_SIG_UBOOT 0x74544275 /* "uBTt" */
#define TBL_SIG_FUKRNL 0x744B5566 /* "fUKt" */
#define TBL_SIG_FUINRD 0x74525566 /* "fURt" */
#define TBL_SIG_BAD 0x74444162 /* "bADt" */
#define MNG_SIG_KRNL 0x6C4E526B /* "kRNl" */
#define TBL_SIG_KRNL 0x744C4E6B /* "kNLt" */
#define TBL_SIG_INRD 0x74445269 /* "iRDt" */
(from icx1087_nand.c
) 所以,显然这些块与“加载器管理”有关……看看我们遇到的这个代码块的引用:
/* read management table */
for( rty = 0; rty < NAND_READ_RETRY; rty++ ) {
if( nand_search_tbl(NAND_BLK_LOADER_START, NAND_BLK_LOADER_LMT,
MNG_SIG_LOADER, TBL_SIG_PART, (void *)&ptbl, 0) ) {
continue;
}
/* success to read Kernel information if reach here */
break;
}
因此,它MNG_SIG_LOADER
在 NAND 块 NAND_BLK_LOADER_START 中寻找(lOAd)。而且价值是...
#define NAND_BLK_LOADER_START 1
所以看起来 82000 是 NAND 块 1 的开始。如果我们进一步检查代码,我们可以看到它正在这个块的页面中寻找其他签名(例如TBL_SIG_PART
或TBL_SIG_FUKRNL
)。让我们看看:
MNG_SIG_LOADER: 82000 (block 1 page 0?)
TBL_SIG_PART:83040(块 1 第 1 页?)
TBL_SIG_UBOOT:84080(块 1 第 2 页?)
83040-82000=1040 和 84080-83040=1040 也是!所以看起来转储中的页面大小是 1040. 和 1040*128 =82000 这意味着我们每个块有 128 个页面。实际数据可能是 0x1000 字节,0x40 是“备用”数据。所以,这解释了为什么我在每四个 0x200 扇区之后去同步:每页有 8 个扇区,但只有 0x40 字节的 OOB 数据。
我不知道为什么转储最终会有如此奇怪的结构;我认为这是因为用于转储的软件(IIRC,它是Matt Oh 的DumpFlash)在许多地方假设 512 字节的页面,并且不知何故 OOB 数据最终以块的形式传播,而不是在每个页面的末尾分组。可能它也与数据表中类似(但不相同)的东芝芯片的这段话有关:
每 512Byte 需要 8 位 ECC
(但没有解释应该如何做)
无论如何,一旦我计算出页面大小的 0x1040 和每块 128 页的幻数,修复我的脚本就不难了:
NAND_PAGE_SIZE = 0x1000
NAND_PAGE_BLK = 128
NAND_SECTOR_PER_PAGE = 8
NAND_SECTOR_SIZE = NAND_PAGE_SIZE/NAND_SECTOR_PER_PAGE
OOBLEN = 16
def page2off(pgno):
return pgno*0x1040 # (NAND_PAGE_SIZE + NAND_SECTOR_PER_PAGE*OOBLEN)
def read_page(inf, blkno, pgno):
blklen = page2off(NAND_PAGE_BLK)
fileoff = blklen*blkno + page2off(pgno)
print "reading block %d page %d: offset %08X" % (blkno, pgno, fileoff)
inf.seek(fileoff, 0)
block = inf.read(blklen)
s = ""
soff =0
for i in range(NAND_SECTOR_PER_PAGE):
print "sector %d offset %08X" % (i, soff)
s+= block[soff:soff+NAND_SECTOR_SIZE]
soff += NAND_SECTOR_SIZE+OOBLEN
return s
有了这个,我可以用内核和 ramdisk 转储闪存的一部分并提取二进制文件。