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);
}

添加模板

加载模板

最后的效果如下图: