From 9d86599e7f391c41f2fb7e06f6f0f811bd97a7b7 Mon Sep 17 00:00:00 2001 From: KevTheHermit Date: Sun, 9 Jan 2022 22:04:13 +0000 Subject: [PATCH] MFT Plugin use ISF instead of Struct --- .../framework/plugins/windows/mftscan.py | 352 ++++------------- .../symbols/windows/extensions/mft.py | 104 +++++ .../framework/symbols/windows/mft.json | 371 ++++++++++++++++++ volatility3/framework/symbols/windows/mft.py | 15 + 4 files changed, 577 insertions(+), 265 deletions(-) create mode 100644 volatility3/framework/symbols/windows/extensions/mft.py create mode 100644 volatility3/framework/symbols/windows/mft.json create mode 100644 volatility3/framework/symbols/windows/mft.py diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 10db6f112..484fcdb9a 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -4,95 +4,20 @@ import datetime import logging -import struct from typing import Dict from volatility3.framework import constants, renderers, interfaces from volatility3.framework.configuration import requirements from volatility3.framework import exceptions -from volatility3.framework.objects import utility from volatility3.framework.renderers import conversion, format_hints +from volatility3.framework.symbols.windows.extensions.mft import AttributeTypes, NameSpace, PermissionFlags, MFTFlags +from volatility3.framework.symbols.windows.mft import MFTIntermedSymbols + from volatility3.plugins import yarascan vollog = logging.getLogger(__name__) -try: - import yara -except ImportError: - vollog.info("Python Yara module not found, plugin (and dependent plugins) not available") - raise - -signatures = { - 'mft_objects': """rule mft_headers - { - strings: - $header1 = "FILE0" - $header2 = "FILE*" - $header3 = "BAAD" - condition: - any of them - }""" -} - -# https://github.com/volatilityfoundation/volatility/blob/a438e768194a9e05eb4d9ee9338b881c0fa25937/volatility/plugins/mftparser.py#L60 -ATTRIBUTE_TYPE_ID = { - 0x10:"STANDARD_INFORMATION", - 0x20:"ATTRIBUTE_LIST", - 0x30:"FILE_NAME", - 0x40:"OBJECT_ID", - 0x50:"SECURITY_DESCRIPTOR", - 0x60:"VOLUME_NAME", - 0x70:"VOLUME_INFORMATION", - 0x80:"DATA", - 0x90:"INDEX_ROOT", - 0xa0:"INDEX_ALLOCATION", - 0xb0:"BITMAP", - 0xc0:"REPARSE_POINT", - 0xd0:"EA_INFORMATION", #Extended Attribute - 0xe0:"EA", - 0xf0:"PROPERTY_SET", - 0x100:"LOGGED_UTILITY_STREAM", -} - -VERBOSE_STANDARD_INFO_FLAGS = { - 0x1:"Read Only", - 0x2:"Hidden", - 0x4:"System", - 0x20:"Archive", - 0x40:"Device", - 0x80:"Normal", - 0x100:"Temporary", - 0x200:"Sparse File", - 0x400:"Reparse Point", - 0x800:"Compressed", - 0x1000:"Offline", - 0x2000:"Content not indexed", - 0x4000:"Encrypted", - 0x10000000:"Directory", - 0x20000000:"Index view", -} - -FILE_NAME_NAMESPACE = { - 0x0:"POSIX", # Case sensitive, allows all Unicode chars except '/' and NULL - 0x1:"Win32", # Case insensitive, allows most Unicide except specials ('/', '\', ';', '>', '<', '?') - 0x2:"DOS", # Case insensitive, upper case, no special chars, name is 8 or fewer chars in name and 3 or less extension - 0x3:"Win32 & DOS", # Used when original name fits in DOS namespace and 2 names are not needed -} - -MFT_FLAGS = { - 0x0: "Removed", - 0x1: "File", # "In Use", - 0x2: "Directory", # if flag & 0x0002 == 0 this is a regular file - 0x3: "Directory" -} - -INDEX_ENTRY_FLAGS = { - 0x1:"Child Node Exists", - 0x2:"Last entry in list", -} - - class MFTScan(interfaces.plugins.PluginInterface): """Scans for MFT FILE objects present in a particular windows memory image.""" @@ -108,198 +33,94 @@ class MFTScan(interfaces.plugins.PluginInterface): version = (2, 0, 0)), ] - # https://docs.python.org/3/library/struct.html - @classmethod - def unpack_data(self, mft_record: bytes, offset: int, data_type: str) -> bytes: - """Helper to unpack values from the raw mft_record - - Args: - mft_record: 1024 bytes starting from header value as returned by layer read - offset: how far in to the record to read - data_type: what is the data type to unpack - - Returns: - bytes: the unpacked data - """ - - if data_type == 'unsigned long': - return struct.unpack(' Dict: - """Takes an MFT Record and attempts to parse, MFT, SI and FN attributes - - Args: - mft_record: 1024 bytes starting from header value as returned by layer read - - Returns: - Dict: a Dictionary that contains the Parse MFT Record - """ - # https://github.com/Invoke-IR/ForensicPosters - - flags = self.unpack_data(mft_record, 22, 'unsigned short') - file_type = MFT_FLAGS.get(flags, 'Unknown') - - mft_entry = { - "signature": mft_record[:4].decode(), - "FixupArrayOffset": self.unpack_data(mft_record, 4, 'unsigned short'), - "NumFixupEntries": self.unpack_data(mft_record, 6, 'unsigned short'), - "LSN": self.unpack_data(mft_record, 8, 'unsigned long long'), - "SequenceValue": self.unpack_data(mft_record, 16, 'unsigned short'), - "link_count": self.unpack_data(mft_record, 18, 'unsigned short'), - "FirstAttrOffset": self.unpack_data(mft_record, 20, 'unsigned short'), - "flags": file_type, - "record_number": self.unpack_data(mft_record, 44, 'unsigned long'), - "attributes": { - "SI": {}, - "FN": [] - } - } - - attr_offset = mft_entry['FirstAttrOffset'] - # Check at most for 6 entries - for i in range(6): - # If we attempt to overread the entry continue out - if attr_offset > 1000: - continue - - # attr_header - attr_type = self.unpack_data(mft_record, attr_offset, 'int') - attr_len = self.unpack_data(mft_record, attr_offset+4, 'int') - - # As we look for strucutres of header + 1K we can not unpack non resident structures - nr_flag = self.unpack_data(mft_record, attr_offset+8, 'unsigned char') - - # Skip headers - attr_data = attr_offset+24 # Len of Common and Resident Headers - - if attr_type in ATTRIBUTE_TYPE_ID: - vollog.debug(f'Found Attribute {ATTRIBUTE_TYPE_ID[attr_type]}') - - if ATTRIBUTE_TYPE_ID[attr_type] == 'STANDARD_INFORMATION': - creation_time_win = self.unpack_data(mft_record, attr_data, 'unsigned long long') - modified_time_win = self.unpack_data(mft_record, attr_data+8, 'unsigned long long') - altered_time_win = self.unpack_data(mft_record, attr_data+16, 'unsigned long long') - access_time_win = self.unpack_data(mft_record, attr_data+24, 'unsigned long long') - flags = self.unpack_data(mft_record, attr_data+32, 'unsigned short') - permissions = VERBOSE_STANDARD_INFO_FLAGS.get(flags, 'Unknown') - - mft_entry['attributes']['SI'] = { - "creation_time": conversion.wintime_to_datetime(creation_time_win), - "modified_time": conversion.wintime_to_datetime(modified_time_win), - "updated_time": conversion.wintime_to_datetime(altered_time_win), - "accessed_time": conversion.wintime_to_datetime(access_time_win), - "flags": permissions - } - - if ATTRIBUTE_TYPE_ID[attr_type] == 'FILE_NAME': - parent_record = self.unpack_data(mft_record, attr_data, 'unsigned long long') - creation_time_win = self.unpack_data(mft_record, attr_data+8, 'unsigned long long') - modified_time_win = self.unpack_data(mft_record, attr_data+16, 'unsigned long long') - altered_time_win = self.unpack_data(mft_record, attr_data+24, 'unsigned long long') - access_time_win = self.unpack_data(mft_record, attr_data+32, 'unsigned long long') - - name_len = self.unpack_data(mft_record, attr_data+64, 'unsigned char') - name_space = self.unpack_data(mft_record, attr_data+65, 'unsigned char') - - # Unicode and partially corruprted records can break us here. - file_name = mft_record[attr_data+66:attr_data+66+(2*name_len)] - #file_name = utility.array_to_string(file_name) - try: - file_name = file_name.replace(b'\x00', b'').decode() - except: - file_name = str(file_name.replace(b'\x00', b'')) - - flags = self.unpack_data(mft_record, attr_data+56, 'unsigned short') - permissions = VERBOSE_STANDARD_INFO_FLAGS.get(flags, 'Unknown') - - mft_entry['attributes']['FN'].append( - { - "creation_time": conversion.wintime_to_datetime(creation_time_win), - "modified_time": conversion.wintime_to_datetime(modified_time_win), - "updated_time": conversion.wintime_to_datetime(altered_time_win), - "accessed_time": conversion.wintime_to_datetime(access_time_win), - "allocated_size": self.unpack_data(mft_record, attr_data+40, 'unsigned long long'), - "real_size": self.unpack_data(mft_record, attr_data+48, 'unsigned long long'), - "flags": permissions, - "file_name": file_name, - "name_space": name_space - }) - - # Update Offset for next Attribute - attr_offset += attr_len - - return mft_entry def _generator(self): - rules = yara.compile(sources = signatures) - 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 = MFTIntermedSymbols.create( + self.context, + self.config_path, + "windows", + "mft" + ) + + # get each of the individual Field Sets + mft_object = symbol_table + constants.BANG + "MFT_ENTRY" + 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)): - - # For each matching rule try to read 1024 bytes (size of an MFT record) at the offset. try: - mft_record = layer.read(offset, 1024, False) - mft_entry = self.parse_mft_record(mft_record) + 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 + + # There is no field that has a count of Attributes + # Keep Attempting to read attributes until we get an invalid attr_header.AttrType + while True: + attr_header = self.context.object(header_object, offset=offset+attr_base_offset, layer_name=layer.name) + attr_resident_header = self.context.object(header_object, offset=offset+attr_base_offset+16, layer_name=layer.name) + + vollog.debug(f"Attr Type: {attr_header.AttrType}") + + # If this is not a valid type then exit the loop + if not AttributeTypes(attr_header.AttrType).value: + break + + # Offset past the headers to the attribute data + attr_data_offset = offset+attr_base_offset+24 + + # Standard Information Attribute + if attr_header.AttrType == 0x10: + 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, + MFTFlags(mft_record.Flags).name, + renderers.NotApplicableValue(), + AttributeTypes(attr_header.AttrType).name, + 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 == 0x30: + attr_data = self.context.object(fn_object, offset=attr_data_offset, layer_name=layer.name) + file_name = attr_data.get_full_name() + + yield 1, ( + format_hints.Hex(attr_data_offset), + mft_record.get_signature(), + mft_record.RecordNumber, + mft_record.LinkCount, + MFTFlags(mft_record.Flags).name, + PermissionFlags(attr_data.Flags).name, + AttributeTypes(attr_header.AttrType).name, + 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 + except exceptions.PagedInvalidAddressException: - mft_entry = None - #except Exception as err: - # vollog.error(err) - # mft_entry = None + pass - if mft_entry: - vollog.debug(mft_entry) - - # Tree Grid is large and variable - si = mft_entry['attributes']['SI'] - fn = mft_entry['attributes']['FN'] - - signature = mft_entry.get('signature', renderers.NotAvailableValue()) - record_number = mft_entry.get('record_number', renderers.NotAvailableValue()) - link_count = mft_entry.get('link_count', renderers.NotAvailableValue()) - permissions = mft_entry.get('flags', renderers.NotAvailableValue()) - - si_creation_time = si.get('creation_time', renderers.NotAvailableValue()) - si_modified_time = si.get('modified_time', renderers.NotAvailableValue()) - si_updated_time = si.get('updated_time', renderers.NotAvailableValue()) - si_accessed_time = si.get('accessed_time', renderers.NotAvailableValue()) - - yield 0, ( - format_hints.Hex(offset), - signature, - record_number, - link_count, - permissions, - 'Standard Information', - renderers.NotApplicableValue(), - si_creation_time, - si_modified_time, - si_updated_time, - si_accessed_time) - - for entry in fn: - # As this is variable and may or may not exist - # And could have 0-6 entries lets do it per row. - yield 1, ( - format_hints.Hex(offset), - signature, - record_number, - link_count, - permissions, - 'FileName', - entry.get('file_name',renderers.NotAvailableValue()), - entry.get('creation_time', renderers.NotAvailableValue()), - entry.get('modified_time', renderers.NotAvailableValue()), - entry.get('updated_time', renderers.NotAvailableValue()), - entry.get('accessed_time', renderers.NotAvailableValue())) def run(self): return renderers.TreeGrid([ @@ -307,11 +128,12 @@ class MFTScan(interfaces.plugins.PluginInterface): ('Record Type', str), ('Record Number', int), ('Link Count', int), + ('MFT Type', str), ('Permissions', str), ('Attribute Type', str), - ('Filename', str), ('Created', datetime.datetime), ('Modified', datetime.datetime), ('Updated', datetime.datetime), - ('Accessed', datetime.datetime) + ('Accessed', datetime.datetime), + ('Filename', str), ],self._generator()) diff --git a/volatility3/framework/symbols/windows/extensions/mft.py b/volatility3/framework/symbols/windows/extensions/mft.py new file mode 100644 index 000000000..09f6346cc --- /dev/null +++ b/volatility3/framework/symbols/windows/extensions/mft.py @@ -0,0 +1,104 @@ +# 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 enum + +from volatility3.framework import exceptions, objects, renderers +from volatility3.framework.objects import utility + + +class AttributeTypes(enum.Enum): + STANDARD_INFORMATION = 0x10 + ATTRIBUTE_LIST = 0x20 + FILE_NAME = 0x30 + OBJECT_ID = 0x40 + SECURITY_DESCRIPTOR = 0x50 + VOLUME_NAME = 0x60 + VOLUME_INFORMATION = 0x70 + DATA = 0x80 + INDEX_ROOT = 0x90 + INDEX_ALLOCATION = 0xa0 + BITMAP = 0xb0 + REPARSE_POINT = 0xc0 + EA_INFORMATION = 0xd0 + EA = 0xe0 + PROPERTY_SET = 0xf0 + LOGGED_UTILITY_STREAM = 0x100 + Unknown = None + + @classmethod + def _missing_(cls, value): + return cls(AttributeTypes.Unknown) + +class NameSpace(enum.Enum): + POSIX = 0x0 + Win32 = 0x1 + DOS = 0x2 + Win32DOS = 0x3 + Unknown = None + + @classmethod + def _missing_(cls, value): + return cls(NameSpace.Unknown) + + +class MFTFlags(enum.Enum): + Removed = 0x00 + File = 0x1 + Directory = 0x2 + DirInUse = 0x3 + Unknown = None + + @classmethod + def _missing_(cls, value): + return cls(MFTFlags.Unknown) + + +class PermissionFlags(enum.Enum): + ReadOnly = 0x1 + Hidden = 0x2 + System = 0x4 + Archive = 0x20 + ArchiveHidden = 0x22 + ArchiveSystem = 0x24 + ArchiveHiddenSystem = 0x26 + Device = 0x40 + Normal = 0x80 + Temporary = 0x100 + TempArchive = 0x120 + SparseFile = 0x200 + ReparsePoint = 0x400 + Compressed = 0x800 + Offline = 0x1000 + NotIndexed = 0x2000 + Encrypted = 0x4000 + Directory = 0x10000000 + IndexView = 0x20000000 + unknown = None + + @classmethod + def _missing_(cls, value): + return cls(PermissionFlags.unknown) + + +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 + + def get_file_namespace(self) -> str: + pass diff --git a/volatility3/framework/symbols/windows/mft.json b/volatility3/framework/symbols/windows/mft.json new file mode 100644 index 000000000..e045ed0fc --- /dev/null +++ b/volatility3/framework/symbols/windows/mft.json @@ -0,0 +1,371 @@ +{ + "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": {}, + "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": "base", + "name": "unsigned short" + } + }, + "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 + },"ATTR_HEADER": { + "fields": { + "AttrType": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned int" + } + },"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": "base", + "name": "unsigned short" + } + }, + "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": "base", + "name": "unsigned short" + } + } + }, + "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": "base", + "name": "unsigned int" + } + }, + "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 + } + } +} \ No newline at end of file diff --git a/volatility3/framework/symbols/windows/mft.py b/volatility3/framework/symbols/windows/mft.py new file mode 100644 index 000000000..921d75cd8 --- /dev/null +++ b/volatility3/framework/symbols/windows/mft.py @@ -0,0 +1,15 @@ +# 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.symbols import intermed +from volatility3.framework.symbols.windows.extensions import mft + + +class MFTIntermedSymbols(intermed.IntermediateSymbolTable): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.set_type_class('FILE_NAME_ENTRY', mft.MFTFileName) + self.set_type_class('MFT_ENTRY', mft.MFTEntry)