DragonFlyBSD Kernel Audit
DF-2620 / df2620_dump.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""DF-2620: dump PFS inodes under the hammer2 super-root (sanity walker)."""
import struct, sys

RADIX_MASK = 0x3F
BREF = 128
INODE_DATA_BLOCKSET = 0x200
T_EMPTY, T_INODE, T_DATA, T_DIRENT, T_INDIRECT = 0, 1, 3, 4, 2

def bref_parse(buf, off):
    t, methods = struct.unpack_from('<2B', buf, off)
    key, mtid, modtid, doff, utid = struct.unpack_from('<QQQQQ', buf, off + 8)
    return dict(type=t, methods=methods, key=key, data_off=doff, off=off,
                modtid=modtid)

def main():
    img = bytearray(open(sys.argv[1], 'rb').read())
    volhdr = img[0:0x10000]
    assert struct.unpack_from('<Q', volhdr, 0)[0] == 0x48414D3205172011, "bad magic"

    sroot0 = None
    for i in range(4):
        br = bref_parse(volhdr, 0x200 + i * BREF)
        if br['data_off']:
            sroot0 = br
            break
    sroot_blk = sroot0['data_off'] & ~RADIX_MASK
    print("[walk] volhdr sroot bref @volhdr+%#x type=%d methods=%#x doff=%#x -> blk @%#x"
          % (sroot0['off'], sroot0['type'], sroot0['methods'], sroot0['data_off'], sroot_blk))

    # walk sroot inode's direct blockset + indirects for INODE children
    def walk_indirect(blk, radix, depth):
        nslots = min((1 << radix) // BREF, 1024)
        for i in range(nslots):
            boff = blk + i * BREF
            br = bref_parse(img, boff)
            if br['type'] == T_INODE and (br['data_off'] & RADIX_MASK):
                dump_child(br, "ind@%#x[%d]" % (blk, i))
            elif br['type'] == T_INDIRECT and (br['data_off'] & RADIX_MASK) and depth < 4:
                walk_indirect(br['data_off'] & ~RADIX_MASK, br['data_off'] & RADIX_MASK, depth + 1)

    def dump_child(br, where):
        iblk = br['data_off'] & ~RADIX_MASK
        inum = struct.unpack_from('<Q', img, iblk + 0x58)[0]
        name_len = struct.unpack_from('<H', img, iblk + 0x80)[0]
        nmast = img[iblk + 0x86]
        ptype = img[iblk + 0x87]
        fname = bytes(img[iblk + 0x100:iblk + 0x100 + max(name_len, 1)]).split(b'\0')[0]
        print("[pfs ] %s: bref@%#x type=%d methods=%#x doff=%#x | inum=%#x name=%r pfs_type=%d pfs_nmasters=%d"
              % (where, br['off'], br['type'], br['methods'], br['data_off'],
                 inum, fname, ptype, nmast))

    for i in range(4):
        boff = sroot_blk + INODE_DATA_BLOCKSET + i * BREF
        br = bref_parse(img, boff)
        if br['type'] == T_INODE and (br['data_off'] & RADIX_MASK):
            dump_child(br, "sroot.direct[%d]" % i)
        elif br['type'] == T_INDIRECT and (br['data_off'] & RADIX_MASK):
            walk_indirect(br['data_off'] & ~RADIX_MASK, br['data_off'] & RADIX_MASK, 1)

if __name__ == '__main__':
    main()