mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-12 04:37:38 +02:00
major updates and new plugins
This commit is contained in:
@@ -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 = "<unsupported d_op symbol: {0}>".format(sym)
|
||||
|
||||
ret = "{0}:[{1:d}]".format(pre_name, dentry.d_inode.i_ino)
|
||||
|
||||
else:
|
||||
ret = "<invalid d_dname pointer> {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,
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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 = "<unsupported d_op symbol: {0}>".format(sym)
|
||||
|
||||
ret = "{0}:[{1:d}]".format(pre_name, dentry.d_inode.i_ino)
|
||||
|
||||
else:
|
||||
ret = "<invalid d_dname pointer> {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)
|
||||
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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())
|
||||
@@ -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)))
|
||||
|
||||
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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)))
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user