diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 4612077b8..7e4e1ca18 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -173,3 +173,140 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ], self._generator(), ) + + +class ADS(interfaces.plugins.PluginInterface): + + """Scans for Alternate Data Stream""" + + _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={ + "MFT_ENTRY": mft.MFTEntry, + "FILE_NAME_ENTRY": mft.MFTFileName, + "ATTRIBUTE": mft.MFTAttribute, + }, + ) + + # get each of the individual Field Sets + mft_object = symbol_table + constants.BANG + "MFT_ENTRY" + attribute_object = symbol_table + constants.BANG + "ATTRIBUTE" + 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) + ): + with contextlib.suppress(exceptions.PagedInvalidAddressException): + 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 = self.context.object( + attribute_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.AttrType + is_ads = False + file_name = renderers.NotAvailableValue + # The First $DATA Attr is the 'principal' file itself not the ADS + while attr.Attr_Header.AttrType.is_valid_choice: + if attr.Attr_Header.AttrType.lookup() == "FILE_NAME": + attr_data = attr.Attr_Data.cast(fn_object) + file_name = attr_data.get_full_name() + if attr.Attr_Header.AttrType.lookup() == "DATA": + if is_ads: + if not attr.Attr_Header.NonResidentFlag: + # Resident files are the most interesting. + if attr.Attr_Header.NameLength > 0: + ads_name = attr.get_resident_filename() + if not ads_name: + ads_name = renderers.NotAvailableValue + + content = attr.get_resident_filecontent() + if content: + # Preparing for Disassembly + disasm = interfaces.renderers.BaseAbsentValue + architecture = layer.metadata.get( + "architecture", None + ) + if architecture: + disasm = interfaces.renderers.Disassembly( + content, 0, architecture.lower() + ) + else: + content = renderers.NotAvailableValue + disasm = interfaces.renderers.BaseAbsentValue + + yield 0, ( + format_hints.Hex(attr_data.vol.offset), + mft_record.get_signature(), + mft_record.RecordNumber, + attr.Attr_Header.AttrType.lookup(), + file_name, + ads_name, + format_hints.HexBytes(content), + disasm, + ) + else: + is_ads = True + + # If there's no advancement the loop will never end, so break it now + if attr.Attr_Header.Length == 0: + break + + # Update the base offset to point to the next attribute + attr_base_offset += attr.Attr_Header.Length + # Get the next attribute + attr = self.context.object( + attribute_object, + offset=offset + attr_base_offset, + layer_name=layer.name, + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("Record Type", str), + ("Record Number", int), + ("MFT Type", str), + ("Filename", str), + ("ADS Filename", str), + ("Hexdump", format_hints.HexBytes), + ("Disasm", interfaces.renderers.Disassembly), + ], + self._generator(), + ) diff --git a/volatility3/framework/symbols/windows/extensions/mft.py b/volatility3/framework/symbols/windows/extensions/mft.py index 17b6c8325..14c1f08d6 100644 --- a/volatility3/framework/symbols/windows/extensions/mft.py +++ b/volatility3/framework/symbols/windows/extensions/mft.py @@ -2,7 +2,7 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -from volatility3.framework import objects +from volatility3.framework import objects, constants, exceptions class MFTEntry(objects.StructType): @@ -21,3 +21,36 @@ class MFTFileName(objects.StructType): "string", encoding="utf16", max_length=self.NameLength * 2, errors="replace" ) return output + + +class MFTAttribute(objects.StructType): + """This represents an MFT ATTRIBUTE""" + + def get_resident_filename(self) -> str: + # To get the resident name, we jump to relative name offset and read name length * 2 bytes of data + try: + name = self._context.object( + self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string", + layer_name=self.vol.layer_name, + offset=self.vol.offset + self.Attr_Header.NameOffset, + max_length=self.Attr_Header.NameLength * 2, + errors="replace", + encoding="utf16", + ) + return name + except exceptions.InvalidAddressException: + return None + + def get_resident_filecontent(self) -> bytes: + # To get the resident content, we jump to relative content offset and read name length * 2 bytes of data + try: + bytesobj = self._context.object( + self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "bytes", + layer_name=self.vol.layer_name, + offset=self.vol.offset + self.Attr_Header.ContentOffset, + native_layer_name=self.vol.native_layer_name, + length=self.Attr_Header.ContentLength, + ) + return bytesobj + except exceptions.InvalidAddressException: + return None diff --git a/volatility3/framework/symbols/windows/mft.json b/volatility3/framework/symbols/windows/mft.json index ecbf1d2d7..d4f2aef7a 100644 --- a/volatility3/framework/symbols/windows/mft.json +++ b/volatility3/framework/symbols/windows/mft.json @@ -300,10 +300,24 @@ "kind": "base", "name": "unsigned short" } + }, + "ContentLength": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "ContentOffset": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } } }, "kind": "struct", - "size": 16 + "size": 24 },"RESIDENT_HEADER": { "fields": { "AttrSize": {