mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-10 19:57:39 +02:00
MFT Plugin use ISF instead of Struct
This commit is contained in:
@@ -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('<L', mft_record[offset:offset+4])[0]
|
||||
elif data_type == 'unsigned short':
|
||||
return struct.unpack('<H', mft_record[offset:offset+2])[0]
|
||||
elif data_type == 'unsigned long long':
|
||||
return struct.unpack('<Q', mft_record[offset:offset+8])[0]
|
||||
elif data_type == 'unsigned char':
|
||||
return struct.unpack('<B', mft_record[offset:offset+1])[0]
|
||||
elif data_type == 'int':
|
||||
return struct.unpack('<I', mft_record[offset:offset+4])[0]
|
||||
|
||||
@classmethod
|
||||
def parse_mft_record(self, mft_record: bytes) -> 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())
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user