Extended attributes

The extended filesystem family supports extended attributes. The first use of extended attributes was for Access Control Lists (ACL). While it is certainly not uncommon for extended attributes to house ACLs today, they can be used to store almost anything as a user attribute if the attribute name begins with “user.”. Something to keep in mind is that older kernels required the correct set of mounting options to be used when ACLs are implemented. This is another reason to capture the mount information during your live analysis.

If the extended attribute is small, it can be stored in the extra space between inodes. Currently there are 100 bytes of extra space. Larger extended attributes can be stored in a data block pointed to by file_acl in the inode. An attacker might use a user attribute to hide information on a hacked system. This is similar to using Alternate Data Streams (ADS) to attach hidden information to a file on a NTFS filesystem. There is one important difference between user attributes and ADS, however. There are standard tools for displaying extended attributes on Linux, and you need not know the exact attribute name to use them, as is the case with ADS.

Whether stored in inodes or data blocks, all extended attribute lists begin with a header. The header does vary in these two cases, however. The header inside the inode consists of a 4-byte magic number only. The extended attributes header structure for attributes stored in the inode and data block are shown in Table 7.22 and Table 7.23, respectively.

Table 7.22. Extended attribute header for attributes in an inode.

Offset Size Name Description
0x0 4 Magic no 0xEA020000

Table 7.23. Extended attribute header for attributes in a data block.

Offset Size Name Description
0x0 4 Magic no 0xEA020000
0x4 4 Ref count Reference count
0x8 4 Blocks Blocks used to store extended attributes
0xC 4 Hash Hash
0x10 4 Checksum Checksum
0x14 12 Reserved Should be zeroed

The extended attribute entry or entries follow(s) the header. The extended attribute entry structure is shown in Table 7.24. Note the use of a name index in order to reduce storage.

Table 7.24. Extended attribute entry structure.

Offset Size Name Description
0x0 1 Name Length of attribute name
0x1 1 Name 0x0 = no prefix 0x1 = user. prefix 0x2 = system.posix_acl_access 0x3 = system.posix_acl_default
0x2 2 Value offs Offset from first inode entry or start of block
0x4 4 Value block Disk block where value stored or zero for this block
0x8 4 Value size Length of value
0xC 4 Hash Hash for attribs in block or zero if in inode
0x10 Name Attribute name w/o trailing NULL

The standard Linux commands for displaying extended attribute and ACL information are getfattr and getfacl, respectively. Not surprisingly, the commands to alter extended attributes and ACLs are setfattr and setfacl, respectively. See the man pages for details on these commands. Basic usage of these commands is demonstrated in Figure 7.26.

FIGURE 7.26

Using the commands to set and get extended attributes.

The following code will add extended attribute support to our extfs Python module. There are no previously undiscussed techniques in this code.

“”” printExtAttrPrefix. Converts a 1-byte prefix code for an extended attribute name to a string. Usage: prefixString = printExtAttrPrefix(index) “”” def printExtAttrPrefix(index): if index == 0 or index > 8:

return “” elif index == 1: return “user.” elif index == 2: return “system.posix_acl_access” elif index == 3:

return “system.posix_acl_default”

elif index == 4: return “trusted.” elif index == 6: return “security.” elif index == 7: return “system.” elif index == 8:

return “system.richacl”

“””

Class ExtAttrEntry. Stores the raw extended attribute structure with the prefix prepended to the attribute name. Usage: ea = ExtAttrEntry(data, offset=0) where data is a packed string representing the extended attribute and offset is the starting point in this block of data for this entry. “”” class ExtAttrEntry():

def init(self, data, offset=0):

self.nameLen = getU8(data, offset + 0x0) self.nameIndex = getU8(data, offset + 0x1) self.valueOffset = getU16(data, offset + 0x2) self.valueBlock = getU32(data, offset + 0x4) self.valueSize = getU32(data, offset + 0x8) self.valueHash = getU32(data, offset + 0xc) self.name = printExtAttrPrefix(self.nameIndex) + \

str(data[offset + 0x10: offset + 0x10 + self.nameLen])

“”” Usage:

getExtAttrsFromBlock(imageFilename, offset, blockNo, blocksize) where imageFilename is a raw ext2/ext3/ext4 image, offset is the offset in 512 byte sectors to the start of the filesystem, blockNo is the data block holding the extended attributes, and blocksize is the filesystem block size (default=4k). “”” def getExtAttrsFromBlock(imageFilename, offset, \

blockNo, blockSize=4096):

data = getDataBlock(imageFilename, offset, \

blockNo, blockSize) return getExtAttrsHelper(False, imageFilename, \ offset, data, blockSize)

“”” Usage:

getExtAttrsInInode(imageFilename, offset, data, blocksize) where imageFilename is a raw ext2/ext3/ext4 image, offset is the offset in 512 byte sectors to the start of the filesystem, data is the packed string holding the extended attributes, and blocksize is the filesystem block size (default=4k). “”” def getExtAttrsInInode(imageFilename, offset, data, blockSize=4096):

return getExtAttrsHelper(True, imageFilename, offset, data, blockSize)

This is a helper function for the proceeding two functions def getExtAttrsHelper(inInode, imageFilename, offset, data, blockSize=4096):

first four bytes are magic number retVal = {} if getU32(data, 0) != 0xEA020000:

return retVal done = False if inInode:

i = 4 else: i = 32 while not done:

eae = ExtAttrEntry(data, i) # is this an extended attribute or not if eae.nameLen == 0 and eae.nameIndex == 0 and eae.valueOffset == 0 \

and eae.valueBlock == 0:

done = True

else: # in the inode or external block? if eae.valueBlock == 0:

v = data[eae.valueOffset : eae.valueOffset + eae.valueSize] else:

v = getDataBlock(imageFilename, offset, eae.valueBlock, \ blockSize)[eae.valueOffset : eae.valueOffset + eae.valueSize] retVal[eae.name] = v i += eae.nameLen + 12 if i >= len(data):

done = True return retVal

The following script can be used to print out extended attributes for an inode in an image. I include this mostly for completeness. If you mount the filesystem image, it is easy to list out these attributes using standard system tools discussed in this section.

!/usr/bin/python

# igetattr.py

#

This is a simple Python script that will # print out extended attributes in an inode from an ext2/3/4 # filesystem inside of an image file.

#

Developed for PentesterAcademy # by Dr. Phil Polstra (@ppolstra) import extfs import sys import os.path import subprocess import struct import time from math import log def usage():

print(“usage “ + sys.argv[0] + “ <offset> \n”\

“Displays extended attributes in 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 = extfs.ExtMetadata(sys.argv[1], sys.argv[2]) # get inode location

inodeLoc = extfs.getInodeLoc(sys.argv[3], \ emd.superblock.inodesPerGroup) offset = emd.bgdList[inodeLoc[0]].inodeTable \ emd.superblock.blockSize + inodeLoc[1] emd.superblock.inodeSize with open(str(sys.argv[1]), ‘rb’) as f:

f.seek(offset + int(sys.argv[2]) * 512) data = str(f.read(emd.superblock.inodeSize)) inode = extfs.Inode(data, emd.superblock.inodeSize) if inode.hasExtendedAttributes:

is it in the inode slack or a datablock if inode.extendAttribs == 0:

attrs = extfs.getExtAttrsInInode(imageFilename, offset, \ data[inode.inodeSize: ], emd.superblock.blockSize) else:

attrs = extfs.getExtAttrsFromBlock(imageFilename, offset, \ blockNo, emd.superblock.blockSize) for k, v in attrs.iteritems():

print “%s : %s” % (k, v) else:

print ‘Inode %s has no extended attributes’ % (sys.argv[3]) if name == “main”: main()

results matching ""

    No results matching ""