mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-26 08:02:23 +02:00
Merge pull request #614 from kevthehermit/plugin/mftscan
Create MFTScanner plugin
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user