Merge pull request #614 from kevthehermit/plugin/mftscan

Create MFTScanner plugin
This commit is contained in:
ikelos
2022-01-16 14:08:08 +00:00
committed by GitHub
3 changed files with 652 additions and 0 deletions
@@ -0,0 +1,164 @@
# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import datetime
import logging
from volatility3.framework import constants, renderers, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework import exceptions
from volatility3.framework.renderers import conversion, format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import mft
from volatility3.plugins import timeliner, yarascan
vollog = logging.getLogger(__name__)
class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Scans for MFT FILE objects present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.VersionRequirement(name = 'yarascanner', component = yarascan.YaraScanner,
version = (2, 0, 0)),
]
def _generator(self):
layer = self.context.layers[self.config['primary']]
# Yara Rule to scan for MFT Header Signatures
rules = yarascan.YaraScan.process_yara_options({'yara_rules': '/FILE0|FILE\*|BAAD/'})
# Read in the Symbol File
symbol_table = intermed.IntermediateSymbolTable.create(context = self.context,
config_path = self.config_path,
sub_path = "windows",
filename = "mft",
class_types = {
'FILE_NAME_ENTRY': mft.MFTFileName,
'MFT_ENTRY': mft.MFTEntry
})
# get each of the individual Field Sets
mft_object = symbol_table + constants.BANG + "MFT_ENTRY"
attribute_object = symbol_table + constants.BANG + "ATTRIBUTE"
header_object = symbol_table + constants.BANG + "ATTR_HEADER"
si_object = symbol_table + constants.BANG + "STANDARD_INFORMATION_ENTRY"
fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY"
# Scan the layer for Raw MFT records and parse the fields
for offset, _rule_name, _name, _value in layer.scan(context = self.context,
scanner = yarascan.YaraScanner(rules = rules)):
try:
mft_record = self.context.object(mft_object, offset = offset, layer_name = layer.name)
# We will update this on each pass in the next loop and use it as the new offset.
attr_base_offset = mft_record.FirstAttrOffset
attr_header = self.context.object(header_object,
offset = offset + attr_base_offset,
layer_name = layer.name)
# There is no field that has a count of Attributes
# Keep Attempting to read attributes until we get an invalid attr_header.AttrType
while attr_header.AttrType.is_valid_choice:
vollog.debug(f"Attr Type: {attr_header.AttrType.lookup()}")
# Offset past the headers to the attribute data
attr_data_offset = offset + attr_base_offset + self.context.symbol_space.get_type(
attribute_object).relative_child_offset("Attr_Data")
# MFT Flags determine the file type or dir
# If we don't have a valid enum, coerce to hex so we can keep the record
try:
mft_flag = mft_record.Flags.lookup()
except ValueError:
mft_flag = hex(mft_record.Flags)
# Standard Information Attribute
if attr_header.AttrType.lookup() == 'STANDARD_INFORMATION':
attr_data = self.context.object(si_object, offset = attr_data_offset, layer_name = layer.name)
yield 0, (
format_hints.Hex(attr_data_offset),
mft_record.get_signature(),
mft_record.RecordNumber,
mft_record.LinkCount,
mft_flag,
renderers.NotApplicableValue(),
attr_header.AttrType.lookup(),
conversion.wintime_to_datetime(attr_data.CreationTime),
conversion.wintime_to_datetime(attr_data.ModifiedTime),
conversion.wintime_to_datetime(attr_data.UpdatedTime),
conversion.wintime_to_datetime(attr_data.AccessedTime),
renderers.NotApplicableValue(),
)
# File Name Attribute
if attr_header.AttrType.lookup() == 'FILE_NAME':
attr_data = self.context.object(fn_object, offset = attr_data_offset, layer_name = layer.name)
file_name = attr_data.get_full_name()
# If we don't have a valid enum, coerce to hex so we can keep the record
try:
permissions = attr_data.Flags.lookup()
except ValueError:
permissions = hex(attr_data.Flags)
yield 1, (format_hints.Hex(attr_data_offset), mft_record.get_signature(),
mft_record.RecordNumber, mft_record.LinkCount, mft_flag, permissions,
attr_header.AttrType.lookup(),
conversion.wintime_to_datetime(attr_data.CreationTime),
conversion.wintime_to_datetime(attr_data.ModifiedTime),
conversion.wintime_to_datetime(attr_data.UpdatedTime),
conversion.wintime_to_datetime(attr_data.AccessedTime), file_name)
# Update the base offset to point to the next attribute
attr_base_offset += attr_header.Length
# Get the next attribute
attr_header = self.context.object(header_object,
offset = offset + attr_base_offset,
layer_name = layer.name)
except exceptions.PagedInvalidAddressException:
pass
def generate_timeline(self):
for row in self._generator():
_depth, row_data = row
# Only Output FN Records
if row_data[6] == 'FILE_NAME':
filename = row_data[-1]
description = f"MFT FILE_NAME entry for {filename}"
yield (description, timeliner.TimeLinerType.CREATED, row_data[7])
yield (description, timeliner.TimeLinerType.MODIFIED, row_data[8])
yield (description, timeliner.TimeLinerType.CHANGED, row_data[9])
yield (description, timeliner.TimeLinerType.ACCESSED, row_data[10])
def run(self):
return renderers.TreeGrid([
('Offset', format_hints.Hex),
('Record Type', str),
('Record Number', int),
('Link Count', int),
('MFT Type', str),
('Permissions', str),
('Attribute Type', str),
('Created', datetime.datetime),
('Modified', datetime.datetime),
('Updated', datetime.datetime),
('Accessed', datetime.datetime),
('Filename', str),
], self._generator())
@@ -0,0 +1,21 @@
# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import objects
class MFTEntry(objects.StructType):
"""This represents the base MFT Record"""
def get_signature(self) -> str:
signature = self.Signature.cast('string', max_length = 4, encoding = 'latin-1')
return signature
class MFTFileName(objects.StructType):
"""This represents an MFT $FILE_NAME Attribute"""
def get_full_name(self) -> str:
output = self.Name.cast("string", encoding = "utf16", max_length = self.NameLength * 2, errors = "replace")
return output
@@ -0,0 +1,467 @@
{
"metadata": {
"producer": {
"version": "0.0.1",
"name": "kevthehermit-by-hand",
"comment": "Using structures defined in File System Forensic Analysis pg 353+",
"datetime": "2022-01-03T13:37:00"
},
"format": "6.1.0"
},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": true,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "int",
"size": 1,
"signed": false,
"endian": "little"
},
"wchar": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
}
},
"symbols": {},
"enums": {
"AttrTypeEnum": {
"base": "unsigned char",
"constants": {
"STANDARD_INFORMATION": 16,
"ATTRIBUTE_LIST": 32,
"FILE_NAME": 48,
"OBJECT_ID": 64,
"SECURITY_DESCRIPTOR": 80,
"VOLUME_NAME": 96,
"VOLUME_INFORMATION": 112,
"DATA": 128,
"INDEX_ROOT": 114,
"INDEX_ALLOCATION": 160,
"BITMAP": 176,
"REPARSE_POINT": 192,
"EA_INFORMATION": 208,
"EA": 224,
"PROPERTY_SET": 240,
"LOGGED_UTILITY_STREAM": 256
},
"size": 1
},
"NameSpaceEnum": {
"base":"unsigned char",
"constants": {
"POSIX": 0,
"Win32": 1,
"DOS": 2,
"Win32 DOS": 3
},
"size": 1
},
"MFTFlagsEnum": {
"base":"unsigned char",
"constants": {
"Removed": 0,
"File": 1,
"Directory": 2,
"DirInUse": 3
},
"size": 1
},
"PermissionFlagEnum": {
"base":"unsigned char",
"constants": {
"ReadOnly": 1,
"Hidden": 2,
"System": 4,
"Archive": 32,
"ArchiveHidden": 34,
"ArchiveSystem": 36,
"ArchiveHiddenSystem": 38,
"Device": 60,
"Normal": 128,
"Temporary": 256,
"TempArchive": 288,
"SparseFile": 512,
"ReparsePoint": 1024,
"Compressed": 2048,
"Offline": 4096,
"NotIndexed": 8192,
"Encrypted": 16384,
"Directory": 268435456,
"IndexView": 536870912
},
"size": 1
}
},
"user_types": {
"MFT_ENTRY": {
"fields": {
"Signature": {
"offset": 0,
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
}
},
"UpdateSequenceOffset": {
"offset": 4,
"type": {
"kind": "base",
"name": "unsigned short"
}
},
"NumFixupEntries": {
"offset": 6,
"type": {
"kind": "base",
"name": "unsigned short"
}
},
"LSN": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"SequenceValue": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned short"
}
},
"LinkCount": {
"offset": 18,
"type": {
"kind": "base",
"name": "unsigned short"
}
},
"FirstAttrOffset": {
"offset": 20,
"type":{
"kind": "base",
"name": "unsigned short"
}
},
"Flags": {
"offset": 22,
"type":{
"kind": "enum",
"name": "MFTFlagsEnum"
}
},
"RealSize": {
"offset": 24,
"type":{
"kind": "base",
"name": "unsigned int"
}
},
"AlocatedSize": {
"offset": 28,
"type":{
"kind": "base",
"name": "unsigned int"
}
},
"BaseReference": {
"offset": 32,
"type":{
"kind": "base",
"name": "unsigned long long"
}
},
"NextAttrID": {
"offset": 40,
"type":{
"kind": "base",
"name": "unsigned short"
}
},
"RecordNumber": {
"offset": 44,
"type":{
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 1024
},
"ATTRIBUTE": {
"fields":{
"Attr_Header": {
"offset": 0,
"type": {
"kind": "struct",
"name": "mft!ATTR_HEADER"
}
},
"Resident_Header": {
"offset": 16,
"type": {
"kind": "struct",
"name": "mft!RESIDENT_HEADER"
}
},
"Attr_Data": {
"offset": 24,
"type": {
"kind": "struct",
"name": "mft!ATTR_HEADER"
}
}
},
"kind": "struct",
"size": 96
},
"ATTR_HEADER": {
"fields": {
"AttrType": {
"offset": 0,
"type": {
"kind": "enum",
"name": "AttrTypeEnum"
}
},"Length": {
"offset": 4,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"NonResidentFlag": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned char" }
},
"NameLength": {
"offset": 9,
"type": {
"kind": "base",
"name": "unsigned char"
}
},
"NameOffset": {
"offset": 10,
"type": {
"kind": "base",
"name": "unsigned short"
}
},
"Flags": {
"offset": 12,
"type": {
"kind": "enum",
"name": "MFTFlagsEnum"
}
},
"AttributeID": {
"offset": 14,
"type": {
"kind": "base",
"name": "unsigned short"
}
}
},
"kind": "struct",
"size": 16
},"RESIDENT_HEADER": {
"fields": {
"AttrSize": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned int"
}
},"AttrOffset": {
"offset": 4,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"IndexFlag": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned short" }
}
},
"kind": "struct",
"size": 8
},
"STANDARD_INFORMATION_ENTRY": {
"fields": {
"CreationTime": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"ModifiedTime": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"UpdatedTime": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"AccessedTime": {
"offset": 24,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"flags": {
"offset": 32,
"type": {
"kind": "enum",
"name": "PermissionFlagEnum"
}
}
},
"kind": "struct",
"size": 1024
},
"FILE_NAME_ENTRY": {
"fields": {
"ParentDirectory": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"CreationTime": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"ModifiedTime": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"UpdatedTime": {
"offset": 24,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"AccessedTime": {
"offset": 32,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"AllocatedFileSize": {
"offset": 40,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"RealFileSize": {
"offset": 48,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"Flags": {
"offset": 56,
"type": {
"kind": "enum",
"name": "PermissionFlagEnum"
}
},
"ReparseValue": {
"offset": 60,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"NameLength": {
"offset": 64,
"type": {
"kind": "base",
"name": "unsigned char"
}
},
"NameSpace": {
"offset": 65,
"type": {
"kind": "base",
"name": "unsigned char"
}
},
"Name": {
"offset": 66,
"type": {
"count": 10,
"kind": "array",
"subtype": {
"kind": "base",
"name": "wchar"
}
}
}
},
"kind": "struct",
"size": 1024
}
}
}