The metadata category of data contains the descriptive data for a file or directory. This section describes where UFS stores the data and how we can analyze them.
Like ExtX, UFS uses inode data structures to store file and directory metadata. UFS2 also has extended attributes to store additional descriptive data about a file. We will examine each of these data structures separately.
Inodes in UFS have the same basic concept as we saw with ExtX. Each cylinder group has an inode table, whose relative location is given in the superblock. With UFS1, all inodes are initialized when the file system is created. UFS2 has dynamic inodes where they are initialized when they are needed, and the space in the inode table can be used for file content if all other blocks in the file system have been used. Each file and directory uses one inode, and it contains the address of the blocks that a file has allocated, the file size, and temporal information.
UFS inodes have 12 direct block pointers, one indirect block pointer, one double indirect block pointer, and one triple indirect block pointer. The address in each block pointer is for a full block, except for the final pointer, which could be for one or more fragments. The file size is used to determine how many fragments are being used. The block pointers are 32-bit values in UFS1 and 64-bit values in UFS2.
UFS supports sparse files, which were discussed in Chapter 8, "File System Analysis." If a file has not defined the contents of part of a file or if the block is all zeros, the OS will probably not allocate a block. Instead, it will place a 0 in the block pointer and output a block of zeros when that part of the file is read.
The same file types and permissions are used with UFS as we saw with ExtX. An inode has a last modified, last accessed, and last changed time values, but it does not have a deleted time value. Although UFS2 added a create time value to the inode. The times are saved as the number of seconds since January 1, 1970 12:00 GMT, and the number of nanoseconds also is given for finer resolution. The time values in UFS1 are 32 bits and 64 bits in UFS2.
Each inode is given an address, starting with 0. Note that this is different from ExtX, which started with inode 1. Inodes 0 and 1 are reserved, but not used for anything. Inode 1 used to be used for bad blocks. Inode 2 is reserved for the root directory. The allocation status of any inode is determined using the inode bitmap, which is located in the group descriptor. To determine which group an inode is in, its address is divided by the number of inodes per group, which can be found in the superblock. The UFS1 and UFS2 inode data structures are described with file system images in the next chapter.
Extended attributes were added to UFS2, and they provide an additional location to store descriptive data about a file or directory. Extended attributes are a name value pair, and there are currently two "types": user name space and system name space. Any user that can read the file's contents can read the file's user name space attributes. Although only privileged users can read the file's system name space attributes.
The UFS2 inode has three fields for the extended attributes. One is a size value, and the other two are block addresses where the attributes are stored. The blocks are filled with variable length data structures that contain a header and the attribute value. A user can set the attributes with the setextattr command, and applications can set them with special system calls. The extended attribute data structure is shown in Chapter 17.
Here is the output from running istat on our example file system image. This inode is manually processed in Chapter 17 and is included here as a reference about what data exist.
# istat -f openbsd -z UTC openbsd.dd 3 inode: 3 Allocated Group: 0 uid / gid: 0 / 0 mode: -rw-r--r-- size: 1274880 num of links: 1 Inode Times: Accessed: Tue Aug 3 14:12:56 2004 File Modified: Tue Aug 3 14:13:14 2004 Inode Modified: Tue Aug 3 14:13:14 2004 Direct Blocks: 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 [REMOVED] 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 Indirect Blocks: 384 385 386 387 388 389 390 391
We can see that this file is 1,247,880 bytes, so it needs more than 12 of the 8KB blocks. The bottom of the istat output lists each fragment that the file has allocated. Recall that only the block address is given in the inode, but istat will give the addresses of all fragments in the block. The last line of the 'Direct Blocks' section shows that only five of the eight fragments in block 1576 are being used. Therefore, fragments 1581 to 1583 could be used by another file. Also notice that block 384 is being used as an indirect block pointer.
The documented allocation method of UFS inodes is the same that we saw for ExtX. An OS can choose any algorithm, but the typical policy is to allocate an inode for a directory in a new cylinder group that has a less than average number of directories and a greater than average number of available blocks. The inode for a file is allocated in the same cylinder group as the parent directory, if space exists. When looking for a free inode in a cylinder group, a first-available strategy is used.
When the inode is allocated, its contents are cleared and the M-time, A-time, C-time, and Create-time (for UFS2) are set to the current time. The link count is set to 1 for files and 2 for directories to account for the '.' name inside of the directory.
When a file is deleted, BSD systems and Solaris will clear the block pointers inside the inode, clear the size, and clear the mode. Therefore, the full size, content location, and type of file will not be known. The contents of the indirect block pointers are not cleared, though, so searching for a block of indirect block pointers could help during recovery. A special tool would likely be needed to find a block full of 32-bit or 64-bit addresses. Note that the differences between the deletion routines with this and what we saw with Linux and ExtX is that Linux did not clear the mode field, but it did clear the contents of the Ext3 indirect block pointers.
The time updating for OpenBSD 3, FreeBSD 5, and Sun Solaris 9 systems are the same as reported in ExtX for Fedora Core 2. Namely, when a file is created, all times are set to the current time. The M- and C-times of the parent directory are also updated. When a file is moved, the new file has an updated C-time, but the M- and A-times remain the same. When a file is copied, the A-time on the source file is updated, and the destination file has all new M-, A-, and C-times. When a file is deleted, its M- and C-times are updated.
When we analyze the metadata category of data, we are looking for descriptive data about a file so that we can sort it and obtain more information. To do this, we need to be able to locate the inodes, determine their allocation status, and process the extended attributes that might exist.
To locate a specific inode, we need to first identify its group, and we can do that by dividing the inode address by the number of inodes per group. Next, we need to locate the inode table by using the starting fragment of the group and the inode table offset, which is given in the superblock. With UFS1 we also will need to calculate what the staggering offset for the group is. Lastly, we locate the specific inode in the table by subtracting the inode address from the address of the first inode in the group.
Processing an inode is fairly straightforward. The mode field and time values are encoded and must be processed before sense can be made of them. To locate the content, the addresses given in the 12 direct block pointers are used. If the file is larger, the indirect blocks must be read and their contents processed as a list of block addresses. Any pointer with a value of 0 is a sparse block and corresponds to a block of all zeros. For the final block pointer, consider the total size of the file and process only that amount. The final block could be a fragment, and the next fragment might be used by another file.
To determine the allocation status of an inode, we use the inode bitmap for the group, which is located in the group descriptor. The group descriptor is located using the starting address of the group and offset values from the superblock. If the inode's bit is set to 1, it is allocated.
It is sometimes useful to examine all the unallocated inode entries because they might contain temporal or other information from deleted files. We can do this by searching the bitmaps for inodes whose bits are 0 and processing inodes that are non-zero. Inodes that are all zeros are typically ones that have not been used and are still in an initialized state.
The extended attributes also might have evidence or hidden data and should be examined. We do this by reading the blocks that are listed in the inode and processing each entry. The user can assign the name and value pairs.
Most of the analysis considerations of ExtX metadata apply to UFS. Deleted files have the block pointers, size, and mode cleared from the inode, but UFS preserves the state of the indirect block pointers. Therefore, recovery could try to locate an indirect block pointer and reconstruct the file. The M- and C-time in an unallocated inode entry might reflect the time that the corresponding file was deleted. The allocation strategies can be used when carving data by focusing attention on only a specific group instead of the entire file system.
As we saw with ExtX, the touch command can be used to modify the M- and A-times of any file. Therefore, it is best to have a second source of temporal data to help determine the reliability of the time values. The C-time of the file is updated when the touch command is used. The time values are stored in UTC, so your analysis tool will need to convert that to the time zone where the computer was actually located so that the displayed time values are correct.
The BSD systems that I tested wrote 0s for the unused bytes in the final fragment of a file, also called slack space. Therefore, the only place to find data from deleted files is in the unallocated fragments. Fortunately, only the minimum number of fragments are allocated and wiped. Therefore, a block still might contain deleted data in the fragments that have not been reallocated. Slack space might contain hidden data, though.
The extended attributes of a UFS2 file system could be used to hide small amounts of data. They are limited to two blocks in size, but test if your analysis tools will show you this content and if they include the content in a keyword search.
The dynamic UFS2 inode tables also allow another area for data hiding. The OS does not initialize a block of inode entries in the inode table until it is needed. Therefore, data could be placed in the unused areas without being noticed until the OS reclaims the area and erases the data.
Part I: Foundations
Digital Investigation Foundations
Hard Disk Data Acquisition
Part II: Volume Analysis
Multiple Disk Volumes
Part III: File System Analysis
File System Analysis
FAT Concepts and Analysis
FAT Data Structures
NTFS Data Structures
Ext2 and Ext3 Concepts and Analysis
Ext2 and Ext3 Data Structures
UFS1 and UFS2 Concepts and Analysis
UFS1 and UFS2 Data Structures