Skip to content

Commit b9c60a5

Browse files
authored
Merge branch 'volatilityfoundation:develop' into linux_kmsg_issue_1055
2 parents 831e6cf + a08b780 commit b9c60a5

File tree

5 files changed

+221
-37
lines changed

5 files changed

+221
-37
lines changed

volatility3/framework/plugins/windows/mftscan.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,140 @@ def run(self):
173173
],
174174
self._generator(),
175175
)
176+
177+
178+
class ADS(interfaces.plugins.PluginInterface):
179+
180+
"""Scans for Alternate Data Stream"""
181+
182+
_required_framework_version = (2, 0, 0)
183+
184+
@classmethod
185+
def get_requirements(cls):
186+
return [
187+
requirements.TranslationLayerRequirement(
188+
name="primary",
189+
description="Memory layer for the kernel",
190+
architectures=["Intel32", "Intel64"],
191+
),
192+
requirements.VersionRequirement(
193+
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
194+
),
195+
]
196+
197+
def _generator(self):
198+
layer = self.context.layers[self.config["primary"]]
199+
200+
# Yara Rule to scan for MFT Header Signatures
201+
rules = yarascan.YaraScan.process_yara_options(
202+
{"yara_rules": "/FILE0|FILE\*|BAAD/"}
203+
)
204+
205+
# Read in the Symbol File
206+
symbol_table = intermed.IntermediateSymbolTable.create(
207+
context=self.context,
208+
config_path=self.config_path,
209+
sub_path="windows",
210+
filename="mft",
211+
class_types={
212+
"MFT_ENTRY": mft.MFTEntry,
213+
"FILE_NAME_ENTRY": mft.MFTFileName,
214+
"ATTRIBUTE": mft.MFTAttribute,
215+
},
216+
)
217+
218+
# get each of the individual Field Sets
219+
mft_object = symbol_table + constants.BANG + "MFT_ENTRY"
220+
attribute_object = symbol_table + constants.BANG + "ATTRIBUTE"
221+
fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY"
222+
223+
# Scan the layer for Raw MFT records and parse the fields
224+
for offset, _rule_name, _name, _value in layer.scan(
225+
context=self.context, scanner=yarascan.YaraScanner(rules=rules)
226+
):
227+
with contextlib.suppress(exceptions.PagedInvalidAddressException):
228+
mft_record = self.context.object(
229+
mft_object, offset=offset, layer_name=layer.name
230+
)
231+
# We will update this on each pass in the next loop and use it as the new offset.
232+
attr_base_offset = mft_record.FirstAttrOffset
233+
234+
attr = self.context.object(
235+
attribute_object,
236+
offset=offset + attr_base_offset,
237+
layer_name=layer.name,
238+
)
239+
240+
# There is no field that has a count of Attributes
241+
# Keep Attempting to read attributes until we get an invalid attr.AttrType
242+
is_ads = False
243+
file_name = renderers.NotAvailableValue
244+
# The First $DATA Attr is the 'principal' file itself not the ADS
245+
while attr.Attr_Header.AttrType.is_valid_choice:
246+
if attr.Attr_Header.AttrType.lookup() == "FILE_NAME":
247+
attr_data = attr.Attr_Data.cast(fn_object)
248+
file_name = attr_data.get_full_name()
249+
if attr.Attr_Header.AttrType.lookup() == "DATA":
250+
if is_ads:
251+
if not attr.Attr_Header.NonResidentFlag:
252+
# Resident files are the most interesting.
253+
if attr.Attr_Header.NameLength > 0:
254+
ads_name = attr.get_resident_filename()
255+
if not ads_name:
256+
ads_name = renderers.NotAvailableValue
257+
258+
content = attr.get_resident_filecontent()
259+
if content:
260+
# Preparing for Disassembly
261+
disasm = interfaces.renderers.BaseAbsentValue
262+
architecture = layer.metadata.get(
263+
"architecture", None
264+
)
265+
if architecture:
266+
disasm = interfaces.renderers.Disassembly(
267+
content, 0, architecture.lower()
268+
)
269+
else:
270+
content = renderers.NotAvailableValue
271+
disasm = interfaces.renderers.BaseAbsentValue
272+
273+
yield 0, (
274+
format_hints.Hex(attr_data.vol.offset),
275+
mft_record.get_signature(),
276+
mft_record.RecordNumber,
277+
attr.Attr_Header.AttrType.lookup(),
278+
file_name,
279+
ads_name,
280+
format_hints.HexBytes(content),
281+
disasm,
282+
)
283+
else:
284+
is_ads = True
285+
286+
# If there's no advancement the loop will never end, so break it now
287+
if attr.Attr_Header.Length == 0:
288+
break
289+
290+
# Update the base offset to point to the next attribute
291+
attr_base_offset += attr.Attr_Header.Length
292+
# Get the next attribute
293+
attr = self.context.object(
294+
attribute_object,
295+
offset=offset + attr_base_offset,
296+
layer_name=layer.name,
297+
)
298+
299+
def run(self):
300+
return renderers.TreeGrid(
301+
[
302+
("Offset", format_hints.Hex),
303+
("Record Type", str),
304+
("Record Number", int),
305+
("MFT Type", str),
306+
("Filename", str),
307+
("ADS Filename", str),
308+
("Hexdump", format_hints.HexBytes),
309+
("Disasm", interfaces.renderers.Disassembly),
310+
],
311+
self._generator(),
312+
)

volatility3/framework/plugins/windows/vadyarascan.py

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,47 +18,26 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
1818
"""Scans all the Virtual Address Descriptor memory maps using yara."""
1919

2020
_required_framework_version = (2, 4, 0)
21-
_version = (1, 0, 0)
21+
_version = (1, 0, 1)
2222

2323
@classmethod
2424
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
25-
return [
25+
# create a list of requirements for vadyarascan
26+
vadyarascan_requirements = [
2627
requirements.ModuleRequirement(
2728
name="kernel",
2829
description="Windows kernel",
2930
architectures=["Intel32", "Intel64"],
3031
),
31-
requirements.BooleanRequirement(
32-
name="wide",
33-
description="Match wide (unicode) strings",
34-
default=False,
35-
optional=True,
36-
),
37-
requirements.StringRequirement(
38-
name="yara_rules", description="Yara rules (as a string)", optional=True
39-
),
40-
requirements.URIRequirement(
41-
name="yara_file", description="Yara rules (as a file)", optional=True
42-
),
43-
# This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code
44-
# As such, there's a separate option to run compiled files, as happened with yara-3.9 and later
45-
requirements.URIRequirement(
46-
name="yara_compiled_file",
47-
description="Yara compiled rules (as a file)",
48-
optional=True,
49-
),
50-
requirements.IntRequirement(
51-
name="max_size",
52-
default=0x40000000,
53-
description="Set the maximum size (default is 1GB)",
54-
optional=True,
55-
),
5632
requirements.PluginRequirement(
5733
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
5834
),
5935
requirements.VersionRequirement(
6036
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
6137
),
38+
requirements.PluginRequirement(
39+
name="yarascan", plugin=yarascan.YaraScan, version=(1, 2, 0)
40+
),
6241
requirements.ListRequirement(
6342
name="pid",
6443
element_type=int,
@@ -67,6 +46,12 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
6746
),
6847
]
6948

49+
# get base yarascan requirements for command line options
50+
yarascan_requirements = yarascan.YaraScan.get_yarascan_option_requirements()
51+
52+
# return the combined requirements
53+
return yarascan_requirements + vadyarascan_requirements
54+
7055
def _generator(self):
7156
kernel = self.context.modules[self.config["kernel"]]
7257

volatility3/framework/symbols/linux/extensions/elf.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
#
44

55
from typing import Dict, Tuple
6+
import logging
67

78
from volatility3.framework import constants
8-
from volatility3.framework import objects, interfaces
9+
from volatility3.framework import objects, interfaces, exceptions
10+
11+
vollog = logging.getLogger(__name__)
912

1013

1114
class elf(objects.StructType):
@@ -33,14 +36,23 @@ def __init__(
3336
layer_name = self.vol.layer_name
3437
symbol_table_name = self.get_symbol_table_name()
3538
# We read the MAGIC: (0x0 to 0x4) 0x7f 0x45 0x4c 0x46
36-
magic = self._context.object(
37-
symbol_table_name + constants.BANG + "unsigned long",
38-
layer_name=layer_name,
39-
offset=object_info.offset,
40-
)
39+
try:
40+
magic = self._context.object(
41+
symbol_table_name + constants.BANG + "unsigned long",
42+
layer_name=layer_name,
43+
offset=object_info.offset,
44+
)
45+
except (
46+
exceptions.PagedInvalidAddressException,
47+
exceptions.InvalidAddressException,
48+
) as excp:
49+
vollog.debug(
50+
f"Unable to check magic bytes for ELF file at offset {hex(object_info.offset)} in layer {layer_name}: {excp}"
51+
)
52+
return None
4153

4254
# Check validity
43-
if magic != 0x464C457F:
55+
if magic != 0x464C457F: # e.g. ELF
4456
return None
4557

4658
# We need to read the EI_CLASS (0x4 offset)
@@ -72,7 +84,10 @@ def is_valid(self):
7284
"""
7385
Determine whether it is a valid object
7486
"""
75-
return self._type_prefix is not None and self._hdr is not None
87+
if hasattr(self, "_type_prefix") and hasattr(self, "_hdr"):
88+
return self._type_prefix is not None and self._hdr is not None
89+
else:
90+
return False
7691

7792
def __getattr__(self, name):
7893
# Just redirect to the corresponding header

volatility3/framework/symbols/windows/extensions/mft.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33
#
44

5-
from volatility3.framework import objects
5+
from volatility3.framework import objects, constants, exceptions
66

77

88
class MFTEntry(objects.StructType):
@@ -21,3 +21,36 @@ def get_full_name(self) -> str:
2121
"string", encoding="utf16", max_length=self.NameLength * 2, errors="replace"
2222
)
2323
return output
24+
25+
26+
class MFTAttribute(objects.StructType):
27+
"""This represents an MFT ATTRIBUTE"""
28+
29+
def get_resident_filename(self) -> str:
30+
# To get the resident name, we jump to relative name offset and read name length * 2 bytes of data
31+
try:
32+
name = self._context.object(
33+
self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string",
34+
layer_name=self.vol.layer_name,
35+
offset=self.vol.offset + self.Attr_Header.NameOffset,
36+
max_length=self.Attr_Header.NameLength * 2,
37+
errors="replace",
38+
encoding="utf16",
39+
)
40+
return name
41+
except exceptions.InvalidAddressException:
42+
return None
43+
44+
def get_resident_filecontent(self) -> bytes:
45+
# To get the resident content, we jump to relative content offset and read name length * 2 bytes of data
46+
try:
47+
bytesobj = self._context.object(
48+
self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "bytes",
49+
layer_name=self.vol.layer_name,
50+
offset=self.vol.offset + self.Attr_Header.ContentOffset,
51+
native_layer_name=self.vol.native_layer_name,
52+
length=self.Attr_Header.ContentLength,
53+
)
54+
return bytesobj
55+
except exceptions.InvalidAddressException:
56+
return None

volatility3/framework/symbols/windows/mft.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,24 @@
300300
"kind": "base",
301301
"name": "unsigned short"
302302
}
303+
},
304+
"ContentLength": {
305+
"offset": 16,
306+
"type": {
307+
"kind": "base",
308+
"name": "unsigned int"
309+
}
310+
},
311+
"ContentOffset": {
312+
"offset": 20,
313+
"type": {
314+
"kind": "base",
315+
"name": "unsigned short"
316+
}
303317
}
304318
},
305319
"kind": "struct",
306-
"size": 16
320+
"size": 24
307321
},"RESIDENT_HEADER": {
308322
"fields": {
309323
"AttrSize": {

0 commit comments

Comments
 (0)