010模板-JFFS2文件系统
一、前言
010 Editor的模板功能是一项专业级的二进制文件解析与编辑机制。它通过结构化的方式对原始十六进制数据进行语义层面的映射,将字节序列转化为具有明确字段含义的数据结构树,从而显著提升了二进制内容的可读性与可编辑性。
该功能不仅内置了覆盖常见文件格式的丰富模板库,还支持用户根据特定格式需求进行自定义开发,并可结合脚本实现解析、修改与校验等流程的自动化。在逆向工程、协议分析及固件研究等专业领域中,模板功能作为连接底层数据与上层逻辑的关键桥梁,发挥着重要的技术支撑作用。

二、JFFS2文件
参考之前写的《JFFS2文件系统》,创建一个jffs2的文件,再使用010Editor打开。
mkfs.jffs2 -r rootfs -o rootfs.jffs2
查找模板,发现不支持jffs2,所以需要自己根据jffs2的格式去编写模板。

三、010模板
将以下代码保存为模板文件jffs2.bt:
// ---------- 节点类型常量 ----------
const uint16 JFFS2_MAGIC = 0x1985;
const uint16 NODETYPE_PADDING = 0x2004;
const uint16 NODETYPE_DIRENT = 0xE001;
const uint16 NODETYPE_INODE = 0xE002;
const uint16 NODETYPE_CLEANMARKER = 0x2003;
const uint16 NODETYPE_SUMMARY = 0x2006;
const uint16 NODETYPE_XATTR = 0xE008;
const uint16 NODETYPE_XREF = 0xE009;
// ---------- CRC-32 ----------
uint32 MtdCrc(uint64 pos, uint64 len) {
local uint64 crc;
local uint64 i, j;
local uint32 b;
// Jefferson's mtd_crc is CRC32 with an effective zero seed and no final XOR.
crc = 0;
for (i = 0; i < len; i = i + 1) {
b = ((uint32)ReadByte(pos + i)) & 0xFFu;
crc = crc ^ (uint64)b;
for (j = 0; j < 8; j = j + 1) {
if ((crc & 1u) != 0u) {
crc = (crc >> 1) ^ 0xEDB88320u;
}
else {
crc = crc >> 1;
}
}
}
return (uint32)crc;
}
string TypeName(uint16 t) {
switch (t) {
case NODETYPE_DIRENT: return "DIRENT";
case NODETYPE_INODE: return "INODE";
case NODETYPE_PADDING: return "PADDING";
case NODETYPE_CLEANMARKER: return "CLEANMARKER";
case NODETYPE_SUMMARY: return "SUMMARY";
case NODETYPE_XATTR: return "XATTR";
case NODETYPE_XREF: return "XREF";
default: return Str("UNK_0x%04X", t);
}
}
// ---------- JFFS2 common header (12 字节) ----------
typedef struct {
uint16 magic <fgcolor=cRed, comment="JFFS2 magic, must be 0x1985">;
uint16 nodetype <comment="Node type">;
uint32 totlen <comment="Total length (incl. header)">;
uint32 hdr_crc <comment="CRC of first 8 bytes">;
} Jffs2Header <read=ShowHdr>;
string ShowHdr(Jffs2Header &h) {
if (h.magic != JFFS2_MAGIC) {
return Str("[bad magic=0x%04X]", h.magic);
}
local uint64 p = startof(h);
local uint32 calc = MtdCrc(p, 8);
if (calc == h.hdr_crc) {
return Str("[OK %s totlen=%u]", TypeName(h.nodetype), h.totlen);
}
if (calc == (~h.hdr_crc & 0xFFFFFFFF)) {
return Str("[DIRTY %s totlen=%u]", TypeName(h.nodetype), h.totlen);
}
return Str("[CRC_BAD %s exp=0x%08X got=0x%08X]",
TypeName(h.nodetype), h.hdr_crc, calc);
}
// ---------- DIRENT body ----------
// Matches struct jffs2_raw_dirent after the common 12-byte header.
typedef struct {
uint32 pino <comment="parent inode">;
uint32 version;
uint32 ino <comment="child inode number">;
uint32 mctime <comment="modification time">;
uchar nsize <comment="name length">;
uchar type <comment="DT_* directory entry type">;
uchar unused[2];
uint32 node_crc;
uint32 name_crc;
char name[nsize] <fgcolor=cDkGreen>;
} Jffs2Dirent <read=ShowDirent>;
// ---------- INODE body ----------
// Matches struct jffs2_raw_inode after the common 12-byte header.
typedef struct {
uint32 ino <comment="inode number">;
uint32 version;
uint32 mode;
uint16 uid;
uint16 gid;
uint32 isize <comment="file size">;
uint32 atime;
uint32 mtime;
uint32 ctime;
uint32 offset <comment="data offset within file">;
uint32 csize <comment="compressed size">;
uint32 dsize <comment="decompressed size">;
uchar compr <comment="compression method">;
uchar usercompr;
uint16 flags;
uint32 data_crc;
uint32 node_crc;
uchar data[csize] <comment="compressed data (length = csize)">;
} Jffs2Inode <read=ShowInode>;
// ---------- CRC checks for node bodies ----------
string CrcStatus(uint32 calculated, uint32 stored) {
if (calculated == stored) {
return "OK";
}
return "BAD";
}
string ShowDirent(Jffs2Dirent &d) {
local uint64 p;
local uint32 node_calc;
local uint32 name_calc;
// node_crc covers the header and fixed dirent fields before node_crc.
p = startof(d) - 12;
node_calc = MtdCrc(p, 32);
name_calc = MtdCrc(startof(d) + 28, d.nsize);
return Str("[node_crc=%s calc=0x%08X stored=0x%08X name_crc=%s calc=0x%08X stored=0x%08X]",
CrcStatus(node_calc, d.node_crc), node_calc, d.node_crc,
CrcStatus(name_calc, d.name_crc), name_calc, d.name_crc);
}
string ShowInode(Jffs2Inode &n) {
local uint64 p;
local uint32 node_calc;
local uint32 data_calc;
// node_crc covers the header and inode fields through flags.
p = startof(n) - 12;
node_calc = MtdCrc(p, 60);
data_calc = MtdCrc(startof(n) + 56, n.csize);
return Str("[data_crc=%s calc=0x%08X stored=0x%08X node_crc=%s calc=0x%08X stored=0x%08X]",
CrcStatus(data_calc, n.data_crc), data_calc, n.data_crc,
CrcStatus(node_calc, n.node_crc), node_calc, n.node_crc);
}
typedef struct {
Jffs2Header hdr;
if (hdr.nodetype == NODETYPE_DIRENT) {
Jffs2Dirent body;
}
else if (hdr.nodetype == NODETYPE_INODE) {
Jffs2Inode body;
}
// PADDING / CLEANMARKER / XATTR / XREF / 其它: 只有 hdr
} Jffs2Node;
FSeek(0);
// JFFS2 magic 的原始字节序列:
// 85 19 -> little-endian (数值 0x1985)
// 19 85 -> big-endian (数值 0x1985)
if (FileSize() < 2) {
Warning("文件太短,无法读取 JFFS2 magic");
return -1;
}
if ((((uint32)ReadByte(0)) & 0xFFu) == 0x85u &&
(((uint32)ReadByte(1)) & 0xFFu) == 0x19u) {
LittleEndian();
Printf("JFFS2 byte order: little-endian\n");
}
else if ((((uint32)ReadByte(0)) & 0xFFu) == 0x19u &&
(((uint32)ReadByte(1)) & 0xFFu) == 0x85u) {
BigEndian();
Printf("JFFS2 byte order: big-endian\n");
}
else {
Warning("不是 JFFS2 镜像: magic bytes 不匹配");
return -1;
}
FSeek(0);
while (FTell() + 12 <= FileSize() && ReadShort(FTell()) == JFFS2_MAGIC) {
Jffs2Node node;
FSeek(startof(node) + ((node.hdr.totlen + 3) / 4) * 4);
}
添加模板

加载模板

最后的效果如下图:
