diff --git a/volatility/framework/automagic/linux.py b/volatility/framework/automagic/linux.py index b198243c1..c8a779402 100644 --- a/volatility/framework/automagic/linux.py +++ b/volatility/framework/automagic/linux.py @@ -6,6 +6,8 @@ from volatility.framework.automagic import linux_symbol_cache from volatility.framework.configuration import requirements from volatility.framework.layers import intel, scanners from volatility.framework.symbols import linux +from volatility.framework.symbols import utility as symbols_utility +from volatility.framework.objects import utility vollog = logging.getLogger(__name__) @@ -166,6 +168,171 @@ class LintelStacker(interfaces.automagic.StackerLayerInterface): class LinuxUtilities(object): """Class with multiple useful linux functions""" + # based on __d_path from the Linux kernel + @classmethod + def _do_get_path(cls, rdentry, rmnt, dentry, vfsmnt) -> str: + try: + rdentry.validate() + dentry.validate() + except InvalidDataException: + return "" + + ret_path = [] # type: typing.List[str] + + while dentry != rdentry or vfsmnt != rmnt: + dname = dentry.path() + if dname == "": + break + + ret_path.insert(0, dname.strip('/')) + if dentry == vfsmnt.get_mnt_root() or dentry == dentry.d_parent: + if vfsmnt.get_mnt_parent() == vfsmnt: + break + + dentry = vfsmnt.get_mnt_mountpoint() + vfsmnt = vfsmnt.get_mnt_parent() + + continue + + parent = dentry.d_parent + dentry = parent + + if ret_path == []: + return "" + + ret_val = '/'.join([str(p) for p in ret_path if p != ""]) + + if ret_val.startswith(("socket:", "pipe:")): + if ret_val.find("]") == -1: + try: + inode = dentry.d_inode + ino = inode.i_ino + except exceptions.InvalidAddressException: + ino = 0 + + ret_val = ret_val[:-1] + ":[{0}]".format(ino) + else: + ret_val = ret_val.replace("/", "") + + elif ret_val != "inotify": + ret_val = '/' + ret_val + + return ret_val + + # method used by 'older' kernels + # TODO: lookup when dentry_operations->d_name was merged into the mainline kernel for exact version + @classmethod + def _get_path_file(cls, task, filp) -> str: + rdentry = task.fs.get_root_dentry() + rmnt = task.fs.get_root_mnt() + dentry = filp.get_dentry() + vfsmnt = filp.get_vfsmnt() + + return LinuxUtilities._do_get_path(rdentry, rmnt, dentry, vfsmnt) + + @classmethod + def _get_new_sock_pipe_path(cls, task, filp) -> str: + dentry = filp.get_dentry() + + sym_addr = dentry.d_op.d_dname + + # IKELOS: _contet.symbol_space has already been masked (including ASLR) by the run() function of the calling plugin. This makes the code super clean + symbols = list(dentry._context.symbol_space.get_symbols_by_location(sym_addr)) + + if len(symbols) == 1: + sym = symbols[0].split("!")[1] + + if sym == "sockfs_dname": + pre_name = "socket" + + elif sym == "anon_inodefs_dname": + pre_name = "anon_inode" + + elif sym == "pipefs_dname": + pre_name = "pipe" + + elif sym == "simple_dname": + pre_name = self._get_path_file(filp) + + else: + pre_name = "".format(sym) + + ret = "{0}:[{1:d}]".format(pre_name, dentry.d_inode.i_ino) + + else: + ret = " {0:x}".format(sym_addr) + + return ret + + # a 'file' structure doesn't have enough information to properly restore its full path + # we need the root mount information from task_struct to determine this + @classmethod + def path_for_file(cls, task, filp) -> str: + try: + dentry = filp.get_dentry() + except exceptions.InvalidAddressException: + return "" + + if dentry == 0: + return "" + + dname_is_valid = False + + # TODO COMPARE THIS IN LSOF OUTPUT TO VOL2 + try: + if dentry.d_op and hasattr(dentry.d_op, "d_dname") and dentry.d_op.d_dname: + dname_is_valid = True + + except exceptions.InvalidAddressException: + dname_is_valid = False + + if dname_is_valid: + ret = LinuxUtilities._get_new_sock_pipe_path(task, filp) + else: + ret = LinuxUtilities._get_path_file(task, filp) + + return ret + + # IKELOS: 'task' will always be a task_struct as defined in the profile json. Do I type this in the parameter list? If so, how? + # IKELOS: what should the type of 'config' be? + @classmethod + def files_descriptors_for_process(cls, + config, + context: interfaces.context.ContextInterface, + task): + + fd_table = task.files.get_fds() + if fd_table == 0: + return + + max_fds = task.files.get_max_fds() + + # corruption check + if max_fds > 500000: + return + + file_type = config["vmlinux"] + constants.BANG + 'file' + + fds = utility.array_of_pointers(fd_table, count = max_fds, subtype = file_type, context = context) + + for (fd_num, filp) in enumerate(fds): + if filp != 0: + full_path = LinuxUtilities.path_for_file(task, filp) + + yield fd_num, filp, full_path + + @classmethod + def aslr_mask_symbol_table(cls, + config, + context: interfaces.context.ContextInterface): + + aslr_layer = config['primary.memory_layer'] + _, aslr_shift = LinuxUtilities.find_aslr(context, config["vmlinux"], aslr_layer) + + sym_table_name = config["vmlinux"] + sym_layer_name = config["primary"] + symbols_utility.mask_symbol_table(context.symbol_space[sym_table_name], context.memory[sym_layer_name].address_mask, aslr_shift) + @classmethod def find_aslr(cls, context: interfaces.context.ContextInterface, diff --git a/volatility/framework/symbols/linux/__init__.py b/volatility/framework/symbols/linux/__init__.py index e82c68d83..7791bcbd9 100644 --- a/volatility/framework/symbols/linux/__init__.py +++ b/volatility/framework/symbols/linux/__init__.py @@ -29,6 +29,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.set_type_class('files_struct', extensions.files_struct) self.set_type_class('vfsmount', extensions.vfsmount) self.set_type_class('mount', extensions.mount) + self.set_type_class('module', extensions.module) @classmethod def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]: diff --git a/volatility/framework/symbols/linux/extensions/__init__.py b/volatility/framework/symbols/linux/extensions/__init__.py index 31db88866..8564c95be 100644 --- a/volatility/framework/symbols/linux/extensions/__init__.py +++ b/volatility/framework/symbols/linux/extensions/__init__.py @@ -3,12 +3,32 @@ import typing from volatility.framework import constants from volatility.framework import exceptions, objects, interfaces -from volatility.framework.objects import utility -from volatility.framework.symbols import generic +from volatility.framework.objects import utility as objects_utility +from volatility.framework.automagic import linux +from volatility.framework.symbols import generic, utility # Keep these in a basic module, to prevent import cycles when symbol providers require them +class module(generic.GenericIntelProcess): + def get_init_size(self): + if hasattr(self, "init_layout"): + return self.init_layout.size + + elif hasattr(self, "init_size"): + return self.init_size + + raise AttributeError("module -> get_init_size: Unable to determine .init section size of module") + + def get_core_size(self): + if hasattr(self, "core_layout"): + return self.core_layout.size + + elif hasattr(self, "core_size"): + return self.core_size + + raise AttributeError("module -> get_core_size: Unable to determine initial size of module") + class task_struct(generic.GenericIntelProcess): def add_process_layer(self, config_prefix: str = None, @@ -18,8 +38,9 @@ class task_struct(generic.GenericIntelProcess): """ parent_layer = self._context.memory[self.vol.layer_name] - pgd = self.mm.pgd - if not pgd: + try: + pgd = self.mm.pgd + except exceptions.PagedInvalidAddressException: return None if not isinstance(parent_layer, interfaces.layers.TranslationLayerInterface): @@ -32,133 +53,6 @@ class task_struct(generic.GenericIntelProcess): # Add the constructed layer and return the name return self._add_process_layer(self._context, dtb, config_prefix, preferred_name) - # based on __d_path from the Linux kernel - def _do_get_path(self, rdentry, rmnt, dentry, vfsmnt) -> str: - try: - rdentry.validate() - dentry.validate() - except InvalidDataException: - return "" - - ret_path = [] # type: typing.List[str] - - try: - inode = dentry.d_inode - ino = inode.i_ino - except exceptions.InvalidAddressException: - ino = 0 - - while dentry != rdentry or vfsmnt != rmnt: - dname = dentry.path() - if dname == "": - break - - ret_path.insert(0, dname.strip('/')) - if dentry == vfsmnt.get_mnt_root() or dentry == dentry.d_parent: - if vfsmnt.get_mnt_parent() == vfsmnt: - break - - dentry = vfsmnt.get_mnt_mountpoint() - vfsmnt = vfsmnt.get_mnt_parent() - - continue - - parent = dentry.d_parent - dentry = parent - - if ret_path == []: - return "" - - ret_val = '/'.join([str(p) for p in ret_path if p != ""]) - - if ret_val.startswith(("socket:", "pipe:")): - if ret_val.find("]") == -1: - ret_val = ret_val[:-1] + ":[{0}]".format(ino) - else: - ret_val = ret_val.replace("/", "") - - elif ret_val != "inotify": - ret_val = '/' + ret_val - - return ret_val - - # old method - def _get_path_file(self, filp) -> str: - rdentry = self.fs.get_root_dentry() - rmnt = self.fs.get_root_mnt() - dentry = filp.dentry - vfsmnt = filp.vfsmnt - - return self._do_get_path(rdentry, rmnt, dentry, vfsmnt) - - def _get_new_sock_pipe_path(self, filp, layer_name) -> str: - dentry = filp.dentry - - sym_addr = dentry.d_op.d_dname - - # BUG - ikelos please read - # layer.address_mask is currently a @propery so the code is awkward, such as: - # sym_addr = sym_addr | self._context.memory[layer_name].address_mask - # is there a reason it couldn't just be a normal function so address_mask(sym_addr) would work? - # the second issue is that the mask I am getting is 0x1ffffffff, when I really need it OR'd with 0xffffffff00000000 in order to get the correct value - # third - having to pass in 'layer_name' way from the plugin is pretty ugly, is there a better way to get access to the mask? - - # TODO - this is currently not ASLR aware, which makes the lookups fail, after the masking issue is fixed, test for ASLR handling - - symbols = list(self._context.symbol_space.get_symbols_by_location(sym_addr)) - if len(symbols) == 1: - sym = symbols[0].split("!")[1] - - if sym == "sockfs_dname": - pre_name = "socket" - - elif sym == "anon_inodefs_dname": - pre_name = "anon_inode" - - elif sym == "pipefs_dname": - pre_name = "pipe" - - elif sym == "simple_dname": - pre_name = self._get_path_file(filp) - - else: - pre_name = "".format(sym) - - ret = "{0}:[{1:d}]".format(pre_name, dentry.d_inode.i_ino) - - else: - ret = " {0:d}".format(sym_addr) - - return ret - - # a 'file' structure doesn't have enough information to properly restore its full path - # we need the root mount information from task_struct to determine this - def path_for_file(self, filp, layer_name) -> str: - try: - dentry = filp.dentry - except exceptions.InvalidAddressException: - return "" - - if dentry == 0: - return "" - - dname_is_valid = False - - # TODO COMPARE THIS IN LSOF OUTPUT TO VOL2 - try: - if dentry.d_op and hasattr(dentry.d_op, "d_dname") and dentry.d_op.d_dname: - dname_is_valid = True - - except exceptions.InvalidAddressException: - dname_is_valid = False - - if dname_is_valid: - ret = self._get_new_sock_pipe_path(filp, layer_name) - else: - ret = self._get_path_file(filp) - - return ret - class fs_struct(objects.Struct): def get_root_dentry(self): # < 2.6.26 @@ -167,13 +61,11 @@ class fs_struct(objects.Struct): else: return self.root.dentry - raise AttributeError("Unable to find the root dentry") - def get_root_mnt(self): # < 2.6.26 if hasattr(self, "rootmnt"): return self.rootmnt - else: + elif hasattr(self.root, "mnt"): return self.root.mnt raise AttributeError("Unable to find the root mount") @@ -264,19 +156,47 @@ class vm_area_struct(objects.Struct): return retval # only parse the rwx bits - def protection(self) -> str: + def get_protection(self) -> str: return self._parse_flags(self.vm_flags & 0b1111, vm_area_struct.perm_flags) # used by malfind - def flags(self) -> str: + def get_flags(self) -> str: return self._parse_flags(self.vm_flags, extended_flags) - def page_offset(self) -> int: + def get_page_offset(self) -> int: if self.vm_file == 0: return 0 return self.vm_pgoff << constants.linux.PAGE_SHIFT + def get_name(self, task): + if self.vm_file != 0: + fname = linux.LinuxUtilities.path_for_file(task, self.vm_file) + elif self.vm_start <= task.mm.start_brk and self.vm_end >= task.mm.brk: + fname = "[heap]" + elif self.vm_start <= task.mm.start_stack and self.vm_end >= task.mm.start_stack: + fname = "[stack]" + elif hasattr(self.vm_mm.context, "vdso") and self.vm_start == self.vm_mm.context.vdso: + fname = "[vdso]" + else: + fname = "Anonymous Mapping" + + return fname + + # used by malfind + def is_suspicious(self): + ret = True + + flags_str = self.get_protection() + + if flags_str.find("VM_READ|VM_WRITE|VM_EXEC") != -1: + ret = True + + elif flags_str == "VM_READ|VM_EXEC" and self.vm_file != 0: + ret = True + + return ret + class qstr(objects.Struct): def name_as_str(self) -> str: if hasattr(self, "len"): @@ -285,7 +205,7 @@ class qstr(objects.Struct): str_length = 255 try: - ret = utility.pointer_to_string(self.name, str_length) + ret = objects_utility.pointer_to_string(self.name, str_length) except exceptions.InvalidAddressException: ret = "" @@ -296,23 +216,21 @@ class dentry(objects.Struct): return self.d_name.name_as_str() class struct_file(objects.Struct): - @property - def dentry(self) -> interfaces.objects.ObjectInterface: + def get_dentry(self) -> interfaces.objects.ObjectInterface: if hasattr(self, "f_dentry"): return self.f_dentry - else: + elif hasattr(self, "f_path"): return self.f_path.dentry - - raise AttributeError("Unable to find file -> dentry") + else: + raise AttributeError("Unable to find file -> dentry") - @property - def vfsmnt(self) -> interfaces.objects.ObjectInterface: + def get_vfsmnt(self) -> interfaces.objects.ObjectInterface: if hasattr(self, "f_vfsmnt"): return self.f_vfsmnt - else: + elif hasattr(self, "f_path"): return self.f_path.mnt - - raise AttributeError("Unable to find file -> vfs mount") + else: + raise AttributeError("Unable to find file -> vfs mount") class list_head(objects.Struct, collections.abc.Iterable): def to_list(self, @@ -350,44 +268,44 @@ class files_struct(objects.Struct): def get_fds(self) -> interfaces.objects.ObjectInterface: if hasattr(self, "fdt"): return self.fdt.fd.dereference() - else: + elif hasattr(self, "fd"): return self.fd.dereference() - - raise AttributeError("Unable to find files -> file descriptors") + else: + raise AttributeError("Unable to find files -> file descriptors") def get_max_fds(self) -> interfaces.objects.ObjectInterface: if hasattr(self, "fdt"): return self.fdt.max_fds - else: + elif hasattr(self, "max_fds"): return self.max_fds - - raise AttributeError("Unable to find files -> maximum file descriptors") + else: + raise AttributeError("Unable to find files -> maximum file descriptors") class mount(objects.Struct): def get_mnt_sb(self): if hasattr(self, "mnt"): return self.mnt.mnt_sb - else: + elif hasattr(self, "mnt_sb"): return self.mnt_sb - - raise AttributeError("Unable to find mount -> super block") + else: + raise AttributeError("Unable to find mount -> super block") def get_mnt_root(self): if hasattr(self, "mnt"): return self.mnt.mnt_root - else: + elif hasattr(self, "mnt_root"): return self.mnt_root - - raise AttributeError("Unable to find mount -> mount root") + else: + raise AttributeError("Unable to find mount -> mount root") def get_mnt_flags(self): if hasattr(self, "mnt"): return self.mnt.mnt_flags - else: + elif hasattr(self, "mnt_flags"): return self.mnt_flags - - raise AttributeError("Unable to find mount -> mount flags") + else: + raise AttributeError("Unable to find mount -> mount flags") def get_mnt_parent(self): return self.mnt_parent @@ -402,11 +320,9 @@ class vfsmount(objects.Struct): self.get_mnt_parent() != 0 def _get_real_mnt(self): - table_name = self.vol.type_name.split(constants.BANG)[0] - + table_name = self.vol.type_name.split(constants.BANG)[0] mount_struct = "{0}{1}mount".format(table_name, constants.BANG) - - offset = self._context.symbol_space.get_type(mount_struct).relative_child_offset("mnt") + offset = self._context.symbol_space.get_type(mount_struct).relative_child_offset("mnt") return self._context.object(mount_struct, self.vol.layer_name, offset = self.vol.offset - offset) diff --git a/volatility/plugins/linux/elfs.py b/volatility/plugins/linux/elfs.py new file mode 100644 index 000000000..256785ec7 --- /dev/null +++ b/volatility/plugins/linux/elfs.py @@ -0,0 +1,61 @@ +"""A module containing a collection of plugins that produce data +typically found in Linux's /proc file system. +""" + +from volatility.framework import renderers +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.automagic import linux +from volatility.plugins.linux import pslist + +class Elfs(plugins.PluginInterface): + """Lists all memory mapped ELF files for all processes""" + + @classmethod + def get_requirements(cls): + # Since we're calling the plugin, make sure we have the plugin's requirements + return pslist.PsList.get_requirements() + [] + + def _generator(self, tasks): + for task in tasks: + proc_layer_name = task.add_process_layer() + if proc_layer_name == None: + continue + + proc_layer = self.context.memory[proc_layer_name] + + name = utility.array_to_string(task.comm) + + for vma in task.mm.mmap_iter: + hdr = proc_layer.read(vma.vm_start, 4, pad = True) + if not (hdr[0] == 0x7f and hdr[1] == 0x45 and hdr[2] == 0x4c and hdr[3] == 0x46): + continue + + path = vma.get_name(task) + + yield ( + 0, + (task.pid, + name, + format_hints.Hex(vma.vm_start), + format_hints.Hex(vma.vm_end), + path + )) + + def run(self): + filter = pslist.PsList.create_filter([self.config.get('pid', None)]) + + plugin = pslist.PsList.list_tasks + + return renderers.TreeGrid( + [("PID", int), + ("Process", str), + ("Start", format_hints.Hex), + ("End", format_hints.Hex), + ("File Path", str)], + self._generator(plugin(self.context, + self.config['primary'], + self.config['vmlinux'], + filter = filter))) + diff --git a/volatility/plugins/linux/lsmod.py b/volatility/plugins/linux/lsmod.py new file mode 100644 index 000000000..3f444b9ea --- /dev/null +++ b/volatility/plugins/linux/lsmod.py @@ -0,0 +1,60 @@ +"""A module containing a collection of plugins that produce data +typically found in Linux's /proc file system. +""" +import datetime, os + +from volatility.framework import renderers, constants, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.automagic import linux +from volatility.plugins.linux import pslist + +class Lsmod(plugins.PluginInterface): + """Lists loaded kernel modules""" + + @classmethod + def get_requirements(cls): + return [requirements.TranslationLayerRequirement(name = 'primary', + description = 'Kernel Address Space', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolRequirement(name = "vmlinux", + description = "Linux Kernel")] + + @classmethod + def list_modules(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + vmlinux_symbols: str): + """Lists all the modules in the primary layer""" + + _, aslr_shift = linux.LinuxUtilities.find_aslr(context, vmlinux_symbols, layer_name) + vmlinux = context.module(vmlinux_symbols, layer_name, aslr_shift) + + module_head_addr = vmlinux.object(symbol_name = "modules").vol.offset + + modules = vmlinux.object(type_name = "list_head", offset = module_head_addr) + + table_name = modules.vol.type_name.split(constants.BANG)[0] + + for module in modules.to_list("{}{}module".format(table_name, constants.BANG), "list"): + yield module + + def _generator(self): + for module in self.list_modules(self.context, + self.config['primary'], + self.config['vmlinux']): + + mod_size = module.get_init_size() + module.get_core_size() + + mod_name = utility.array_to_string(module.name) + + yield 0, (format_hints.Hex(module.vol.offset), mod_name, mod_size) + + def run(self): + return renderers.TreeGrid( + [("Offset", format_hints.Hex), + ("Name", str), + ("Size", int)], + self._generator()) diff --git a/volatility/plugins/linux/lsof.py b/volatility/plugins/linux/lsof.py index 1be6607fd..71e6304cb 100644 --- a/volatility/plugins/linux/lsof.py +++ b/volatility/plugins/linux/lsof.py @@ -3,12 +3,15 @@ typically found in Linux's /proc file system. """ import logging +from volatility.framework import interfaces from volatility.framework.interfaces import plugins from volatility.framework import renderers from volatility.framework import constants from volatility.framework.automagic import linux from volatility.framework.renderers import format_hints from volatility.framework.objects import utility +from volatility.framework.symbols import utility as symbols_utility + from volatility.plugins.linux import pslist vollog = logging.getLogger(__name__) @@ -21,43 +24,29 @@ class Lsof(plugins.PluginInterface): # Since we're calling the plugin, make sure we have the plugin's requirements return pslist.PsList.get_requirements() + [] - # yields list of data, e.g.: calculate def _generator(self, tasks): - layer_name = self.config['primary.memory_layer'] - - _, aslr_shift = linux.LinuxUtilities.find_aslr(self.context, self.config["vmlinux"], layer_name) - vmlinux = self.context.module(self.config["vmlinux"], self.config["primary"], aslr_shift) - pointer_template = self.context.symbol_space[self.config['vmlinux']].get_type('pointer') - for task in tasks: - fd_table = task.files.get_fds() - if fd_table == 0: - continue + name = str(task.comm) + pid = int(task.pid) - max_fds = task.files.get_max_fds() - - proc_name = utility.array_to_string(task.comm) - - # corruption check - if max_fds > 500000: - continue - - fds = vmlinux.object(type_name="array", offset = fd_table.vol.offset, subtype = pointer_template, count = max_fds) - - for (i, fd_ptr) in enumerate(fds): - if fd_ptr: - filp = fd_ptr.dereference().cast(self.config["vmlinux"] + constants.BANG + 'file') - - full_path = task.path_for_file(filp, layer_name) - - yield (0, (task.pid, proc_name, i, full_path)) + for fd_num, _, full_path in linux.LinuxUtilities.files_descriptors_for_process(self.config, self.context, task): + yield (0, (pid, name, fd_num, full_path)) def run(self): - plugin = pslist.PsList(self.context, "plugins.Lsof") + linux.LinuxUtilities.aslr_mask_symbol_table(self.config, self.context) + + filter = pslist.PsList.create_filter([self.config.get('pid', None)]) + + plugin = pslist.PsList.list_tasks return renderers.TreeGrid( [("PID", int), ("Process", str), ("FD", int), ("Path", str)], - self._generator(plugin.list_tasks())) + self._generator(plugin(self.context, + self.config['primary'], + self.config['vmlinux'], + filter = filter))) + + diff --git a/volatility/plugins/linux/malfind.py b/volatility/plugins/linux/malfind.py new file mode 100644 index 000000000..8cee29cb0 --- /dev/null +++ b/volatility/plugins/linux/malfind.py @@ -0,0 +1,75 @@ +import volatility.framework.interfaces.plugins as interfaces_plugins +import volatility.framework.interfaces.renderers as interfaces_renderers +import volatility.plugins.linux.pslist as pslist +from volatility.framework import constants +from volatility.framework import renderers +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints + +class Malfind(interfaces_plugins.PluginInterface): + """Lists process memory ranges that potentially contain injected code""" + + @classmethod + def get_requirements(cls): + # Since we're calling the plugin, make sure we have the plugin's requirements + return pslist.PsList.get_requirements() + [] + + def list_injections(self, task): + """Generate memory regions for a process that may contain + injected code. + """ + + proc_layer_name = task.add_process_layer() + if proc_layer_name == None: + return + + proc_layer = self.context.memory[proc_layer_name] + + for vma in task.mm.mmap_iter: + if vma.is_suspicious() and vma.get_name(task) != "[vdso]": + data = proc_layer.read(vma.vm_start, 64, pad = True) + yield vma, data + + def _generator(self, tasks): + # determine if we're on a 32 or 64 bit kernel + if self.context.symbol_space.get_type(self.config["vmlinux"] + constants.BANG + "pointer").size == 4: + is_32bit_arch = True + else: + is_32bit_arch = False + + for task in tasks: + process_name = utility.array_to_string(task.comm) + + for vma, data in self.list_injections(task): + if is_32bit_arch: + architecture = "intel" + else: + architecture = "intel64" + + disasm = interfaces_renderers.Disassembly(data, vma.vm_start, architecture) + + yield (0, (task.pid, + process_name, + format_hints.Hex(vma.vm_start), + format_hints.Hex(vma.vm_end), + vma.get_protection(), + format_hints.HexBytes(data), + disasm)) + + def run(self): + filter = pslist.PsList.create_filter([self.config.get('pid', None)]) + + plugin = pslist.PsList.list_tasks + + return renderers.TreeGrid([("PID", int), + ("Process", str), + ("Start", format_hints.Hex), + ("End", format_hints.Hex), + ("Protection", str), + ("Hexdump", format_hints.HexBytes), + ("Disasm", interfaces_renderers.Disassembly)], + self._generator(plugin(self.context, + self.config['primary'], + self.config['vmlinux'], + filter = filter))) + diff --git a/volatility/plugins/linux/proc.py b/volatility/plugins/linux/proc.py index c15ac902f..f467107cb 100644 --- a/volatility/plugins/linux/proc.py +++ b/volatility/plugins/linux/proc.py @@ -7,9 +7,9 @@ from volatility.framework.configuration import requirements from volatility.framework.interfaces import plugins from volatility.framework.objects import utility from volatility.framework.renderers import format_hints +from volatility.framework.automagic import linux from volatility.plugins.linux import pslist - class Maps(plugins.PluginInterface): """Lists all memory maps for all processes""" @@ -30,20 +30,22 @@ class Maps(plugins.PluginInterface): name = utility.array_to_string(task.comm) for vma in task.mm.mmap_iter: - flags = vma.protection() - page_offset = vma.page_offset() + flags = vma.get_protection() + page_offset = vma.get_page_offset() major = 0 minor = 0 inode = 0 path = "" if vma.vm_file != 0: - inode_object = vma.vm_file.f_path.dentry.d_inode - major = inode_object.i_sb.major - minor = inode_object.i_sb.minor - inode = inode_object.i_ino - # TODO - update the second parameter to hopefully go away once extension is updated - path = task.path_for_file(vma.vm_file, "") + dentry = vma.vm_file.get_dentry() + if dentry != 0: + inode_object = dentry.d_inode + major = inode_object.i_sb.major + minor = inode_object.i_sb.minor + inode = inode_object.i_ino + + path = vma.get_name(task) yield ( 0, @@ -60,6 +62,10 @@ class Maps(plugins.PluginInterface): )) def run(self): + filter = pslist.PsList.create_filter([self.config.get('pid', None)]) + + plugin = pslist.PsList.list_tasks + return renderers.TreeGrid( [("PID", int), ("Process", str), @@ -71,6 +77,8 @@ class Maps(plugins.PluginInterface): ("Minor", int), ("Inode", int), ("File Path", str)], - self._generator(pslist.PsList.list_tasks(self.context, - self.config['primary'], - self.config['vmlinux']))) + self._generator(plugin(self.context, + self.config['primary'], + self.config['vmlinux'], + filter = filter))) + diff --git a/volatility/plugins/linux/pslist.py b/volatility/plugins/linux/pslist.py index 598970096..d09a00880 100644 --- a/volatility/plugins/linux/pslist.py +++ b/volatility/plugins/linux/pslist.py @@ -1,5 +1,5 @@ import volatility.framework.interfaces.plugins as interfaces_plugins -from volatility.framework import renderers +from volatility.framework import renderers, interfaces from volatility.framework.automagic import linux from volatility.framework.configuration import requirements from volatility.framework.objects import utility @@ -16,8 +16,21 @@ class PsList(interfaces_plugins.PluginInterface): requirements.SymbolRequirement(name = "vmlinux", description = "Linux Kernel")] + @classmethod + def create_filter(cls, pid_list: typing.List[int] = None) -> typing.Callable[[int], bool]: + filter = lambda _: False + # FIXME: mypy #4973 or #2608 + pid_list = pid_list or [] + filter_list = [x for x in pid_list if x is not None] + if filter_list: + filter = lambda x: x not in filter_list + return filter + def _generator(self): - for task in self.list_tasks(self.context, self.config['primary'], self.config['vmlinux']): + for task in self.list_tasks(self.context, + self.config['primary'], + self.config['vmlinux'], + filter = self.create_filter([self.config.get('pid', None)])): pid = task.pid ppid = 0 if task.parent: @@ -26,13 +39,18 @@ class PsList(interfaces_plugins.PluginInterface): yield (0, (pid, ppid, name)) @classmethod - def list_tasks(cls, context, primary_layer: str, vmlinux_table: str): + def list_tasks(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + vmlinux_symbols: str, + filter: typing.Callable[[int], bool] = lambda _: False) -> \ + typing.Iterable[interfaces.objects.ObjectInterface]: + """Lists all the tasks in the primary layer""" - layer_name = context.memory[primary_layer].config['memory_layer'] - _, aslr_shift = linux.LinuxUtilities.find_aslr(context, vmlinux_table, layer_name) - vmlinux = context.module(vmlinux_table, primary_layer, aslr_shift) + _, aslr_shift = linux.LinuxUtilities.find_aslr(context, vmlinux_symbols, layer_name) + vmlinux = context.module(vmlinux_symbols, layer_name, aslr_shift) init_task = vmlinux.object(symbol_name = "init_task") for task in init_task.tasks: diff --git a/volatility/plugins/linux/pstree.py b/volatility/plugins/linux/pstree.py new file mode 100644 index 000000000..491c269ff --- /dev/null +++ b/volatility/plugins/linux/pstree.py @@ -0,0 +1,52 @@ +from volatility.framework.renderers import format_hints +from volatility.framework.objects import utility +from volatility.plugins.linux import pslist + +class PsTree(pslist.PsList): + """Plugin for listing processes in a tree based on their parent process ID """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._processes = {} + self._levels = {} + self._children = {} + + def find_level(self, pid): + """Finds how deep the pid is in the processes list""" + seen = set([]) + seen.add(pid) + level = 0 + proc = self._processes.get(pid, None) + while proc is not None and proc.parent != 0 and proc.parent.pid not in seen: + ppid = int(proc.parent.pid) + + child_list = self._children.get(ppid, set([])) + child_list.add(proc.pid) + self._children[ppid] = child_list + proc = self._processes.get(ppid, None) + level += 1 + self._levels[pid] = level + + def _generator(self): + """Generates the """ + for proc in self.list_tasks(self.context, self.config['primary'], self.config['vmlinux']): + self._processes[proc.pid] = proc + + # Build the child/level maps + for pid in self._processes: + self.find_level(pid) + + def yield_processes(pid): + proc = self._processes[pid] + row = (proc.pid, + proc.parent.pid, + utility.array_to_string(proc.comm)) + + yield (self._levels[pid] - 1, row) + for child_pid in self._children.get(pid, []): + yield from yield_processes(child_pid) + + for pid in self._levels: + if self._levels[pid] == 1: + yield from yield_processes(pid) +