Python 脚本从分区的原始数据中识别 Ext4 Inode

Python 脚本从分区的原始数据中识别 Ext4 Inode

我正在尝试编写一个 python 脚本,该脚本可以从 ext4 分区的备份映像中读取原始数据,并识别何时看到 ext4 inode 结构,以便可以恢复特定文件。

此类脚本的目的是当超级块损坏并且无法使用幻数、幻字节或已知扩展类型恢复文件时,所有其他方法都不起作用。

将通过成功执行指向适当分区的此命令来创建要分析的数据:

dd if=/dev/sda of=partition.dd

我正在寻找的答案将是 python 代码: 从 dd.image 一次读取一个原始数据块,并确定该数据块是否是 ext4 inode 块。

如果我得到这方面的帮助,我可以使用索引节点中找到的数据来查找允许该文件完全恢复的扩展块。我似乎没有找到这样的东西,所以我确信这是一个迫切需要的缺失工具。

我一直在研究这个:

https://www.kernel.org/doc/html/latest/filesystems/ext4/about.html

到目前为止,我已经了解了这一点,并根据以下答案进行更新:

#!/usr/bin/python

import sys

READ_BYTES = 512
SUPERBLOCK_SIZE = 1024
SUPERBLOCK_OFFSETS = [
    0x0, 
    0x4, 
    0x8,
    0xC,
    0x10,
    0x14,
    0x18,
    0x1C,
    0x20,
    0x24,
    0x28,
    0x2C,
    0x30,
    0x34,
    0x36,
    0x38,
    0x3A,
    0x3C,
    0x3E,
    0x40,
    0x44,
    0x48,
    0x4C,
    0x50,
    0x52,
    0x54,
    0x58,
    0x5A,
    0x5C,
    0x60,
    0x64,
    0x68,
    0x78,
    0x88,
    0xC8,
    0xCC,
    0xCD,
    0xCE,
    0xD0,
    0xE0,
    0xE4,
    0xE8,
    0xEC,
    0xFC,
    0xFD,
    0xFE,
    0x100,
    0x104,
    0x108,
    0x10C,
    0x150,
    0x154,
    0x158,
    0x15C,
    0x15E,
    0x160,
    0x164,
    0x166,
    0x168,
    0x170,
    0x174,
    0x175,
    0x176,
    0x178,
    0x180,
    0x184,
    0x188,
    0x190,
    0x194,
    0x198,
    0x19C,
    0x1A0,
    0x1A8,
    0x1C8,
    0x1CC,
    0x1D0,
    0x1D4,
    0x1D8,
    0x1E0,
    0x200,
    0x240,
    0x244,
    0x248,
    0x24C,
    0x254,
    0x258,
    0x268,
    0x26C,
    0x270,
    0x274,
    0x275,
    0x276,
    0x277,
    0x278,
    0x279,
    0x27A,
    0x27C,
    0x27E,
    0x280,
    0x3FC
]
SUPERBLOCK_MAGIC_NUMBER_OFFSET = SUPERBLOCK_OFFSETS[15]

INODE_OFFSETS = [
    0x0, 
    0x2, 
    0x4,
    0x8,
    0xC,
    0x10,
    0x14,
    0x18,
    0x1A,
    0x1C,
    0x20,
    0x24,
    0x28,
    0x64,
    0x68,
    0x6C,
    0x70,
    0x74,
    0x80,
    0x82,
    0x84,
    0x88,
    0x8C,
    0x90,
    0x94,
    0x98,
    0x9C,
]
INODE_MAGIC_NUMBER_OFFSET = SUPERBLOCK_OFFSETS[9]
INODE_BLOCK_SIZE = INODE_OFFSETS[-1] + 32

class Partition:
    def __init__(self, path):
        self.path = path
        self.superblockMagicNumber = '\x53\xEF'
        self.superblocks = []
        self.inodes = []

        self.inodeMagicNumber = "\x0A\xF3"
        # self.inodeMagicNumber = '\x00\x00\x02\xEA'

    def findSuperblock(self):
        byteCount = 0

        filePointer = open(self.path, 'rb')
        data = filePointer.read(READ_BYTES)
        byteCount += len(data)

        while len(data):

            if self.superblockMagicNumber in data:

                # print info when magic number found
                print 'found magic number in read block:', byteCount, " (" + str(READ_BYTES) + " bytes per block)"
                magicNumberPosition = data.find(self.superblockMagicNumber)
                print 'position in data block:', magicNumberPosition
                print 'hex offset:', hex(magicNumberPosition)
                print " ".join([d.encode('hex') for d in data])

                # reset the file pointer to the begining of the superblock
                currentPosition = filePointer.tell()
                position = currentPosition - len(data) + magicNumberPosition - SUPERBLOCK_MAGIC_NUMBER_OFFSET
                filePointer.seek(position)
                superblockData = filePointer.read(SUPERBLOCK_SIZE)
                print "superblock data:"
                print " ".join([d.encode('hex') for d in superblockData])

                # # use the offsets to gather and set the superblock values
                superblockArgs = []
                for i in range(len(SUPERBLOCK_OFFSETS)-1) :
                    arg = superblockData[SUPERBLOCK_OFFSETS[i] : SUPERBLOCK_OFFSETS[i+1]]
                    superblockArgs.append(arg)
                arg = superblockData[SUPERBLOCK_OFFSETS[i+1] : SUPERBLOCK_SIZE]
                superblockArgs.append(arg)

                sb = Superblock(superblockArgs)
                for key, value in sb.__dict__.items():
                    values = []
                    for b in value:
                        values.append(b.encode('hex'))
                    print key, ":", " ".join(values)
                self.superblocks.append(sb)

            # reset the file pointer to end of data already read
            data = filePointer.read(READ_BYTES)
            byteCount += len(data)

        filePointer.close()

    def findInodes(self):

        byteCount = 0

        filePointer = open(self.path, 'rb')
        data = filePointer.read(READ_BYTES)
        byteCount += len(data)

        while data != None and len(data):

            if self.inodeMagicNumber in data:

                # print info when magic number found
                print 'found magic number in read block:', byteCount, " (" + str(READ_BYTES) + " bytes per block)"
                magicNumberPosition = data.find(self.inodeMagicNumber)
                print 'position in data block:', magicNumberPosition
                print 'hex offset:', hex(magicNumberPosition)
                print " ".join([d.encode('hex') for d in data])


                # reset the file pointer to the begining of the inode
                currentPosition = filePointer.tell()
                position = currentPosition - len(data) + magicNumberPosition - INODE_MAGIC_NUMBER_OFFSET
                filePointer.seek(position)
                indodeData = filePointer.read(INODE_BLOCK_SIZE)
                print "inode data:"
                print " ".join([d.encode('hex') for d in indodeData])

                # # use the offsets to gather and set the inode values
                indodeArgs = []
                for i in range(len(INODE_OFFSETS)-1) :
                    arg = indodeData[INODE_OFFSETS[i] : INODE_OFFSETS[i+1]]
                    indodeArgs.append(arg)
                arg = indodeData[INODE_OFFSETS[i+1] : INODE_BLOCK_SIZE]
                indodeArgs.append(arg)

                sb = Inode(indodeArgs)
                for key, value in sb.__dict__.items():
                    values = []
                    for b in value:
                        values.append(b.encode('hex'))
                    print key, ":", " ".join(values)
                self.inodes.append(sb)

            # reset the file pointer to end of data already read
            data = filePointer.read(READ_BYTES)
            byteCount += len(data)
            magicNumber = None

        filePointer.close()

class Superblock:
    def __init__(self, args=[]):

        if len(args):
            self.inodeCount = args[0]
            self.blockCount = args[1] 
            self.reservedBlockCount = args[2]
            self.freeBlockCount = args[3]
            self.freeInodeCount = args[4]
            self.firstDataBlock = args[5]
            self.logBlockSize = args[6]
            self.logClusterSize = args[7]
            self.blocksPerGroup = args[8]
            self.clustersPerGroup = args[9]
            self.inodesPerGroup = args[10]
            self.mountTime = args[11]
            self.writeTime = args[12]
            self.mountCount = args[13]
            self.maxMountCount = args[14]
            self.magic = args[15]
            self.state = args[16]
            self.errors = args[17]
            self.minorRevisionLevel = args[18]
            self.lastCheck = args[19]
            self.checkInterveal = args[20]
            self.creatorOS = args[21]
            self.revisionLevel = args[22]
            self.reservedBlocksUID = args[23]
            self.reservedBlocksDefaultGID = args[24]
            self.firstNonReservedInode = args[25]
            self.inodeSize = args[26]
            self.blockGroup = args[27]
            self.compatibleFeatures = args[28]
            self.incompatibleFeatures = args[29]
            self.readOnlyCompatibleFeatures = args[30]
            self.uuid = args[31]
            self.label = args[32]
            self.lastMounted = args[33]
            self.compression = args[34]
            self.preallocatedFileBlocks = args[35]
            self.preallocatedDirectoryBlocks = args[36]
            self.reservedGDTBlocks = args[37]
            self.journalUUID = args[38]
            self.journalInodeNumber = args[39]
            self.journalFileDeviceNumber = args[40]
            self.lastOrphan = args[41]
            self.hashSeed = args[42]
            self.hashVersion = args[43]
            self.journalBackupType = args[44]
            self.groupDescriptorSize = args[45]
            self.mountOptionsDefault = args[46]
            self.firstMetablockBlockGroup = args[47]
            self.makeFileSystemTime = args[48]
            self.journalInodesBackup = args[49]
            self.blockCountHigh = args[50]
            self.reserverdBlockCountHigh = args[51]
            self.freeBlockCountHigh = args[52]
            self.minimumInodeSize = args[53]
            self.newInodeReservationSize = args[54]
            self.miscFlags = args[55]
            self.raidStride = args[56]
            self.multiMountPreventionInterval = args[57]
            self.multiMountPreventionData = args[58]
            self.raidStripeWidth = args[59]
            self.flexibleBlockGroupSize = args[60]
            self.metadataChecksumAlgorithmType = args[61]
            self.reservedPad = args[62]
            self.kilobytesWritten = args[63]
            self.snapshotInodeNumber = args[64]
            self.snapshotID = args[65]
            self.snapshotReservedBlockCount = args[66]
            self.snapshotList = args[67]
            self.errorCount = args[68]
            self.firstErrorTime = args[69]
            self.firstErrorInode = args[70]
            self.firstErrorBlock = args[71]
            self.firstErrorFunction = args[72]
            self.firstErrorLine = args[73]
            self.lastErrorTime = args[74]
            self.lastErrorInode = args[75]
            self.lastErrorLine = args[76]
            self.lastErrorBlock = args[77]
            self.lastErrorFunction = args[78]
            self.mountOptions = args[79]
            self.inodeOfUserQuotaFile = args[80]
            self.infodeOfGroupQuotaFile = args[81]
            self.overheadBlocks = args[82]
            self.superblockBackups = args[83]
            self.encryptionAlgorithms = args[84]
            self.encryptionSalt = args[85]
            self.inodeLostAndFound = args[86]
            self.inodeProjectQuota = args[87]
            self.checksumSeed = args[88]
            self.wtimeHigh = args[89]
            self.mtimeHigh = args[90]
            self.makeFileSystemTimeHigh = args[91]
            self.lastCheckHigh = args[92]
            self.firstErrorTimeHigh = args[93]
            self.lastErrorTimeHigh = args[94]
            self.zeroPadding = args[95]
            self.encoding = args[96]
            self.encodingFlags = args[97]
            self.reservedPadding = args[98]
            self.checksum = args[99]

        else:
            self.inodeCount = None
            self.blockCount = None
            self.reservedBlockCount = None
            self.freeBlockCount = None
            self.freeInodeCount = None
            self.firstDataBlock = None
            self.logBlockSize = None
            self.logClusterSize = None
            self.blocksPerGroup = None
            self.clustersPerGroup = None
            self.inodesPerGroup = None
            self.mountTime = None
            self.writeTime = None
            self.mountCount = None
            self.maxMountCount = None
            self.magic = None
            self.state = None
            self.errors = None
            self.minorRevisionLevel = None
            self.lastCheck = None
            self.checkInterveal = None
            self.creatorOS = None
            self.revisionLevel = None
            self.reservedBlocksUID = None
            self.reservedBlocksDefaultGID = None
            self.firstNonReservedInode = None
            self.inodeSize = None
            self.blockGroup = None
            self.compatibleFeatures = None
            self.incompatibleFeatures = None
            self.readOnlyCompatibleFeatures = None
            self.uuid = None
            self.label = None
            self.lastMounted = None
            self.compression = None
            self.preallocatedFileBlocks = None
            self.preallocatedDirectoryBlocks = None
            self.reservedGDTBlocks = None
            self.journalUUID = None
            self.journalInodeNumber = None
            self.journalFileDeviceNumber = None
            self.lastOrphan = None
            self.hashSeed = None
            self.hashVersion = None
            self.journalBackupType = None
            self.groupDescriptorSize = None
            self.mountOptionsDefault = None
            self.firstMetablockBlockGroup = None
            self.makeFileSystemTime = None
            self.journalInodesBackup = None
            self.blockCountHigh = None
            self.reserverdBlockCountHigh = None
            self.freeBlockCountHigh = None
            self.minimumInodeSize = None
            self.newInodeReservationSize = None
            self.miscFlags = None
            self.raidStride = None
            self.multiMountPreventionInterval = None
            self.multiMountPreventionData = None
            self.raidStripeWidth = None
            self.flexibleBlockGroupSize = None
            self.metadataChecksumAlgorithmType = None
            self.reservedPad = None
            self.kilobytesWritten = None
            self.snapshotInodeNumber = None
            self.snapshotID = None
            self.snapshotReservedBlockCount = None
            self.snapshotList = None
            self.errorCount = None
            self.firstErrorTime = None
            self.firstErrorInode = None
            self.firstErrorBlock = None
            self.firstErrorFunction = None
            self.firstErrorLine = None
            self.lastErrorTime = None
            self.lastErrorInode = None
            self.lastErrorLine = None
            self.lastErrorBlock = None
            self.lastErrorFunction = None
            self.mountOptions = None
            self.inodeOfUserQuotaFile = None
            self.infodeOfGroupQuotaFile = None
            self.overheadBlocks = None
            self.superblockBackups = None
            self.encryptionAlgorithms = None
            self.encryptionSalt = None
            self.inodeLostAndFound = None
            self.inodeProjectQuota = None
            self.checksumSeed = None
            self.wtimeHigh = None
            self.mtimeHigh = None
            self.makeFileSystemTimeHigh = None
            self.lastCheckHigh = None
            self.firstErrorTimeHigh = None
            self.lastErrorTimeHigh = None
            self.zeroPadding = None
            self.encoding = None
            self.encodingFlags = None
            self.reservedPadding = None
            self.checksum = None

class Inode:
    def __init__(self, args=[]):

        if len(args):
            self.fileMode = args[0]
            self.uidLow = args[1] 
            self.sizeLow = args[2]
            self.accessTime = args[3]
            self.changeTime = args[4]
            self.modificationTime = args[5]
            self.deletionTime = args[6]
            self.gidLow = args[7]
            self.linkCount = args[8]
            self.blockCountLow = args[9]
            self.flags = args[10]
            self.osd1 = args[11]
            self.blockMap = args[12]
            self.fileVersion = args[13]
            self.extendedAttributeBlockLow = args[14]
            self.fileDirectorySizeHigh = args[15]
            self.fragmentAddress = args[16]
            self.osd2 = args[17]
            self.extraSize = args[18]
            self.checksumHigh = args[19]
            self.extraChangeTime = args[20]
            self.extraModificationTime = args[21]
            self.extraAccessTime = args[22]
            self.creationTime = args[23]
            self.versionHigh = args[24]
            self.projectID = args[25]

        else:
            self.fileMode = None
            self.uidLow = None
            self.sizeLow = None
            self.accessTime = None
            self.changeTime = None
            self.modificationTime = None
            self.deletionTime = None
            self.gidLow = None
            self.linkCount = None
            self.blockCountLow = None
            self.flags = None
            self.osd1 = None
            self.blockMap = None
            self.fileVersion = None
            self.extendedAttributeBlockLow = None
            self.fileDirectorySizeHigh = Non
            self.fragmentAddress = None
            self.osd2 = None
            self.extraSize = None
            self.checksumHigh = None
            self.extraChangeTime = None
            self.extraModificationTime = None
            self.extraAccessTime = None
            self.creationTime = None
            self.versionHigh = None
            self.projectID = None

    def printSize(self):



p = Partition(sys.argv[1])
#p.findSuperblock()
p.findInodes()

答案1

没有真的ext4 inode 格式的幻数(稍后会详细介绍),因此您所要求的并不完全可能。那里e2fsprogs 代码中的“findsuper”和 ext3grep 等工具将扫描设备并查找超级块幻数(可能是备份),然后您可以尝试使用 e2fsck 从中恢复。

由于ext4具有相对固定的磁盘格式,因此扫描查找文件的需要相对较少。找到超级块更容易,然后只需计算组描述符(或其备份)的位置,然后直接读取所有索引节点,因为它们的位置在格式化时是固定的。

然而,在写这篇文章时我意识到如果你真的想走这条路,一些神奇的数字已经悄悄进入了 inode 主体,还有一些你可能可以使用的其他功能。

如果 inode 足够大(256 字节或更大,应该是所有现代文件系统的默认值),并且 xattr 足够小以适应(对于 SELinux 应该如此),则快速 xattr 功能会将 xattr 存储在 inode 内。

EXT4_XATTR_MAGIC  0xEA020000

i_extra_bytes这将存储在额外 inode 字段之后的 inode 的第二个 128 字节中。至少,这将在 4 字节边界上正确对齐,并且在 inode 开始后可能约为 128+32 字节(取决于 inode 写入的内核版本)。

其次,使用范围格式的索引节点(在较新的文件系统中也很常见)将在字段的开头存储一个范围魔术字段,i_blocks以指示此功能的使用:

EXT4_EXT_MAGIC    0xf30a

如果您在 inode 中的适当偏移处找到其中一个或两个值,则您很可能拥有一个 inode。

相关内容