#!/usr/bin/python # This is a simple Python script that will # get metadata from an ext2/3/4 filesystem inside # of an image file. # # Developed for PentesterAcademy by Dr. Phil Polstra import sys import os.path import subprocess import struct import time from math import log # these are simple functions to make conversions easier def getU32(data, offset=0): return struct.unpack('= self.firstMetaBlockGroup: mbgSize = self.blockSize / 32 retVal = (bgNo % mbgSize == 0) or ((bgNo + 1) % mbgSize == 0) or ((bgNo + 2) % mbgSize == 0) else: # if we got this far we must have default with every bg having sb and gdt retVal = True return retVal class GroupDescriptor(): def __init__(self, data, wide=False): self.wide = wide self.blockBitmapLo=getU32(data) #/* Blocks bitmap block */ self.inodeBitmapLo=getU32(data, 4) #/* Inodes bitmap block */ self.inodeTableLo=getU32(data, 8) #/* Inodes table block */ self.freeBlocksCountLo=getU16(data, 0xc)#/* Free blocks count */ self.freeInodesCountLo=getU16(data, 0xe)#/* Free inodes count */ self.usedDirsCountLo=getU16(data, 0x10) #/* Directories count */ self.flags=getU16(data, 0x12) #/* EXT4_BG_flags (INODE_UNINIT, etc) */ self.flagList = self.printFlagList() self.excludeBitmapLo=getU32(data, 0x14) #/* Exclude bitmap for snapshots */ self.blockBitmapCsumLo=getU16(data, 0x18) #/* crc32c(s_uuid+grp_num+bbitmap) LE */ self.inodeBitmapCsumLo=getU16(data, 0x1a) #/* crc32c(s_uuid+grp_num+ibitmap) LE */ self.itableUnusedLo=getU16(data, 0x1c) #/* Unused inodes count */ self.checksum=getU16(data, 0x1e) #/* crc16(sb_uuid+group+desc) */ if wide==True: self.blockBitmapHi=getU32(data, 0x20) #/* Blocks bitmap block MSB */ self.inodeBitmapHi=getU32(data, 0x24) #/* Inodes bitmap block MSB */ self.inodeTableHi=getU32(data, 0x28) #/* Inodes table block MSB */ self.freeBlocksCountHi=getU16(data, 0x2c) #/* Free blocks count MSB */ self.freeInodesCountHi=getU16(data, 0x2e) #/* Free inodes count MSB */ self.usedDirsCountHi=getU16(data, 0x30) #/* Directories count MSB */ self.itableUnusedHi=getU16(data, 0x32) #/* Unused inodes count MSB */ self.excludeBitmapHi=getU32(data, 0x34) #/* Exclude bitmap block MSB */ self.blockBitmapCsumHi=getU16(data, 0x38)#/* crc32c(s_uuid+grp_num+bbitmap) BE */ self.inodeBitmapCsumHi=getU16(data, 0x3a)#/* crc32c(s_uuid+grp_num+ibitmap) BE */ self.reserved=getU32(data, 0x3c) def printFlagList(self): flagList = [] if self.flags & 0x1: #inode table and bitmap are not initialized (EXT4_BG_INODE_UNINIT). flagList.append('Inode Uninitialized') if self.flags & 0x2: #block bitmap is not initialized (EXT4_BG_BLOCK_UNINIT). flagList.append('Block Uninitialized') if self.flags & 0x4: #inode table is zeroed (EXT4_BG_INODE_ZEROED). flagList.append('Inode Zeroed') return flagList def prettyPrint(self): for k, v in sorted(self.__dict__.iteritems()) : print k+":", v # This class combines informaton from the block group descriptor # and the superblock to more fully describe the block group class ExtendedGroupDescriptor(): def __init__(self, bgd, sb, bgNo): self.blockGroup = bgNo self.startBlock = sb.groupStartBlock(bgNo) self.endBlock = sb.groupEndBlock(bgNo) self.startInode = sb.groupStartInode(bgNo) self.endInode = sb.groupEndInode(bgNo) self.flags = bgd.printFlagList() self.freeInodes = bgd.freeInodesCountLo if bgd.wide: self.freeInodes += bgd.freeInodesCountHi * pow(2, 16) self.freeBlocks = bgd.freeBlocksCountLo if bgd.wide: self.freeBlocks += bgd.freeBlocksCountHi * pow(2, 16) self.directories = bgd.usedDirsCountLo if bgd.wide: self.directories += bgd.usedDirsCountHi * pow(2, 16) self.checksum = bgd.checksum self.blockBitmapChecksum = bgd.blockBitmapCsumLo if bgd.wide: self.blockBitmapChecksum += bgd.blockBitmapCsumHi * pow(2, 16) self.inodeBitmapChecksum = bgd.inodeBitmapCsumLo if bgd.wide: self.inodeBitmapChecksum += bgd.inodeBitmapCsumHi * pow(2, 16) # now figure out the layout and store it in a list (with lists inside) self.layout = [] self.nonDataBlocks = 0 # for flexible block groups must make an adjustment fbgAdj = 1 if 'Flexible Block Groups' in sb.incompatibleFeaturesList: if bgNo % sb.groupsPerFlex == 0: # only first group in flex block affected fbgAdj = sb.groupsPerFlex if sb.groupHasSuperblock(bgNo): self.layout.append(['Superblock', self.startBlock, self.startBlock]) gdSize = sb.groupDescriptorSize() * sb.blockGroups() / sb.blockSize self.layout.append(['Group Descriptor Table', self.startBlock + 1, self.startBlock + gdSize]) self.nonDataBlocks += gdSize + 1 if sb.reservedGdtBlocks > 0: self.layout.append(['Reserved GDT Blocks', self.startBlock + gdSize + 1, \ self.startBlock + gdSize + sb.reservedGdtBlocks]) self.nonDataBlocks += sb.reservedGdtBlocks bbm = bgd.blockBitmapLo if bgd.wide: bbm += bgd.blockBitmapHi * pow(2, 32) self.layout.append(['Data Block Bitmap', bbm, bbm]) # is block bitmap in this group (not flex block group, etc) if sb.groupFromBlock(bbm) == bgNo: self.nonDataBlocks += fbgAdj ibm = bgd.inodeBitmapLo if bgd.wide: ibm += bgd.inodeBitmapHi * pow(2, 32) self.layout.append(['Inode Bitmap', ibm, ibm]) # is inode bitmap in this group? if sb.groupFromBlock(ibm) == bgNo: self.nonDataBlocks += fbgAdj it = bgd.inodeTableLo if bgd.wide: it += bgd.inodeTableHi * pow(2, 32) itBlocks = (sb.inodesPerGroup * sb.inodeSize) / sb.blockSize self.layout.append(['Inode Table', it, it + itBlocks - 1]) # is inode table in this group? if sb.groupFromBlock(it) == bgNo: self.nonDataBlocks += itBlocks * fbgAdj self.layout.append(['Data Blocks', self.startBlock + self.nonDataBlocks, self.endBlock]) def prettyPrint(self): print "" print 'Block Group: ' + str(self.blockGroup) print 'Flags: %r ' % self.flags print 'Blocks: %s - %s ' % (self.startBlock, self.endBlock) print 'Inodes: %s - %s ' % (self.startInode, self.endInode) print 'Layout:' for item in self.layout: print ' %s %s - %s' % (item[0], item[1], item[2]) print 'Free Inodes: %u ' % self.freeInodes print 'Free Blocks: %u ' % self.freeBlocks print 'Directories: %u ' % self.directories print 'Checksum: 0x%x ' % self.checksum print 'Block Bitmap Checksum: 0x%x ' % self.blockBitmapChecksum print 'Inode Bitmap Checksum: 0x%x ' % self.inodeBitmapChecksum class ExtMetadata(): def __init__(self, filename, offset): # read first sector if not os.path.isfile(sys.argv[1]): print("File " + str(filename) + " cannot be openned for reading") exit(1) with open(str(filename), 'rb') as f: f.seek(1024 + int(offset) * 512) sbRaw = str(f.read(1024)) self.superblock = Superblock(sbRaw) # read block group descriptors self.blockGroups = self.superblock.blockGroups() if self.superblock.descriptorSize != 0: self.wideBlockGroups = True self.blockGroupDescriptorSize = 64 else: self.wideBlockGroups = False self.blockGroupDescriptorSize = 32 # read in group descriptors starting in block 1 with open(str(filename), 'rb') as f: f.seek(int(offset) * 512 + self.superblock.blockSize) bgdRaw = str(f.read(self.blockGroups * self.blockGroupDescriptorSize)) self.bgdList = [] for i in range(0, self.blockGroups): bgd = GroupDescriptor(bgdRaw[i * self.blockGroupDescriptorSize:], self.wideBlockGroups) ebgd = ExtendedGroupDescriptor(bgd, self.superblock, i) self.bgdList.append(ebgd) def prettyPrint(self): self.superblock.prettyPrint() for bgd in self.bgdList: bgd.prettyPrint() def usage(): print("usage " + sys.argv[0] + " \nReads superblock from an image file") exit(1) def main(): if len(sys.argv) < 3: usage() # read first sector if not os.path.isfile(sys.argv[1]): print("File " + sys.argv[1] + " cannot be openned for reading") exit(1) emd = ExtMetadata(sys.argv[1], sys.argv[2]) emd.prettyPrint() if __name__ == "__main__": main()