我正在尝试编写一个 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。