From a68d56e19109a81098ae208d33ae94a679ff0824 Mon Sep 17 00:00:00 2001 From: Eve Date: Tue, 13 Dec 2022 16:09:57 +0000 Subject: [PATCH 1/4] add linux.vmayarascan based on windows.vadtarascan --- .../framework/plugins/linux/vmayarascan.py | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 volatility3/framework/plugins/linux/vmayarascan.py diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py new file mode 100644 index 000000000..e9482089a --- /dev/null +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -0,0 +1,121 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable, List, Tuple + +from volatility3.framework import interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.plugins import yarascan +from volatility3.plugins.linux import pslist + +class VmaYaraScan(interfaces.plugins.PluginInterface): + """Scans all virtual memory areas for tasks using yara.""" + + _required_framework_version = (2, 4, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.BooleanRequirement( + name="wide", + description="Match wide (unicode) strings", + default=False, + optional=True, + ), + requirements.StringRequirement( + name="yara_rules", description="Yara rules (as a string)", optional=True + ), + requirements.URIRequirement( + name="yara_file", description="Yara rules (as a file)", optional=True + ), + # This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code + # As such, there's a separate option to run compiled files, as happened with yara-3.9 and later + requirements.URIRequirement( + name="yara_compiled_file", + description="Yara compiled rules (as a file)", + optional=True, + ), + requirements.IntRequirement( + name="max_size", + default=0x40000000, + description="Set the maximum size (default is 1GB)", + optional=True, + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) + ), + requirements.ListRequirement( + name="pid", + element_type=int, + description="Process IDs to include (all other processes are excluded)", + optional=True, + ), + ] + + def _generator(self): + kernel = self.context.modules[self.config["kernel"]] + + rules = yarascan.YaraScan.process_yara_options(dict(self.config)) + + filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + for task in pslist.PsList.list_tasks( + context=self.context, + vmlinux_module_name=self.config["kernel"], + filter_func=filter_func, + ): + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + continue + + proc_layer = self.context.layers[proc_layer_name] + for offset, rule_name, name, value in proc_layer.scan( + context=self.context, + scanner=yarascan.YaraScanner(rules=rules), + sections=self.get_vma_maps(task), + ): + yield 0, ( + format_hints.Hex(offset), + task.tgid, + rule_name, + name, + value, + ) + + @staticmethod + def get_vma_maps( + task: interfaces.objects.ObjectInterface, + ) -> Iterable[Tuple[int, int]]: + """Creates a map of start/end addresses for each virtual memory area in a task. + + Args: + task: The task object of which to read the vmas from + + Returns: + An iterable of tuples containing start and end addresses for each descriptor + """ + if task.mm: + for vma in task.mm.get_mmap_iter(): + vm_size = vma.vm_end - vma.vm_start + yield (vma.vm_start, vm_size) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("PID", int), + ("Rule", str), + ("Component", str), + ("Value", bytes), + ], + self._generator(), + ) From f82a3f520facdc4e9ba973ddf8316473bbd190fe Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 4 Jan 2023 09:32:39 +0000 Subject: [PATCH 2/4] liniting for linux.vmayarascan --- volatility3/framework/plugins/linux/vmayarascan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index e9482089a..3efbd2ae1 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -10,6 +10,7 @@ from volatility3.framework.renderers import format_hints from volatility3.plugins import yarascan from volatility3.plugins.linux import pslist + class VmaYaraScan(interfaces.plugins.PluginInterface): """Scans all virtual memory areas for tasks using yara.""" From 6f82e3d8cf7b173f07c51634016413b5ef2243c6 Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 4 Jan 2023 09:36:20 +0000 Subject: [PATCH 3/4] remove unused variable in linux.vmayarascan --- volatility3/framework/plugins/linux/vmayarascan.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index 3efbd2ae1..c7a48cc14 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -64,8 +64,6 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): ] def _generator(self): - kernel = self.context.modules[self.config["kernel"]] - rules = yarascan.YaraScan.process_yara_options(dict(self.config)) filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) From 3b1e4ce0e2635db5ac860cf30bb5c5524d55632e Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 1 Feb 2023 11:27:44 +0000 Subject: [PATCH 4/4] Update linux.vmayarascan to pull requirements from the generic yarascan plugin --- .../framework/plugins/linux/vmayarascan.py | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index c7a48cc14..f0d42f6e3 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -1,4 +1,4 @@ -# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# This file is Copyright 2023 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # @@ -15,68 +15,71 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): """Scans all virtual memory areas for tasks using yara.""" _required_framework_version = (2, 4, 0) + _version = (1, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ - requirements.ModuleRequirement( - name="kernel", - description="Linux kernel", - architectures=["Intel32", "Intel64"], - ), - requirements.BooleanRequirement( - name="wide", - description="Match wide (unicode) strings", - default=False, - optional=True, - ), - requirements.StringRequirement( - name="yara_rules", description="Yara rules (as a string)", optional=True - ), - requirements.URIRequirement( - name="yara_file", description="Yara rules (as a file)", optional=True - ), - # This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code - # As such, there's a separate option to run compiled files, as happened with yara-3.9 and later - requirements.URIRequirement( - name="yara_compiled_file", - description="Yara compiled rules (as a file)", - optional=True, - ), - requirements.IntRequirement( - name="max_size", - default=0x40000000, - description="Set the maximum size (default is 1GB)", - optional=True, - ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(2, 0, 0) - ), - requirements.VersionRequirement( - name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) - ), + # create a list of requirements for vmayarascan + vmayarascan_requirements = [ requirements.ListRequirement( name="pid", element_type=int, description="Process IDs to include (all other processes are excluded)", optional=True, ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 0, 0) + ), + requirements.PluginRequirement( + name="yarascan", plugin=yarascan.YaraScan, version=(1, 1, 0) + ), + requirements.VersionRequirement( + name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) + ), + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), ] + # get base yarascan requirements + yarascan_requirements = yarascan.YaraScan.get_requirements() + + # remove TranslationLayerRequirement from the base yarascan requirements + # if this is not removed automagic will not find both the TranslationLayerRequirement + # for YaraScan and the ModuleRequirement for VmaYaraScan + yarascan_requirements = [ + requirement + for requirement in yarascan_requirements + if not isinstance(requirement, requirements.TranslationLayerRequirement) + ] + + # return the combined requirements + return yarascan_requirements + vmayarascan_requirements + def _generator(self): + # use yarascan to parse the yara options provided and create the rules rules = yarascan.YaraScan.process_yara_options(dict(self.config)) + # filter based on the pid option if provided filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) for task in pslist.PsList.list_tasks( context=self.context, vmlinux_module_name=self.config["kernel"], filter_func=filter_func, ): + + # attempt to create a process layer for each task and skip those + # that cannot (e.g. kernel threads) proc_layer_name = task.add_process_layer() if not proc_layer_name: continue + # get the proc_layer object from the context proc_layer = self.context.layers[proc_layer_name] + + # scan the process layer with the yarascanner for offset, rule_name, name, value in proc_layer.scan( context=self.context, scanner=yarascan.YaraScanner(rules=rules),