Move all core plugins over to framework/plugins.

This should have no impact functionality-wise.
The statistics plugin was left out a) as an example and b) because it
was committed by mistake in the first place and was never meant to be a
real plugin.
This commit is contained in:
Mike Auty
2018-12-16 13:40:15 +00:00
parent 9824538bd9
commit 35ad2325a8
38 changed files with 9 additions and 0 deletions
@@ -0,0 +1,46 @@
import json
import logging
from typing import List
from volatility.framework import renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins
vollog = logging.getLogger(__name__)
class ConfigWriter(plugins.PluginInterface):
"""Runs the automagics and both prints and outputs configuration in the output directory"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.BooleanRequirement(name = 'extra',
description = 'Outputs whole configuration tree',
default = False,
optional = True)
]
def _generator(self):
filename = "config.json"
config = dict(self.build_configuration())
if self.config.get('extra', False):
vollog.debug("Outputting additional information, this will NOT work with the -c option")
config = dict(self.context.config)
filename = "config.extra"
try:
filedata = plugins.FileInterface(filename)
filedata.data.write(bytes(json.dumps(config, sort_keys = True, indent = 2), 'latin-1'))
self.produce_file(filedata)
except Exception:
vollog.warn("Unable to JSON encode configuration")
for k, v in config.items():
yield (0, (k, json.dumps(v)))
def run(self):
return renderers.TreeGrid([("Key", str),
("Value", str)],
self._generator())
+107
View File
@@ -0,0 +1,107 @@
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
import datetime
import struct
from typing import List
from volatility.framework import constants, renderers, symbols, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins
from volatility.framework.layers import scanners
from volatility.framework.objects import utility
from volatility.framework.symbols.linux.bash import BashIntermedSymbols
from volatility.plugins import timeliner
from volatility.plugins.linux import pslist
class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Recovers bash command history from memory"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "vmlinux",
description = "Linux Kernel")]
def _generator(self, tasks):
is_32bit = not symbols.symbol_table_is_64bit(self.context, self.config["vmlinux"])
if is_32bit:
pack_format = "I"
bash_json_file = "bash32"
else:
pack_format = "Q"
bash_json_file = "bash64"
bash_table_name = BashIntermedSymbols.create(self.context,
self.config_path,
"linux",
bash_json_file)
ts_offset = self.context.symbol_space.get_type(
bash_table_name + constants.BANG + "hist_entry").relative_child_offset("timestamp")
for task in tasks:
task_name = utility.array_to_string(task.comm)
if task_name not in ["bash", "sh", "dash"]:
continue
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
continue
proc_layer = self.context.memory[proc_layer_name]
bang_addrs = []
# find '#' values on the heap
for address in proc_layer.scan(self.context,
scanners.BytesScanner(b"#"),
sections = task.get_process_memory_sections(heap_only = True)):
bang_addrs.append(struct.pack(pack_format, address))
history_entries = []
for address, _ in proc_layer.scan(self.context,
scanners.MultiStringScanner(bang_addrs),
sections = task.get_process_memory_sections(heap_only = True)):
hist = self.context.object(bash_table_name + constants.BANG + "hist_entry",
offset = address - ts_offset,
layer_name = proc_layer_name)
if hist.is_valid():
history_entries.append(hist)
for hist in sorted(history_entries, key = lambda x: x.get_time_as_integer()):
yield (0, (task.pid, task_name, hist.get_time_object(), hist.get_command()))
def run(self):
filt = pslist.PsList.create_filter([self.config.get('pid', None)])
plugin = pslist.PsList.list_tasks
return renderers.TreeGrid(
[("PID", int),
("Process", str),
("CommandTime", datetime.datetime),
("Command", str)],
self._generator(plugin(self.context,
self.config['primary'],
self.config['vmlinux'],
filter = filt)))
def generate_timeline(self):
filt = pslist.PsList.create_filter([self.config.get('pid', None)])
plugin = pslist.PsList.list_tasks
for row in self._generator(plugin(self.context,
self.config['primary'],
self.config['vmlinux'],
filter = filt)):
_depth, row_data = row
description = "{} ({}): \"{}\"".format(row_data[0], row_data[1], row_data[3])
yield (description, timeliner.TimeLinerType.CREATED, row_data[2])
@@ -0,0 +1,96 @@
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
import logging
from typing import List
from volatility.framework import exceptions, interfaces
from volatility.framework import renderers
from volatility.framework.automagic import linux
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins
from volatility.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
class Check_afinfo(plugins.PluginInterface):
"""Verifies the operation function pointers of network protocols"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "vmlinux",
description = "Linux Kernel")]
# returns whether the symbol is found within the kernel (system.map) or not
def _is_known_address(self, handler_addr):
symbols = list(self.context.symbol_space.get_symbols_by_location(handler_addr))
return len(symbols) > 0
def _check_members(self, var_ops, var_name, members):
for check in members:
# redhat-specific garbage
if check.startswith("__UNIQUE_ID_rh_kabi_hide"):
continue
if check == "write":
addr = var_ops.member(attr = 'write')
else:
addr = getattr(var_ops, check)
if addr and addr != 0 and not self._is_known_address(addr):
yield check, addr
def _check_afinfo(self, var_name, var, op_members, seq_members):
for hooked_member, hook_address in self._check_members(var.seq_fops, var_name, op_members):
yield var_name, hooked_member, hook_address
# newer kernels
if var.has_member("seq_ops"):
for hooked_member, hook_address in self._check_members(var.seq_ops, var_name, seq_members):
yield var_name, hooked_member, hook_address
# this is the most commonly hooked member by rootkits, so a force a check on it
elif not self._is_known_address(var.seq_show):
yield var_name, "show", var.seq_show
def _generator(self):
_, aslr_shift = linux.LinuxUtilities.find_aslr(self.context, self.config['vmlinux'], self.config['primary'])
vmlinux = self.context.module(self.config['vmlinux'], self.config['primary'], aslr_shift)
linux.LinuxUtilities.aslr_mask_symbol_table(self.context,
self.config['primary'],
self.config['vmlinux'],
aslr_shift)
op_members = vmlinux.get_type('file_operations').members
seq_members = vmlinux.get_type('seq_operations').members
tcp = ("tcp_seq_afinfo", ["tcp6_seq_afinfo", "tcp4_seq_afinfo"])
udp = ("udp_seq_afinfo", ["udplite6_seq_afinfo", "udp6_seq_afinfo", "udplite4_seq_afinfo", "udp4_seq_afinfo"])
protocols = [tcp, udp]
for (struct_type, global_vars) in protocols:
for global_var_name in global_vars:
# this will lookup fail for the IPv6 protocols on kernels without IPv6 support
try:
global_var = vmlinux.get_symbol(global_var_name)
except exceptions.SymbolError:
continue
global_var = vmlinux.object(type_name = struct_type, offset = global_var.address)
for name, member, address in self._check_afinfo(global_var_name, global_var, op_members, seq_members):
yield 0, (name, member, format_hints.Hex(address))
def run(self):
return renderers.TreeGrid(
[("Symbol Name", str),
("Member", str),
("Handler Address", format_hints.Hex)],
self._generator())
@@ -0,0 +1,187 @@
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
import logging
from typing import List
from volatility.framework import exceptions, interfaces
from volatility.framework import renderers, constants
from volatility.framework.automagic import linux
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins
from volatility.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
try:
import capstone
has_capstone = True
except ImportError:
has_capstone = False
class Check_syscall(plugins.PluginInterface):
"""Check system call table for hooks"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "vmlinux",
description = "Linux Kernel")]
def _get_table_size_next_symbol(self, table_addr, ptr_sz, vmlinux):
"""
Returns the size of the table based on the next symbol
"""
ret = 0
sym_table = self.context.symbol_space[vmlinux.name]
sorted_symbols = sorted([(sym_table.get_symbol(sn).address, sn) for sn in sym_table.symbols])
sym_address = 0
for tmp_sym_address, sym_name in sorted_symbols:
if tmp_sym_address > table_addr:
sym_address = tmp_sym_address
break
if sym_address > 0:
ret = int((sym_address - table_addr) / ptr_sz)
return ret
def _get_table_size_meta(self, vmlinux):
"""
returns the number of symbols that start with __syscall_meta__
this is a fast way to determine the number of system calls, but not the most accurate
"""
return len(
[sym for sym in self.context.symbol_space[vmlinux.name].symbols if sym.startswith("__syscall_meta__")])
def _get_table_info_other(self, table_addr, ptr_sz, vmlinux):
table_size_meta = self._get_table_size_meta(vmlinux)
table_size_syms = self._get_table_size_next_symbol(table_addr, ptr_sz, vmlinux)
sizes = [size for size in [table_size_meta, table_size_syms] if size > 0]
table_size = min(sizes)
return table_size
def _get_table_info_disassembly(self, ptr_sz, vmlinux):
"""
Find the size of the system call table by disassembling functions
that immediately reference it in their first isntruction
This is in the form 'cmp reg,NR_syscalls'
"""
table_size = 0
if not has_capstone:
return table_size
if ptr_sz == 4:
syscall_entry_func = "sysenter_do_call"
mode = capstone.CS_MODE_32
else:
syscall_entry_func = "system_call_fastpath"
mode = capstone.CS_MODE_64
md = capstone.Cs(capstone.CS_ARCH_X86, mode)
try:
func_addr = self.context.symbol_space.get_symbol(vmlinux.name + constants.BANG + syscall_entry_func).address
except exceptions.SymbolError as e:
# if we can't find the disassemble function then bail and rely on a different method
return 0
data = self.context.memory.read(self.config['primary'], func_addr, 6)
for (address, size, mnemonic, op_str) in md.disasm_lite(data, func_addr):
if mnemonic == 'CMP':
table_size = int(op_str.split(",")[1].strip()) & 0xffff
break
return table_size
def _get_table_info(self, vmlinux, table_name, ptr_sz):
table_sym = self.context.symbol_space.get_symbol(vmlinux.name + constants.BANG + table_name)
table_size = self._get_table_info_disassembly(ptr_sz, vmlinux)
if table_size == 0:
table_size = self._get_table_info_other(table_sym.address, ptr_sz, vmlinux)
if table_size == 0:
vollog.error("Unable to get system call table size")
return 0, 0
return table_sym.address, table_size
# TODO - add finding and parsing unistd.h once cached file enumeration is added
def _generator(self):
_, aslr_shift = linux.LinuxUtilities.find_aslr(self.context, self.config['vmlinux'], self.config['primary'])
vmlinux = self.context.module(self.config['vmlinux'], self.config['primary'], aslr_shift)
linux.LinuxUtilities.aslr_mask_symbol_table(self.context,
self.config['vmlinux'],
self.config['primary'],
aslr_shift)
ptr_sz = vmlinux.get_type("pointer").size
if ptr_sz == 4:
table_name = "32bit"
else:
table_name = "64bit"
try:
table_info = self._get_table_info(vmlinux, "sys_call_table", ptr_sz)
except exceptions.SymbolError:
vollog.error("Unable to find the system call table. Exiting.")
return
tables = [(table_name, table_info)]
# this table is only present on 64 bit systems with 32 bit emulation
# enabled in order to support 32 bit programs and libraries
# if the symbol isn't there then the support isn't in the kernel and so we skip it
try:
ia32_symbol = self.context.symbol_space.get_symbol(vmlinux.name + constants.BANG + "ia32_sys_call_table")
except exceptions.SymbolError:
ia32_symbol = None
if ia32_symbol != None:
ia32_info = self._get_table_info(vmlinux, "ia32_sys_call_table", ptr_sz)
tables.append(("32bit", ia32_info))
for (table_name, (tableaddr, tblsz)) in tables:
table = vmlinux.object(type_name = "array", subtype = vmlinux.get_type("pointer"),
offset = tableaddr, count = tblsz)
for (i, call_addr) in enumerate(table):
if not call_addr:
continue
symbols = list(self.context.symbol_space.get_symbols_by_location(call_addr))
if len(symbols) > 0:
sym_name = str(symbols[0].split(constants.BANG)[1]) if constants.BANG in symbols[0] else \
str(symbols[0])
else:
sym_name = "UNKNOWN"
yield (0, (format_hints.Hex(tableaddr), table_name, i, format_hints.Hex(call_addr), sym_name))
def run(self):
return renderers.TreeGrid(
[("Table Address", format_hints.Hex),
("Table Name", str),
("Index", int),
("Handler Address", format_hints.Hex),
("Handler Symbol", str)],
self._generator())
@@ -0,0 +1,66 @@
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
from typing import List
from volatility.framework import renderers, 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.plugins.linux import pslist
class Elfs(plugins.PluginInterface):
"""Lists all memory mapped ELF files for all processes"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "vmlinux",
description = "Linux Kernel")]
def _generator(self, tasks):
for task in tasks:
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
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):
filt = 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 = filt)))
@@ -0,0 +1,59 @@
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
from volatility.framework import renderers, constants, interfaces
from volatility.framework.automagic import linux
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
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.get_symbol("modules").address
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())
@@ -0,0 +1,51 @@
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
import logging
from volatility.framework import renderers
from volatility.framework.automagic import linux
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins
from volatility.framework.objects import utility
from volatility.plugins.linux import pslist
vollog = logging.getLogger(__name__)
class Lsof(plugins.PluginInterface):
"""Lists all memory maps for all processes"""
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "vmlinux",
description = "Linux Kernel")]
def _generator(self, tasks):
for task in tasks:
name = utility.array_to_string(task.comm)
pid = int(task.pid)
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):
linux.LinuxUtilities.aslr_mask_symbol_table(self.context, self.config['vmlinux'], self.config['primary'])
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(self.context,
self.config['primary'],
self.config['vmlinux'],
filter = filter)))
@@ -0,0 +1,81 @@
from typing import List
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, interfaces
from volatility.framework import renderers
from volatility.framework.configuration import requirements
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) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "vmlinux",
description = "Linux Kernel")]
def list_injections(self, task):
"""Generate memory regions for a process that may contain
injected code.
"""
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
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):
filt = 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 = filt)))
@@ -0,0 +1,82 @@
"""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.configuration import requirements
from volatility.framework.interfaces import plugins
from volatility.framework.objects import utility
from volatility.framework.renderers import format_hints
from volatility.plugins.linux import pslist
class Maps(plugins.PluginInterface):
"""Lists all memory maps for all processes"""
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "vmlinux",
description = "Linux Kernel")]
def _generator(self, tasks):
for task in tasks:
if not task.mm:
continue
name = utility.array_to_string(task.comm)
for vma in task.mm.mmap_iter:
flags = vma.get_protection()
page_offset = vma.get_page_offset()
major = 0
minor = 0
inode = 0
if vma.vm_file != 0:
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,
(task.pid,
name,
format_hints.Hex(vma.vm_start),
format_hints.Hex(vma.vm_end),
flags,
format_hints.Hex(page_offset),
major,
minor,
inode,
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),
("Flags", str),
("PgOff", format_hints.Hex),
("Major", int),
("Minor", int),
("Inode", int),
("File Path", str)],
self._generator(plugin(self.context,
self.config['primary'],
self.config['vmlinux'],
filter = filter)))
@@ -0,0 +1,67 @@
from typing import Callable, Iterable, List
import volatility.framework.interfaces.plugins as interfaces_plugins
from volatility.framework import renderers, interfaces
from volatility.framework.automagic import linux
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
class PsList(interfaces_plugins.PluginInterface):
"""Lists the processes present in a particular linux memory image"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "vmlinux",
description = "Linux Kernel")]
@classmethod
def create_filter(cls, pid_list: List[int] = None) -> Callable[[int], bool]:
# 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:
def filter_func(x):
return x not in filter_list
return filter_func
else:
return lambda _: False
def _generator(self):
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:
ppid = task.parent.pid
name = utility.array_to_string(task.comm)
yield (0, (pid, ppid, name))
@classmethod
def list_tasks(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
vmlinux_symbols: str,
filter: Callable[[int], bool] = lambda _: False) -> Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the tasks in the primary layer"""
_, 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:
if not filter(task):
yield task
def run(self):
return renderers.TreeGrid([("PID", int),
("PPID", int),
("COMM", str)],
self._generator())
@@ -0,0 +1,51 @@
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)
+97
View File
@@ -0,0 +1,97 @@
"""In-memory artifacts from OSX systems"""
from typing import Iterator, Tuple, Any, Generator, List
from volatility.framework import exceptions, renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins
from volatility.framework.objects import utility
from volatility.plugins.mac import pslist
class Psaux(plugins.PluginInterface):
"""Recovers program command line arguments"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "darwin",
description = "Mac Kernel")]
def _generator(self, tasks: Iterator[Any]) -> Generator[Tuple[int, Tuple[int, str, int, str]], None, None]:
for task in tasks:
proc_layer_name = task.add_process_layer()
if proc_layer_name is None:
continue
proc_layer = self.context.memory[proc_layer_name]
argsstart = task.user_stack - task.p_argslen
if (not proc_layer.is_valid(argsstart) or
not task.p_argslen or not task.p_argc):
continue
# Add one because the first two are usually duplicates
argc = task.p_argc + 1
# smear protection
if argc > 1024:
continue
task_name = utility.array_to_string(task.p_comm)
args = []
while argc > 0:
try:
arg = proc_layer.read(argsstart, 256)
except exceptions.PagedInvalidAddressException:
break
idx = arg.find(b'\x00')
if idx > -1:
arg = arg[:idx]
argsstart += len(str(arg)) + 1
# deal with the stupid alignment (leading nulls) and arg duplication
if not args:
while argsstart < task.user_stack:
try:
check = proc_layer.read(argsstart, 1)
except exceptions.PagedInvalidAddressException:
break
if check != b"\x00":
break
argsstart = argsstart + 1
args.append(arg)
# also check for initial duplicates since OS X is painful
elif arg != args[0]:
args.append(arg)
argc -= 1
args_str = " ".join([s.decode("utf-8") for s in args])
yield (0, (task.p_pid, task_name, task.p_argc, args_str))
def run(self) -> renderers.TreeGrid:
filter = pslist.PsList.create_filter([self.config.get('pid', None)])
plugin = pslist.PsList.list_tasks
return renderers.TreeGrid(
[("PID", int),
("Process", str),
("Argc", int),
("Arguments", str)],
self._generator(plugin(self.context,
self.config['primary'],
self.config['darwin'],
filter = filter)))
@@ -0,0 +1,74 @@
import logging
from typing import Callable, Generator, List
import volatility.framework.interfaces.plugins as interfaces_plugins
from volatility.framework import renderers, interfaces
from volatility.framework.automagic import mac
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
vollog = logging.getLogger(__name__)
class PsList(interfaces_plugins.PluginInterface):
"""Lists the processes present in a particular mac memory image"""
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "darwin",
description = "Mac Kernel")]
@classmethod
def create_filter(cls, pid_list: List[int] = None) -> 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['darwin'],
filter = self.create_filter([self.config.get('pid', None)])):
pid = task.p_pid
ppid = task.p_ppid
name = utility.array_to_string(task.p_comm)
yield (0, (pid, ppid, name))
@classmethod
def list_tasks(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
mac_symbols: str,
filter: Callable[[int], bool] = lambda _: False) \
-> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Lists all the tasks in the primary layer"""
aslr_shift = mac.MacUtilities.find_aslr(context, mac_symbols, layer_name)
darwin = context.module(mac_symbols, layer_name, aslr_shift)
proc = darwin.object(symbol_name = "allproc").lh_first
seen = {}
while proc != None and proc.vol.offset != 0:
if proc.vol.offset in seen:
vollog.log(logging.INFO, "Recursive process list detected (a result of non-atomic acquisition).")
break
else:
seen[proc.vol.offset] = 1
yield proc
proc = proc.p_list.le_next.dereference()
def run(self):
return renderers.TreeGrid([("PID", int),
("PPID", int),
("COMM", str)],
self._generator())
+152
View File
@@ -0,0 +1,152 @@
import abc
import datetime
import enum
import io
import json
import logging
import traceback
from typing import Generator, Iterable, List, Optional, Tuple, Type
from volatility import framework
from volatility.framework import renderers, automagic, interfaces, plugins, exceptions
from volatility.framework.configuration import requirements
vollog = logging.getLogger(__name__)
class TimeLinerType(enum.IntEnum):
CREATED = 1
MODIFIED = 2
ACCESSED = 3
CHANGED = 4
class TimeLinerInterface(metaclass = abc.ABCMeta):
"""Interface defining methods that timeliner will use to generate a body file"""
@abc.abstractmethod
def generate_timeline(self) -> Generator[Tuple[str, TimeLinerType, datetime.datetime], None, None]:
"""Method generates Tuples of (description, timestamp_type, timestamp)
These need not be generated in any particular order, sorting will be done later
"""
class Timeliner(interfaces.plugins.PluginInterface):
"""Runs all relevant plugins that provide time related information and orders the results by time"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.timeline = {}
self.usable_plugins = None
self.automagics = None
@classmethod
def get_usable_plugins(cls, selected_list: List[str] = None) -> List[Type]:
# Initialize for the run
plugin_list = list(framework.class_subclasses(TimeLinerInterface))
# Get the filter from the configuration
def passthrough(_n, _s):
return True
filter_func = passthrough
if selected_list:
def filter_plugins(name, selected):
return any([s in name for s in selected])
filter_func = filter_plugins
return [plugin_class for plugin_class in plugin_list if filter_func(plugin_class.__name__, selected_list)]
@classmethod
def get_requirements(cls):
return [requirements.StringRequirement(name = 'plugins',
description = "Comma separated list of plugins to run",
optional = True,
default = None),
requirements.BooleanRequirement(name = 'record-config',
description = "Whether to record the state of all the plugins once complete",
optional = True,
default = False)]
def _generator(self, runable_plugins: List[TimeLinerInterface]) -> Optional[Iterable[Tuple[int, Tuple]]]:
"""Takes a timeline, sorts it and output the data from each relevant row from each plugin"""
# Generate the results for each plugin
for plugin in runable_plugins:
plugin_name = plugin.__class__.__name__
try:
vollog.log(logging.INFO, "Running {}".format(plugin_name))
for (item, timestamp_type, timestamp) in plugin.generate_timeline():
times = self.timeline.get((plugin_name, item), {})
if times.get(timestamp_type, None) is not None:
vollog.debug(
"Multiple timestamps for the same plugin/file combination found: {} {}".format(plugin_name,
item))
times[timestamp_type] = timestamp
self.timeline[(plugin_name, item)] = times
except Exception:
# FIXME: traceback shouldn't be printed directly, but logged instead
traceback.print_exc()
vollog.log(logging.INFO, "Exception occurred running plugin: {}".format(plugin_name))
for (plugin_name, item) in self.timeline:
times = self.timeline[(plugin_name, item)]
data = (0, [plugin_name, item,
times.get(TimeLinerType.CREATED, renderers.NotApplicableValue()),
times.get(TimeLinerType.MODIFIED, renderers.NotApplicableValue()),
times.get(TimeLinerType.ACCESSED, renderers.NotApplicableValue()),
times.get(TimeLinerType.CHANGED, renderers.NotApplicableValue())])
yield data
def run(self):
"""Isolate each plugin and run it"""
# Use all the plugins if there's no filter
self.usable_plugins = self.usable_plugins or self.get_usable_plugins()
self.automagics = self.automagics or automagic.available(self._context)
runable_plugins = []
# Identify plugins that we can run which output datetimes
for plugin_class in self.usable_plugins:
try:
automagics = automagic.choose_automagic(self.automagics, plugin_class)
plugin = plugins.run_plugin(self.context,
automagics,
plugin_class,
self.config_path,
self._progress_callback,
self._file_consumer)
if isinstance(plugin, TimeLinerInterface):
runable_plugins.append(plugin)
except exceptions.UnsatisfiedException as excp:
# Remove the failed plugin from the list and continue
vollog.debug("Unable to satisfy {}: {}".format(plugin_class.__name__, excp.unsatisfied))
continue
if self.config['record-config']:
total_config = {}
for plugin in runable_plugins:
old_dict = dict(plugin.build_configuration())
for entry in old_dict:
total_config[interfaces.configuration.path_join(plugin.__class__.__name__, entry)] = old_dict[entry]
filedata = interfaces.plugins.FileInterface("config.json")
with io.TextIOWrapper(filedata.data, write_through = True) as fp:
json.dump(total_config, fp, sort_keys = True, indent = 2)
self.produce_file(filedata)
return renderers.TreeGrid(columns = [("Plugin", str),
("Description", str),
("Created Date", datetime.datetime),
("Modified Date", datetime.datetime),
("Accessed Date", datetime.datetime),
("Changed Date", datetime.datetime)],
generator = self._generator(runable_plugins))
def build_configuration(self):
"""Builds the configuration to save for the plugin such that it can be reconstructed"""
vollog.warning("Unable to record configuration data for the timeliner plugin")
return []
@@ -0,0 +1,56 @@
from typing import List
import volatility.framework.constants as constants
import volatility.framework.interfaces.plugins as interfaces_plugins
from volatility.framework import exceptions, renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.plugins.windows import pslist
class CmdLine(interfaces_plugins.PluginInterface):
"""Lists process command line arguments"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self, procs):
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
# TODO: what kind of exceptions could this raise and what should we do?
proc_layer_name = proc.add_process_layer()
try:
peb = self._context.object(self.config["nt_symbols"] + constants.BANG + "_PEB",
layer_name = proc_layer_name,
offset = proc.Peb)
result_text = peb.ProcessParameters.CommandLine.get_string()
except exceptions.SwappedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is inaccessible (swapped)".format(exp.invalid_address)
except exceptions.PagedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is not valid (process exited?)".format(exp.invalid_address)
yield (0, (proc.UniqueProcessId,
process_name,
result_text))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Args", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,104 @@
import logging
import ntpath
from typing import List
import volatility.framework.constants as constants
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.pslist as pslist
import volatility.plugins.windows.vadinfo as vadinfo
from volatility.framework import interfaces
from volatility.framework import renderers
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
vollog = logging.getLogger(__name__)
class DllDump(interfaces_plugins.PluginInterface):
"""Dumps process memory ranges as DLLs"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
# TODO: Convert this to a ListRequirement so that people can filter on sets of ranges
requirements.IntRequirement(name = 'address',
description = "Process virtual memory address to include " \
"(all other address ranges are excluded). This must be " \
"a base address, not an address within the desired range.",
optional = True)]
def _generator(self, procs):
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
filter_func = lambda _: False
if self.config.get('address', None) is not None:
filter_func = lambda x: x.get_start() not in [self.config['address']]
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
# TODO: what kind of exceptions could this raise and what should we do?
proc_layer_name = proc.add_process_layer()
for vad in vadinfo.VadInfo.list_vads(proc, filter_func = filter_func):
# this parameter is inherited from the VadInfo plugin. if a user specifies
# an address, then it bypasses the DLL identification heuristics
if self.config.get("address", None) is None:
# rather than relying on the PEB for DLLs, which can be swapped,
# it requires special handling on wow64 processes, and its
# unreliable from an integrity standpoint, let's use the VADs instead
protection_string = vad.get_protection(vadinfo.VadInfo.protect_values(self.context,
self.config['primary'],
self.config['nt_symbols']),
vadinfo.winnt_protections)
# DLLs are write copy...
if protection_string != "PAGE_EXECUTE_WRITECOPY":
continue
# DLLs have mapped files...
if isinstance(vad.get_file_name(), interfaces.renderers.BaseAbsentValue):
continue
try:
filedata = interfaces_plugins.FileInterface(
"pid.{0}.{1}.{2:#x}.dmp".format(proc.UniqueProcessId,
ntpath.basename(vad.get_file_name()),
vad.get_start()))
dos_header = self.context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = vad.get_start(),
layer_name = proc_layer_name)
for offset, data in dos_header.reconstruct():
filedata.data.seek(offset)
filedata.data.write(data)
self.produce_file(filedata)
result_text = "Stored {}".format(filedata.preferred_filename)
except Exception:
result_text = "Unable to dump PE at {0:#x}".format(vad.get_start())
yield (0, (proc.UniqueProcessId,
process_name,
result_text))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Result", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,54 @@
from typing import List
import volatility.framework.interfaces.plugins as interfaces_plugins
from volatility.framework import exceptions, renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.plugins.windows import pslist
class DllList(interfaces_plugins.PluginInterface):
"""Lists the loaded modules in a particular windows memory image"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self, procs):
for proc in procs:
for entry in proc.load_order_modules():
BaseDllName = FullDllName = renderers.UnreadableValue()
try:
BaseDllName = entry.BaseDllName.get_string()
# We assume that if the BaseDllName points to an invalid buffer, so will FullDllName
FullDllName = entry.FullDllName.get_string()
except exceptions.InvalidAddressException:
pass
yield (0, (proc.UniqueProcessId,
proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count,
errors = 'replace'),
format_hints.Hex(entry.DllBase), format_hints.Hex(entry.SizeOfImage),
BaseDllName, FullDllName))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Base", format_hints.Hex),
("Size", format_hints.Hex),
("Name", str),
("Path", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,319 @@
import logging
from typing import Optional
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.pslist as pslist
from volatility.framework import constants, exceptions, renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
try:
import capstone
has_capstone = True
except ImportError:
has_capstone = False
class Handles(interfaces_plugins.PluginInterface):
"""Lists process open handles"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._sar_value = None
self._type_map = None
self._cookie = None
self._level_mask = 7
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _decode_pointer(self, value, magic):
"""Windows encodes pointers to objects and decodes them on the fly
before using them. This function mimics the decoding routine so we
can generate the proper pointer values as well."""
value = value & 0xFFFFFFFFFFFFFFF8
value = value >> magic
# if (value & (1 << 47)):
# value = value | 0xFFFF000000000000
return value
def _get_item(self, handle_table_entry, handle_value):
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from
a process' handle table, determine where the corresponding object's
_OBJECT_HEADER can be found."""
virtual = self.config["primary"]
try:
# before windows 7
if not self.context.memory[virtual].is_valid(handle_table_entry.Object):
return None
fast_ref = handle_table_entry.Object.cast(self.config["nt_symbols"] + constants.BANG + "_EX_FAST_REF")
object_header = fast_ref.dereference().cast(self.config["nt_symbols"] + constants.BANG + "_OBJECT_HEADER")
object_header.GrantedAccess = handle_table_entry.GrantedAccess
except AttributeError:
# starting with windows 8
if handle_table_entry.LowValue == 0:
return None
magic = self.find_sar_value()
# is this the right thing to raise here?
if magic == None:
raise AttributeError("Unable to find the SAR value for decoding handle table pointers")
offset = self._decode_pointer(handle_table_entry.LowValue, magic)
# print("LowValue: {0:#x} Magic: {1:#x} Offset: {2:#x}".format(handle_table_entry.InfoTable, magic, offset))
object_header = self.context.object(self.config["nt_symbols"] + constants.BANG + "_OBJECT_HEADER", virtual,
offset = offset)
object_header.GrantedAccess = handle_table_entry.GrantedAccessBits
object_header.HandleValue = handle_value
return object_header
def find_sar_value(self):
"""Locate ObpCaptureHandleInformationEx if it exists in the
sample. Once found, parse it for the SAR value that we need
to decode pointers in the _HANDLE_TABLE_ENTRY which allows us
to find the associated _OBJECT_HEADER."""
if self._sar_value is None:
if not has_capstone:
return None
virtual_layer_name = self.config['primary']
kvo = self.context.memory[virtual_layer_name].config['kernel_virtual_offset']
ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = virtual_layer_name, offset = kvo)
try:
func_addr = ntkrnlmp.get_symbol("ObpCaptureHandleInformationEx").address
except exceptions.SymbolError:
return None
data = self.context.memory.read(virtual_layer_name, kvo + func_addr, 0x200)
if data == None:
return None
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
for (address, size, mnemonic, op_str) in md.disasm_lite(data, kvo + func_addr):
# print("{} {} {} {}".format(address, size, mnemonic, op_str))
if mnemonic.startswith("sar"):
# if we don't want to parse op strings, we can disasm the
# single sar instruction again, but we use disasm_lite for speed
self._sar_value = int(op_str.split(",")[1].strip(), 16)
break
return self._sar_value
@classmethod
def list_objects(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str) -> dict:
"""List the executive object types (_OBJECT_TYPE) using the
ObTypeIndexTable or ObpObjectTypes symbol (differs per OS).
This method will be necessary for determining what type of
object we have given an object header.
Note: The object type index map was hard coded into profiles
in vol2, but we generate it dynamically now."""
type_map = {}
kvo = context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
try:
table_addr = ntkrnlmp.get_symbol("ObTypeIndexTable").address
except exceptions.SymbolError:
table_addr = ntkrnlmp.get_symbol("ObpObjectTypes").address
ptrs = ntkrnlmp.object(type_name = "array", offset = kvo + table_addr,
subtype = ntkrnlmp.get_type("pointer"),
count = 100)
for i, ptr in enumerate(ptrs):
# the first entry in the table is always null. break the
# loop when we encounter the first null entry after that
if i > 0 and ptr == 0:
break
objt = ptr.dereference().cast(symbol_table + constants.BANG + "_OBJECT_TYPE")
try:
type_name = objt.Name.String
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV,
"Cannot access _OBJECT_HEADER.Name at {0:#x}".format(objt.Name.vol.offset))
continue
type_map[i] = type_name
return type_map
@classmethod
def find_cookie(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str) -> Optional[interfaces.objects.ObjectInterface]:
"""Find the ObHeaderCookie value (if it exists)"""
try:
offset = context.symbol_space.get_symbol(
symbol_table + constants.BANG + "ObHeaderCookie").address
except exceptions.SymbolError:
return None
kvo = context.memory[layer_name].config['kernel_virtual_offset']
return context.object(symbol_table + constants.BANG + "unsigned int",
layer_name, offset = kvo + offset)
def _make_handle_array(self, offset, level, depth = 0):
"""Parse a process' handle table and yield valid handle table
entries, going as deep into the table "levels" as necessary."""
virtual = self.config["primary"]
kvo = self.context.memory[virtual].config['kernel_virtual_offset']
ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = virtual, offset = kvo)
if level > 0:
subtype = ntkrnlmp.get_type("pointer")
count = 0x1000 / subtype.size
else:
subtype = ntkrnlmp.get_type("_HANDLE_TABLE_ENTRY")
count = 0x1000 / subtype.size
if not self.context.memory[virtual].is_valid(offset):
return
table = ntkrnlmp.object(type_name = "array", offset = offset,
subtype = subtype, count = int(count))
layer_object = self.context.memory[virtual]
masked_offset = (offset & layer_object.maximum_address)
for entry in table:
if level > 0:
for x in self._make_handle_array(entry, level - 1, depth):
yield x
depth += 1
else:
handle_multiplier = 4
handle_level_base = depth * count * handle_multiplier
handle_value = ((entry.vol.offset - masked_offset) /
(subtype.size / handle_multiplier)) + handle_level_base
item = self._get_item(entry, handle_value)
if item == None:
continue
try:
if item.TypeIndex != 0x0:
yield item
except AttributeError:
if item.Type.Name:
yield item
except exceptions.PagedInvalidAddressException:
continue
def handles(self, handle_table):
try:
TableCode = handle_table.TableCode & ~self._level_mask
table_levels = handle_table.TableCode & self._level_mask
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV, "Handle table parsing was aborted due to an invalid address exception")
return
for handle_table_entry in self._make_handle_array(TableCode, table_levels):
yield handle_table_entry
def _generator(self, procs):
type_map = self.list_objects(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"])
cookie = self.find_cookie(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"])
for proc in procs:
try:
object_table = proc.ObjectTable
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV,
"Cannot access _EPROCESS.ObjectType at {0:#x}".format(proc.ObjectTable.vol.offset))
continue
process_name = utility.array_to_string(proc.ImageFileName)
for entry in self.handles(object_table):
try:
obj_type = entry.get_object_type(type_map, cookie)
if obj_type == None:
continue
if obj_type == "File":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_FILE_OBJECT")
obj_name = item.file_name_with_device()
elif obj_type == "Process":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_EPROCESS")
obj_name = "{} Pid {}".format(utility.array_to_string(proc.ImageFileName),
item.UniqueProcessId)
elif obj_type == "Thread":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_ETHREAD")
obj_name = "Tid {} Pid {}".format(item.Cid.UniqueThread, item.Cid.UniqueProcess)
elif obj_type == "Key":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_CM_KEY_BODY")
obj_name = item.get_full_key_name()
else:
try:
obj_name = entry.NameInfo.Name.String
except exceptions.InvalidAddressException:
obj_name = ""
except (exceptions.InvalidAddressException):
vollog.log(constants.LOGLEVEL_VVV,
"Cannot access _OBJECT_HEADER at {0:#x}".format(entry.vol.offset))
continue
yield (0, (proc.UniqueProcessId,
process_name,
format_hints.Hex(entry.HandleValue),
obj_type,
format_hints.Hex(entry.GrantedAccess),
obj_name))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("HandleValue", format_hints.Hex),
("Type", str),
("GrantedAccess", format_hints.Hex),
("Name", str)],
self._generator(pslist.PsList.list_processes(self.context,
self.config['primary'],
self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,139 @@
import time
from typing import List
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import constants, interfaces, layers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import TreeGrid
from volatility.framework.symbols.windows.kdbg import KdbgIntermedSymbols
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
class Info(plugins.PluginInterface):
"""Show OS & kernel details of the memory sample being analyzed"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def get_depends(self, layer_name: str, index: int = 0):
"""List the dependencies of a given layer.
Args:
layer_name: the name of the starting layer
index: the index/order of the layer
"""
layer = self.context.memory[layer_name]
yield index, layer
try:
for depends in layer.dependencies:
for j, dep in self.get_depends(depends, index + 1):
yield j, self.context.memory[dep.name]
except AttributeError:
# FileLayer won't have dependencies
pass
def _generator(self):
virtual_layer_name = self.config["primary"]
virtual_layer = self.context.memory[virtual_layer_name]
if not isinstance(virtual_layer, layers.intel.Intel):
raise TypeError("Virtual Layer is not an intel layer")
native_types = self.context.symbol_space[self.config["nt_symbols"]].natives
kdbg_table_name = KdbgIntermedSymbols.create(self.context,
self.config_path,
"windows",
"kdbg",
native_types = native_types)
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
kvo = virtual_layer.config["kernel_virtual_offset"]
ntkrnlmp = self.context.module(self.config["nt_symbols"],
layer_name = virtual_layer_name, offset = kvo)
kdbg_offset = ntkrnlmp.get_symbol("KdDebuggerDataBlock").address
kdbg = self.context.object(kdbg_table_name + constants.BANG +
"_KDDEBUGGER_DATA64", offset = kvo + kdbg_offset,
layer_name = virtual_layer_name)
yield (0, ("Memory Location", self.config["primary.memory_layer.location"]))
yield (0, ("Kernel Base", hex(self.config["primary.kernel_virtual_offset"])))
yield (0, ("DTB", hex(self.config["primary.page_map_offset"])))
yield (0, ("Symbols", self.config["nt_symbols.isf_url"]))
for i, layer in self.get_depends("primary"):
yield (0, (layer.name, "{} {}".format(i, layer.__class__.__name__)))
if kdbg.Header.OwnerTag == 0x4742444B:
yield (0, ("KdDebuggerDataBlock", hex(kdbg.vol.offset)))
yield (0, ("NTBuildLab", kdbg.get_build_lab()))
yield (0, ("CSDVersion", str(kdbg.get_csdversion())))
vers_offset = ntkrnlmp.get_symbol("KdVersionBlock").address
vers = ntkrnlmp.object(type_name = "_DBGKD_GET_VERSION64",
layer_name = virtual_layer_name,
offset = kvo + vers_offset)
yield (0, ("KdVersionBlock", hex(vers.vol.offset)))
yield (0, ("Major/Minor", "{0}.{1}".format(vers.MajorVersion, vers.MinorVersion)))
yield (0, ("MachineType", str(vers.MachineType)))
cpu_count_offset = ntkrnlmp.get_symbol("KeNumberProcessors").address
cpu_count = ntkrnlmp.object(type_name = "unsigned int",
layer_name = virtual_layer_name,
offset = kvo + cpu_count_offset)
yield (0, ("KeNumberProcessors", str(cpu_count)))
# this is a hard-coded address in the Windows OS
if virtual_layer.bits_per_register == 32:
kuser_addr = 0xFFDF0000
else:
kuser_addr = 0xFFFFF78000000000
kuser = ntkrnlmp.object(type_name = "_KUSER_SHARED_DATA",
layer_name = virtual_layer_name,
offset = kuser_addr)
yield (0, ("SystemTime", str(kuser.SystemTime.get_time())))
yield (0, ("NtSystemRoot", str(kuser.NtSystemRoot.cast("string",
encoding = "utf-16",
errors = "replace",
max_length = 260))))
yield (0, ("NtProductType", str(kuser.NtProductType.description)))
yield (0, ("NtMajorVersion", str(kuser.NtMajorVersion)))
yield (0, ("NtMinorVersion", str(kuser.NtMinorVersion)))
# yield (0, ("KdDebuggerEnabled", "True" if ord(kuser.KdDebuggerEnabled) else "False"))
# yield (0, ("SafeBootMode", "True" if ord(kuser.SafeBootMode) else "False"))
dos_header = self.context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = kvo,
layer_name = virtual_layer_name)
nt_header = dos_header.get_nt_header()
yield (0, ("PE MajorOperatingSystemVersion", str(nt_header.OptionalHeader.MajorOperatingSystemVersion)))
yield (0, ("PE MinorOperatingSystemVersion", str(nt_header.OptionalHeader.MinorOperatingSystemVersion)))
yield (0, ("PE Machine", str(nt_header.FileHeader.Machine)))
yield (0, ("PE TimeDateStamp", time.asctime(time.gmtime(nt_header.FileHeader.TimeDateStamp))))
def run(self):
return TreeGrid([("Variable", str),
("Value", str)],
self._generator())
@@ -0,0 +1,129 @@
import volatility.plugins.windows.pslist as pslist
import volatility.plugins.windows.vadinfo as vadinfo
from volatility.framework import interfaces, symbols
from volatility.framework import renderers
from volatility.framework.configuration import requirements
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 [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
@classmethod
def is_vad_empty(self, proc_layer, vad):
"""Check if a VAD region is either entirely unavailable
due to paging, entirely consisting of zeros, or a
combination of the two. This helps ignore false positives
whose VAD flags match task._injection_filter requirements
but there's no data and thus not worth reporting it.
Args:
proc_layer: the process layer
vad: the MMVAD structure to test
"""
CHUNK_SIZE = 0x1000
all_zero_page = "\x00" * CHUNK_SIZE
offset = 0
vad_length = vad.get_end() - vad.get_start()
while offset < vad_length:
next_addr = vad.get_start() + offset
if proc_layer.is_valid(next_addr) and proc_layer.read(next_addr, CHUNK_SIZE) != all_zero_page:
return False
offset += CHUNK_SIZE
return True
@classmethod
def list_injections(cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
proc: interfaces.objects.ObjectInterface):
"""Generate memory regions for a process that may contain
injected code.
Args:
proc: an _EPROCESS instance
"""
proc_layer_name = proc.add_process_layer()
proc_layer = context.memory[proc_layer_name]
for vad in proc.get_vad_root().traverse():
protection_string = vad.get_protection(vadinfo.VadInfo.protect_values(context,
proc_layer_name,
symbol_table),
vadinfo.winnt_protections)
write_exec = "EXECUTE" in protection_string and "WRITE" in protection_string
# the write/exec check applies to everything
if not write_exec:
continue
if (vad.get_private_memory() == 1 and vad.get_tag() == "VadS") or (
vad.get_private_memory() == 0 and protection_string != "PAGE_EXECUTE_WRITECOPY"):
if cls.is_vad_empty(proc_layer, vad):
continue
data = proc_layer.read(vad.get_start(), 64, pad = True)
yield vad, data
def _generator(self, procs):
# determine if we're on a 32 or 64 bit kernel
is_32bit_arch = not symbols.symbol_table_is_64bit(self.context, self.config["nt_symbols"])
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
for vad, data in self.list_injections(self.context, self.config["nt_symbols"], proc):
# if we're on a 64 bit kernel, we may still need 32 bit disasm due to wow64
if is_32bit_arch or proc.get_is_wow64():
architecture = "intel"
else:
architecture = "intel64"
disasm = interfaces.renderers.Disassembly(data, vad.get_start(), architecture)
yield (0, (proc.UniqueProcessId,
process_name,
format_hints.Hex(vad.get_start()),
format_hints.Hex(vad.get_end()),
vad.get_tag(),
vad.get_protection(vadinfo.VadInfo.protect_values(self.context,
proc.vol.layer_name,
self.config["nt_symbols"]),
vadinfo.winnt_protections),
vad.get_commit_charge(),
vad.get_private_memory(),
format_hints.HexBytes(data),
disasm))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Start VPN", format_hints.Hex),
("End VPN", format_hints.Hex),
("Tag", str),
("Protection", str),
("CommitCharge", int),
("PrivateMemory", int),
("Hexdump", format_hints.HexBytes),
("Disasm", interfaces.renderers.Disassembly)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,148 @@
import logging
from typing import List, Generator, Iterable
import volatility.framework.constants as constants
import volatility.framework.exceptions as exceptions
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.framework.renderers as renderers
import volatility.plugins.windows.modules as modules
import volatility.plugins.windows.pslist as pslist
from volatility.framework import interfaces
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
vollog = logging.getLogger(__name__)
class ModDump(interfaces_plugins.PluginInterface):
"""Dumps kernel modules"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Reuse the requirements from the plugins we use
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
@classmethod
def get_session_layers(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
pids: List[int] = None) -> Generator[str, None, None]:
"""Build a cache of possible virtual layers, in priority starting with
the primary/kernel layer. Then keep one layer per session by cycling
through the process list.
Returns:
<list> of layer names
"""
seen_ids = [] # type: List[interfaces.objects.ObjectInterface]
filter_func = pslist.PsList.create_filter(pids or [])
for proc in pslist.PsList.list_processes(context = context,
layer_name = layer_name,
symbol_table = symbol_table,
filter_func = filter_func):
proc_layer_name = proc.add_process_layer()
try:
# create the session space object in the process' own layer.
# not all processes have a valid session pointer.
session_space = context.object(symbol_table + constants.BANG + "_MM_SESSION_SPACE",
layer_name = layer_name,
offset = proc.Session)
if session_space.SessionId in seen_ids:
continue
except exceptions.InvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV,
"Process {} does not have a valid Session".format(proc.UniqueProcessId))
continue
# save the layer if we haven't seen the session yet
seen_ids.append(session_space.SessionId)
yield proc_layer_name
@classmethod
def find_session_layer(cls,
context: interfaces.context.ContextInterface,
session_layers: Iterable[str],
base_address: int):
"""Given a base address and a list of layer names, find a
layer that can access the specified address.
Args:
session_layers: <list> of layer names
base_address: <int> the base address
Returns:
layer name (or None)
"""
for layer_name in session_layers:
if context.memory[layer_name].is_valid(base_address):
return layer_name
return None
def _generator(self, mods):
session_layers = list(self.get_session_layers(self.context,
self.config['primary'],
self.config['nt_symbols']))
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
for mod in mods:
try:
BaseDllName = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = renderers.UnreadableValue()
session_layer_name = self.find_session_layer(self.context, session_layers, mod.DllBase)
if session_layer_name is None:
result_text = "Cannot find a viable session layer for {0:#x}".format(mod.DllBase)
else:
try:
dos_header = self.context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = mod.DllBase,
layer_name = session_layer_name)
filedata = interfaces_plugins.FileInterface(
"module.{0:#x}.dmp".format(mod.DllBase))
for offset, data in dos_header.reconstruct():
filedata.data.seek(offset)
filedata.data.write(data)
self.produce_file(filedata)
result_text = "Stored {}".format(filedata.preferred_filename)
except ValueError:
result_text = "PE parsing error"
except exceptions.SwappedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is inaccessible (swapped)".format(exp.invalid_address)
except exceptions.InvalidAddressException as exp:
result_text = "Required memory at {0:#x} is not valid".format(exp.invalid_address)
yield (0, (format_hints.Hex(mod.DllBase),
BaseDllName,
result_text))
def run(self):
return renderers.TreeGrid([("Base", format_hints.Hex),
("Name", str),
("Result", str)],
self._generator(
modules.Modules.list_modules(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'])))
@@ -0,0 +1,71 @@
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import constants
from volatility.framework import exceptions, interfaces
from volatility.framework import renderers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
class Modules(plugins.PluginInterface):
"""Lists the loaded kernel modules"""
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self):
for mod in self.list_modules(self.context, self.config['primary'], self.config['nt_symbols']):
try:
BaseDllName = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = ""
try:
FullDllName = mod.FullDllName.get_string()
except exceptions.InvalidAddressException:
FullDllName = ""
yield (0, (format_hints.Hex(mod.vol.offset),
format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage),
BaseDllName,
FullDllName,
))
@classmethod
def list_modules(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str):
"""Lists all the modules in the primary layer"""
kvo = context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
try:
# use this type if its available (starting with windows 10)
ldr_entry_type = ntkrnlmp.get_type("_KLDR_DATA_TABLE_ENTRY")
except exceptions.SymbolError:
ldr_entry_type = ntkrnlmp.get_type("_LDR_DATA_TABLE_ENTRY")
type_name = ldr_entry_type.type_name.split(constants.BANG)[1]
list_head = ntkrnlmp.get_symbol("PsLoadedModuleList").address
list_entry = ntkrnlmp.object(type_name = "_LIST_ENTRY", offset = kvo + list_head)
reloff = ldr_entry_type.relative_child_offset("InLoadOrderLinks")
module = ntkrnlmp.object(type_name = type_name, offset = list_entry.vol.offset - reloff)
for mod in module.InLoadOrderLinks:
yield mod
def run(self):
return renderers.TreeGrid([("Offset", format_hints.Hex),
("Base", format_hints.Hex),
("Size", format_hints.Hex),
("Name", str),
("Path", str)],
self._generator())
@@ -0,0 +1,250 @@
import enum
import logging
from typing import Optional, Tuple, List, Generator
import volatility.plugins.windows.handles as handles
from volatility.framework import constants, interfaces, renderers, validity, exceptions, symbols
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins, configuration
from volatility.framework.layers import scanners
from volatility.framework.renderers import format_hints
from volatility.framework.symbols import intermed
from volatility.framework.symbols.windows import extensions
vollog = logging.getLogger(__name__)
class PoolType(enum.IntEnum):
"""Class to maintain the different possible PoolTypes
The values must be integer powers of 2"""
PAGED = 1
NONPAGED = 2
FREE = 4
class PoolHeaderSymbolTable(intermed.IntermediateSymbolTable):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.set_type_class('_POOL_HEADER', extensions._POOL_HEADER)
class PoolConstraint(validity.ValidityRoutines):
"""Class to maintain tag/size/index/type information about Pool header tags"""
def __init__(self,
tag: bytes,
type_name: str,
object_type: Optional[str] = None,
page_type: Optional[PoolType] = None,
size: Optional[Tuple[Optional[int], Optional[int]]] = None,
index: Optional[Tuple[Optional[int], Optional[int]]] = None,
alignment: Optional[int] = 1) -> None:
self.tag = self._check_type(tag, bytes)
self.type_name = type_name
self.object_type = object_type
self.page_type = page_type
self.size = size
self.index = index
self.alignment = alignment
class PoolScanner(plugins.PluginInterface):
"""A generic pool scanner plugin"""
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self):
constraints = [
# atom tables
PoolConstraint(b'AtmT',
type_name = self.config["nt_symbols"] + constants.BANG + "_RTL_ATOM_TABLE",
size = (200, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# processes on windows before windows 8
PoolConstraint(b'Pro\xe3',
type_name = self.config["nt_symbols"] + constants.BANG + "_EPROCESS",
object_type = "Process",
size = (600, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# processes on windows starting with windows 8
PoolConstraint(b'Proc',
type_name = self.config["nt_symbols"] + constants.BANG + "_EPROCESS",
object_type = "Process",
size = (600, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# files on windows before windows 8
PoolConstraint(b'Fil\xe5',
type_name = self.config["nt_symbols"] + constants.BANG + "_FILE_OBJECT",
object_type = "File",
size = (150, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# files on windows starting with windows 8
PoolConstraint(b'File',
type_name = self.config["nt_symbols"] + constants.BANG + "_FILE_OBJECT",
object_type = "File",
size = (150, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
]
# get the object type map
type_map = handles.Handles.list_objects(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"])
cookie = handles.Handles.find_cookie(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"])
# FIXME: replace these lambdas with real functions
is_windows_10 = lambda: False
is_windows_8_or_later = lambda: False
# FIXME: scanning the primary layer seems very slow (10min on 512mb grrcon)
# start off with the primary virtual layer
scan_layer = self.config['primary']
# switch to a non-virtual layer if necessary
if not is_windows_10():
scan_layer = self.context.memory[scan_layer].config['memory_layer']
for constraint, header in self.pool_scan(self._context,
scan_layer,
self.config['nt_symbols'],
constraints,
alignment = 8):
mem_object = header.get_object(type_name = constraint.type_name,
type_map = type_map,
use_top_down = is_windows_8_or_later(),
object_type = constraint.object_type,
native_layer_name = 'primary',
cookie = cookie)
if mem_object is None:
vollog.log(constants.LOGLEVEL_VVV, "Cannot create an instance of {}".format(constraint.type_name))
continue
# generate some type-specific info for sanity checking
if constraint.object_type == "Process":
name = mem_object.ImageFileName.cast("string",
max_length = mem_object.ImageFileName.vol.count,
errors = "replace")
elif constraint.object_type == "File":
try:
name = mem_object.FileName.String
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV, "Skipping file at {0:#x}".format(mem_object.vol.offset))
continue
else:
name = renderers.NotApplicableValue()
yield (0, (constraint.type_name,
format_hints.Hex(header.vol.offset),
header.vol.layer_name,
name))
@classmethod
def pool_scan(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
pool_constraints: List[PoolConstraint],
alignment: int = 8,
progress_callback: Optional[validity.ProgressCallback] = None) \
-> Generator[Tuple[PoolConstraint, interfaces.objects.ObjectInterface], None, None]:
"""Returns the _POOL_HEADER object (based on the symbol_table template) after scanning through layer_name
returning all headers that match any of the constraints provided. Only one constraint can be provided per tag"""
# Setup the pattern
constraint_lookup = {} # type: Dict[bytes, List[PoolConstraint]]
for constraint in pool_constraints:
temp_list = constraint_lookup.get(constraint.tag, [])
temp_list.append(constraint)
constraint_lookup[constraint.tag] = temp_list
# Setup the pool header and offset differential
try:
module = context.module(symbol_table, layer_name, offset = 0)
header_type = module.get_type('_POOL_HEADER')
except exceptions.SymbolError:
# We have to manually load a symbol table
if symbols.symbol_table_is_64bit(context, symbol_table):
# FIXME: Do proper test for is_win_7
is_win_7 = False
if is_win_7:
pool_header_json_filename = "poolheader-x64-win7"
else:
pool_header_json_filename = "poolheader-x64"
else:
pool_header_json_filename = "poolheader-x86"
new_table_name = PoolHeaderSymbolTable.create(context = context,
config_path = configuration.path_join(
context.symbol_space[symbol_table].config_path,
"poolheader"
),
sub_path = "windows",
filename = pool_header_json_filename,
table_mapping = {'nt_symbols': symbol_table})
module = context.module(new_table_name, layer_name, offset = 0)
header_type = module.get_type('_POOL_HEADER')
header_offset = header_type.relative_child_offset('PoolTag')
# Run the scan locating the offsets of a particular tag
layer = context.memory[layer_name]
scanner = scanners.MultiStringScanner([c for c in constraint_lookup.keys()])
for offset, pattern in layer.scan(context, scanner, progress_callback = progress_callback):
for constraint in constraint_lookup[pattern]:
header = module.object(type_name = "_POOL_HEADER", offset = offset - header_offset)
# Size check
try:
if constraint.size is not None:
if constraint.size[0]:
if (alignment * header.BlockSize) < constraint.size[0]:
continue
if constraint.size[1]:
if (alignment * header.BlockSize) > constraint.size[1]:
continue
# Type check
if constraint.page_type is not None:
checks_pass = False
if (constraint.page_type & PoolType.FREE) and header.PoolType == 0:
checks_pass = True
elif (
constraint.page_type & PoolType.PAGED) and header.PoolType % 2 == 0 and header.PoolType > 0:
checks_pass = True
elif (constraint.page_type & PoolType.NONPAGED) and header.PoolType % 2 == 1:
checks_pass = True
if not checks_pass:
continue
if constraint.index is not None:
if constraint.index[0]:
if header.index < constraint.index[0]:
continue
if constraint.index[1]:
if header.index > constraint.index[1]:
continue
except exceptions.InvalidAddressException:
# The tested object's header doesn't point to valid addresses, ignore it
continue
# We found one that passed!
yield (constraint, header)
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid([("Tag", str),
("Offset", format_hints.Hex),
("Layer", str),
("Name", str)],
self._generator())
@@ -0,0 +1,81 @@
import logging
from typing import List
import volatility.framework.constants as constants
import volatility.framework.exceptions as exceptions
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.framework.renderers as renderers
import volatility.plugins.windows.pslist as pslist
from volatility.framework import interfaces
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
vollog = logging.getLogger(__name__)
class ProcDump(interfaces_plugins.PluginInterface):
"""Dumps process executable images"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self, procs):
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
# TODO: what kind of exceptions could this raise and what should we do?
proc_layer_name = proc.add_process_layer()
try:
peb = self._context.object(self.config["nt_symbols"] + constants.BANG + "_PEB",
layer_name = proc_layer_name,
offset = proc.Peb)
dos_header = self.context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = peb.ImageBaseAddress,
layer_name = proc_layer_name)
filedata = interfaces_plugins.FileInterface(
"pid.{0}.{1:#x}.dmp".format(proc.UniqueProcessId, peb.ImageBaseAddress))
for offset, data in dos_header.reconstruct():
filedata.data.seek(offset)
filedata.data.write(data)
self.produce_file(filedata)
result_text = "Stored {}".format(filedata.preferred_filename)
except ValueError:
result_text = "PE parsing error"
except exceptions.SwappedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is inaccessible (swapped)".format(exp.invalid_address)
except exceptions.PagedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is not valid (process exited?)".format(exp.invalid_address)
yield (0, (proc.UniqueProcessId,
process_name,
result_text))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Result", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,124 @@
import datetime
from typing import Callable, Iterable, List
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import renderers, interfaces, layers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.plugins import timeliner
class PsList(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the processes present in a particular windows memory image"""
PHYSICAL_DEFAULT = False
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
# TODO: Convert this to a ListRequirement so that people can filter on sets of pids
requirements.IntRequirement(name = 'pid',
description = "Process ID to include (all other processes are excluded)",
optional = True),
requirements.BooleanRequirement(name = 'physical',
description = 'Display physical offsets instead of virtual',
default = cls.PHYSICAL_DEFAULT,
optional = True)]
@classmethod
def create_filter(cls, pid_list: List[int] = None) -> Callable[[int], bool]:
filter_func = 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_func = lambda x: x not in filter_list
return filter_func
@classmethod
def list_processes(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the processes in the primary layer that are in the pid config option"""
# We only use the object factory to demonstrate how to use one
kvo = context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
ps_aph_offset = ntkrnlmp.get_symbol("PsActiveProcessHead").address
list_entry = ntkrnlmp.object(type_name = "_LIST_ENTRY", offset = kvo + ps_aph_offset)
# This is example code to demonstrate how to use symbol_space directly, rather than through a module:
#
# ```
# reloff = self.context.symbol_space.get_type(
# self.config['nt_symbols'] + constants.BANG + "_EPROCESS").relative_child_offset(
# "ActiveProcessLinks")
# ```
#
# Note: "nt_symbols!_EPROCESS" could have been used, but would rely on the "nt_symbols" symbol table not already
# having been present. Strictly, the value of the requirement should be joined with the BANG character
# defined in the constants file
reloff = ntkrnlmp.get_type("_EPROCESS").relative_child_offset("ActiveProcessLinks")
eproc = ntkrnlmp.object(type_name = "_EPROCESS", offset = list_entry.vol.offset - reloff)
for proc in eproc.ActiveProcessLinks:
if not filter_func(proc):
yield proc
def _generator(self):
for proc in self.list_processes(self.context,
self.config['primary'],
self.config['nt_symbols'],
filter_func = self.create_filter([self.config.get('pid', None)])):
if not self.config.get('physical', self.PHYSICAL_DEFAULT):
offset = proc.vol.offset
else:
layer_name = self.config['primary']
memory = self.context.memory[layer_name]
if not isinstance(memory, layers.intel.Intel):
raise TypeError("Primary layer is not an intel layer")
(_, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0]
yield (0, (proc.UniqueProcessId,
proc.InheritedFromUniqueProcessId,
proc.ImageFileName.cast("string",
max_length = proc.ImageFileName.vol.count,
errors = 'replace'),
format_hints.Hex(offset),
proc.ActiveThreads,
proc.get_handle_count(),
proc.get_session_id(),
proc.get_is_wow64(),
proc.get_create_time(),
proc.get_exit_time()))
def generate_timeline(self):
for row in self._generator():
_depth, row_data = row
description = "Process: {} ({})".format(row_data[2], row_data[3])
yield (description, timeliner.TimeLinerType.CREATED, row_data[8])
yield (description, timeliner.TimeLinerType.MODIFIED, row_data[9])
def run(self):
offsettype = "(V)" if not self.config.get('physical', self.PHYSICAL_DEFAULT) else "(P)"
return renderers.TreeGrid([("PID", int),
("PPID", int),
("ImageFileName", str),
("Offset{0}".format(offsettype), format_hints.Hex),
("Threads", int),
("Handles", int),
("SessionId", int),
("Wow64", bool),
("CreateTime", datetime.datetime),
("ExitTime", datetime.datetime)],
self._generator())
@@ -0,0 +1,67 @@
from volatility.framework import objects
from volatility.framework.renderers import format_hints
from volatility.plugins.windows import pslist
class PsTree(pslist.PsList):
"""Plugin for listing processes in a tree based on their parent process ID """
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._processes = {}
self._levels = {}
self._children = {}
def find_level(self, pid: objects.Pointer) -> None:
"""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.InheritedFromUniqueProcessId not in seen:
child_list = self._children.get(proc.InheritedFromUniqueProcessId, set([]))
child_list.add(proc.UniqueProcessId)
self._children[proc.InheritedFromUniqueProcessId] = child_list
proc = self._processes.get(proc.InheritedFromUniqueProcessId, None)
level += 1
self._levels[pid] = level
def _generator(self):
"""Generates the Tree of processes"""
for proc in self.list_processes(self.context, self.config['primary'], self.config['nt_symbols']):
if not self.config.get('physical', self.PHYSICAL_DEFAULT):
offset = proc.vol.offset
else:
layer_name = self.config['primary']
memory = self.context.memory[layer_name]
(_, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0]
self._processes[proc.UniqueProcessId] = 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.UniqueProcessId,
proc.InheritedFromUniqueProcessId,
proc.ImageFileName.cast("string",
max_length = proc.ImageFileName.vol.count,
errors = 'replace'),
format_hints.Hex(offset),
proc.ActiveThreads,
proc.get_handle_count(),
proc.get_session_id(),
proc.get_is_wow64(),
proc.get_create_time(),
proc.get_exit_time())
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)
@@ -0,0 +1,56 @@
from typing import Iterator, List, Tuple
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
class HiveList(plugins.PluginInterface):
"""Lists the registry hives present in a particular memory image"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
requirements.StringRequirement(name = 'filter',
description = "String to filter hive names returned",
optional = True,
default = None)]
def _generator(self) -> Iterator[Tuple[int, Tuple[int, str]]]:
for hive in self.list_hives(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"],
filter_string = self.config.get('filter', None)):
yield (0, (format_hints.Hex(hive.vol.offset),
hive.get_name() or ""))
@classmethod
def list_hives(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
filter_string: None = None) -> Iterator[interfaces.objects.ObjectInterface]:
"""Lists all the hives in the primary layer"""
# We only use the object factory to demonstrate how to use one
kvo = context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
list_head = ntkrnlmp.get_symbol("CmpHiveListHead").address
list_entry = ntkrnlmp.object(type_name = "_LIST_ENTRY", offset = kvo + list_head)
reloff = ntkrnlmp.get_type("_CMHIVE").relative_child_offset("HiveList")
cmhive = ntkrnlmp.object(type_name = "_CMHIVE", offset = list_entry.vol.offset - reloff)
for hive in cmhive.HiveList:
if filter_string is None or filter_string.lower() in str(hive.get_name() or "").lower():
yield hive
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid([("Offset", format_hints.Hex),
("FileFullPath", str)],
self._generator())
@@ -0,0 +1,136 @@
import datetime
import logging
from typing import Generator, Sequence
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import objects, renderers, exceptions
from volatility.framework.configuration import requirements
from volatility.framework.layers.registry import RegistryHive
from volatility.framework.renderers import TreeGrid, conversion, format_hints
from volatility.framework.symbols.windows.extensions.registry import RegValueTypes
vollog = logging.getLogger(__name__)
class PrintKey(plugins.PluginInterface):
"""Lists the registry keys under a hive or specific key value"""
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols",
description = "Windows OS"),
requirements.IntRequirement(name = 'offset',
description = "Hive Offset",
default = None,
optional = True),
requirements.StringRequirement(name = 'key',
description = "Key to start from",
default = None,
optional = True),
requirements.BooleanRequirement(name = 'recurse',
description = 'Recurses through keys',
default = False,
optional = True)]
def hive_walker(self,
hive: RegistryHive,
node_path: Sequence[objects.Struct] = None,
key_path: str = None) -> Generator:
"""Walks through a set of nodes from a given node (last one in node_path).
Avoids loops by not traversing into nodes already present in the node_path
"""
if not node_path:
node_path = [hive.get_node(hive.root_cell_offset)]
if not isinstance(node_path, list) or len(node_path) < 1:
vollog.warning("Hive walker was not passed a valid node_path (or None)")
return
node = node_path[-1]
key_path = key_path or node.get_key_path()
last_write_time = conversion.wintime_to_datetime(node.LastWriteTime.QuadPart)
for key_node in node.get_subkeys():
result = (key_path.count("\\"),
(last_write_time,
renderers.format_hints.Hex(hive.hive_offset),
"Key",
key_path,
key_node.get_name(),
"",
key_node.get_volatile()))
yield result
for value_node in node.get_values():
result = (key_path.count("\\"),
(last_write_time,
renderers.format_hints.Hex(hive.hive_offset),
RegValueTypes.get(value_node.Type).name,
key_path,
value_node.get_name(),
str(value_node.decode_data()),
node.get_volatile()))
yield result
if self.config.get('recurse', None):
for sub_node in node.get_subkeys():
if sub_node.vol.offset not in [x.vol.offset for x in node_path]:
yield from self.hive_walker(hive, node_path + [sub_node], key_path + "\\" + sub_node.get_name())
def registry_walker(self):
"""Walks through a registry, hive by hive"""
if self.config.get('offset', None) is None:
try:
import volatility.plugins.windows.registry.hivelist as hivelist
hive_offsets = [hive.vol.offset for hive in hivelist.HiveList.list_hives(self.context,
self.config['primary'],
self.config['nt_symbols'])]
except ImportError:
vollog.warning("Unable to import windows.hivelist plugin, please provide a hive offset")
raise ValueError("Unable to import windows.hivelist plugin, please provide a hive offset")
else:
hive_offsets = [self.config['offset']]
for hive_offset in hive_offsets:
# Construct the hive
reg_config_path = self.make_subconfig(hive_offset = hive_offset,
base_layer = self.config['primary'],
nt_symbols = self.config['nt_symbols'])
hive = RegistryHive(self.context, reg_config_path, name = 'hive' + hex(hive_offset))
try:
self.context.memory.add_layer(hive)
# Walk it
if 'key' in self.config:
node_path = hive.get_key(self.config['key'], return_list = True)
else:
node_path = [hive.get_node(hive.root_cell_offset)]
yield from self.hive_walker(hive, node_path)
except (exceptions.PagedInvalidAddressException, KeyError) as excp:
if type(excp) == KeyError:
vollog.debug(
"Key '{}' not found in Hive at offset {}.".format(self.config['key'], hex(hive_offset)))
else:
vollog.debug("Invalid address identified in Hive: {}".format(hex(excp.invalid_address)))
result = (0,
(renderers.UnreadableValue(),
format_hints.Hex(hive.hive_offset),
"Key",
self.config.get('key', "ROOT"),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue()))
yield result
def run(self):
return TreeGrid(columns = [('Last Write Time', datetime.datetime),
('Hive Offset', format_hints.Hex),
('Type', str),
('Key', str),
('Name', str),
('Data', str),
('Volatile', bool)],
generator = self.registry_walker())
@@ -0,0 +1,103 @@
{
"{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs",
"{054FAE61-4DD8-4787-80B6-090220C4B700}": "GameExplorer",
"{0762D272-C50A-4BB0-A382-697DCD729B80}": "%SystemDrive%\\Users",
"{0AC0837C-BBF8-452A-850D-79D08E667CA7}": "(My) Computer",
"{0F214138-B1D3-4a90-BBA9-27CBC0C5389A}": "Sync Setup",
"{15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5}": "%PUBLIC%\\Music\\Sample Playlists",
"{1777F761-68AD-4D8A-87BD-30B759FA33DD}": "%USERPROFILE%\\Favorites",
"{18989B1D-99B5-455B-841C-AB7C74E4DDFC}": "%USERPROFILE%\\Videos",
"{190337d1-b8ca-4121-a639-6d472d16972a}": "Search Results",
"{1A6FDBA2-F42D-4358-A798-B74D745926C5}": "%PUBLIC%\\RecordedTV.library-ms",
"{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}": "%windir%\\system32",
"{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}": "%APPDATA%\\Microsoft\\Windows\\Libraries",
"{2112AB0A-C86A-4FFE-A368-0DE96E47012E}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Music.library-ms",
"{2400183A-6185-49FB-A2D8-4A392A602BA3}": "%PUBLIC%\\Videos",
"{289a9a43-be44-4057-a41b-587a76d7e7f9}": "Sync Results",
"{2A00375E-224C-49DE-B8D1-440DF7EF3DDC}": "%windir%\\resources\\0409 (code page)",
"{2B0F765D-C0E9-4171-908E-08A611B84FF6}": "%APPDATA%\\Microsoft\\Windows\\Cookies",
"{2C36C0AA-5812-4b87-BFD0-4CD0DFB19B39}": "%LOCALAPPDATA%\\Microsoft\\Windows Photo Gallery\\Original Images",
"{3214FAB5-9757-4298-BB61-92A9DEAA44FF}": "%PUBLIC%\\Music",
"{33E28130-4E1E-4676-835A-98395C3BC3BB}": "%USERPROFILE%\\Pictures",
"{352481E8-33BE-4251-BA85-6007CAEDCF9D}": "%LOCALAPPDATA%\\Microsoft\\Windows\\Temporary Internet Files",
"{374DE290-123F-4565-9164-39C4925E467B}": "%USERPROFILE%\\Downloads",
"{3D644C9B-1FB8-4f30-9B45-F670235F79C0}": "%PUBLIC%\\Downloads",
"{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}": "%APPDATA% (%USERPROFILE%\\AppData\\Roaming)",
"{43668BF8-C14E-49B2-97C9-747784D784B7}": "Sync Center",
"{48DAF80B-E6CF-4F4E-B800-0E69D84EE384}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Libraries",
"{491E922F-5643-4AF4-A7EB-4E7A138D8174}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Videos.library-ms",
"{4BD8D571-6D19-48D3-BE97-422220080E43}": "%USERPROFILE%\\Music",
"{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}": "%USERPROFILE%\\Saved Games",
"{4D9F7874-4E0C-4904-967B-40B0D20C3E4B}": "The Internet",
"{4bfefb45-347d-4006-a5be-ac0cb0567192}": "Conflicts",
"{52528A6B-B9E3-4ADD-B60D-588C2DBA842D}": "Homegroup",
"{52a4f021-7b75-48a9-9f6b-4b87a210bc8f}": "%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch",
"{56784854-C6CB-462b-8169-88E350ACB882}": "%USERPROFILE%\\Contacts",
"{5CD7AEE2-2219-4A67-B85D-6C9CE15660CB}": "%LOCALAPPDATA%\\Programs",
"{5CE4A5E9-E4EB-479D-B89F-130C02886155}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\DeviceMetadataStore",
"{5E6C858F-0E22-4760-9AFE-EA3317B67173}": "%USERPROFILE% (%SystemDrive%\\Users\\%USERNAME%)",
"{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}": "%APPDATA%\\Microsoft\\Windows\\Start Menu",
"{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}": "%ALLUSERSPROFILE% (%ProgramData%, %SystemDrive%\\ProgramData)",
"{6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D}": "%ProgramFiles%\\Common Files",
"{69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C}": "%USERPROFILE%\\Pictures\\Slide Shows",
"{6D809377-6AF0-444b-8957-A3773F02200E}": "%ProgramFiles%",
"{6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD}": "Network Connections",
"{724EF170-A42D-4FEF-9F26-B60E846FBA4F}": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools",
"{76FC4E2D-D6AD-4519-A663-37BD56068185}": "Printers",
"{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Documents.library-ms",
"{7B396E54-9EC5-4300-BE0A-2482EBAE1A26}": "%ProgramFiles%\\Windows Sidebar\\Gadgets",
"{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}": "%ProgramFiles%",
"{7d1d3a04-debb-4115-95cf-2f29da2920da}": "%USERPROFILE%\\Searches",
"{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp",
"{82A74AEB-AEB4-465C-A014-D097EE346D63}": "Control Panel",
"{859EAD94-2E85-48AD-A71A-0969CB56A6CD}": "%PUBLIC%\\Videos\\Sample Videos",
"{8983036C-27C0-404B-8F08-102D10DCFD74}": "%APPDATA%\\Microsoft\\Windows\\SendTo",
"{8AD10C31-2ADB-4296-A8F7-E4701232C972}": "%windir%\\Resources",
"{905e63b6-c1bf-494e-b29c-65b732d3d21a}": "%ProgramFiles%",
"{9274BD8D-CFD1-41C3-B35E-B13F55A758F4}": "%APPDATA%\\Microsoft\\Windows\\Printer Shortcuts",
"{98ec0e18-2098-4d44-8644-66979315a281}": "Microsoft Office Outlook",
"{9E3995AB-1F9C-4F13-B827-48B24B6C7174}": "%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch\\User Pinned",
"{9E52AB10-F80D-49DF-ACB8-4330F5687855}": "%LOCALAPPDATA%\\Microsoft\\Windows\\Burn\\Burn",
"{A302545D-DEFF-464b-ABE8-61C8648D939B}": "Libraries",
"{A4115719-D62E-491D-AA7C-E74B8BE3B067}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu",
"{A520A1A4-1780-4FF6-BD18-167343C5AF16}": "%USERPROFILE%\\AppData\\LocalLow",
"{A63293E8-664E-48DB-A079-DF759E0509F7}": "%APPDATA%\\Microsoft\\Windows\\Templates",
"{A75D362E-50FC-4fb7-AC2C-A8BEAA314493}": "%LOCALAPPDATA%\\Microsoft\\Windows Sidebar\\Gadgets",
"{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs",
"{A990AE9F-A03B-4E80-94BC-9912D7504104}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Pictures.library-ms",
"{AE50C081-EBD2-438A-8655-8A092E34987A}": "%APPDATA%\\Microsoft\\Windows\\Recent",
"{B250C668-F57D-4EE1-A63C-290EE7D1AA1F}": "%PUBLIC%\\Music\\Sample Music",
"{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}": "Desktop",
"{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}": "%PUBLIC%\\Pictures",
"{B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC}": "Recycle Bin",
"{B94237E7-57AC-4347-9151-B08C6C32D1F7}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Templates",
"{B97D20BB-F46A-4C97-BA10-5E3608430854}": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp",
"{BCB5256F-79F6-4CEE-B725-DC34E402FD46}": "%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\ImplicitAppShortcuts",
"{BCBD3057-CA5C-4622-B42D-BC56DB0AE516}": "%LOCALAPPDATA%\\Programs\\Common",
"{C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D}": "%ALLUSERSPROFILE%\\OEM Links",
"{C4900540-2379-4C75-844B-64E6FAF8716B}": "%PUBLIC%\\Pictures\\Sample Pictures",
"{C4AA340D-F20F-4863-AFEF-F87EF2E6BA25}": "%PUBLIC%\\Desktop",
"{C5ABBF53-E17F-4121-8900-86626FC2C973}": "%APPDATA%\\Microsoft\\Windows\\Network Shortcuts",
"{C870044B-F49E-4126-A9C3-B52A1FF411E8}": "%LOCALAPPDATA%\\Microsoft\\Windows\\Ringtones",
"{CAC52C1A-B53D-4edc-92D7-6B2E8AC19434}": "Games",
"{D0384E7D-BAC3-4797-8F14-CBA229B392B5}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools",
"{D20BEEC4-5CA8-4905-AE3B-BF251EA09B53}": "Network",
"{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}": "%windir%\\system32",
"{D9DC8A3B-B784-432E-A781-5A1130A75963}": "%LOCALAPPDATA%\\Microsoft\\Windows\\History",
"{DE92C1C7-837F-4F69-A3BB-86E631204A23}": "%USERPROFILE%\\Music\\Playlists",
"{DE974D24-D9C6-4D3E-BF91-F4455120B917}": "%ProgramFiles%\\Common Files",
"{DEBF2536-E1A8-4c59-B6A2-414586476AEA}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\GameExplorer",
"{DFDF76A2-C82A-4D63-906A-5644AC457385}": "%PUBLIC% (%SystemDrive%\\Users\\Public)",
"{E555AB60-153B-4D17-9F04-A5FE99FC15EC}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Ringtones",
"{ED4824AF-DCE4-45A8-81E2-FC7965083634}": "%PUBLIC%\\Documents",
"{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}": "%LOCALAPPDATA% (%USERPROFILE%\\AppData\\Local)",
"{F38BF404-1D43-42F2-9305-67DE0B28FC23}": "%windir%",
"{F7F1ED05-9F6D-47A2-AAAE-29D317C6F066}": "%ProgramFiles%\\Common Files",
"{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}": "%windir%\\Fonts",
"{a305ce99-f527-492b-8b1a-7e76fa98d6e4}": "Installed Updates",
"{bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968}": "%USERPROFILE%\\Links",
"{de61d971-5ebc-4f02-a3a9-6c82895e5c04}": "Add or Remove Programs (Control Panel)",
"{df7266ac-9274-4867-8d55-3bd661de872d}": "Programs and Features",
"{ee32e446-31ca-4aba-814f-a5ebd2fd6d5e}": "Offline Files",
"{f3ce0f7c-4901-4acc-8648-d5d44b04ef8f}": "The user's full name"
}
@@ -0,0 +1,277 @@
import codecs
import datetime
import json
import logging
import os
from typing import List
from volatility.framework import exceptions, renderers, constants, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.layers.physical import BufferDataLayer
from volatility.framework.layers.registry import RegistryHive
from volatility.framework.renderers import format_hints, conversion
from volatility.framework.symbols import intermed
vollog = logging.getLogger(__name__)
class UserAssist(interfaces.plugins.PluginInterface):
"""Print userassist registry keys and information"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._userassist_size = 0
self._userassist_type_name = "_VOL_USERASSIST_TYPES_7"
self._reg_table_name = None
self._win7 = None
# taken from http://msdn.microsoft.com/en-us/library/dd378457%28v=vs.85%29.aspx
self._folder_guids = json.load(open(os.path.join(os.path.dirname(__file__), "userassist.json"), "rb"))
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols",
description = "Windows OS"),
requirements.IntRequirement(name = 'offset',
description = "Hive Offset",
default = None,
optional = True)]
def parse_userassist_data(self, reg_val):
"""Reads the raw data of a _CM_KEY_VALUE and returns a dict of userassist fields"""
item = {
"id": renderers.UnparsableValue(),
"count": renderers.UnparsableValue(),
"focus": renderers.UnparsableValue(),
"time": renderers.UnparsableValue(),
"lastupdated": renderers.UnparsableValue(),
"rawdata": renderers.UnparsableValue(),
}
userassist_data = reg_val.decode_data()
if userassist_data is None:
return item
item["rawdata"] = userassist_data
if self._win7 is None:
# if OS is still unknown at this point, return the default item which just has the rawdata
return item
if len(userassist_data) < self._userassist_size:
return item
userassist_layer_name = self.context.memory.free_layer_name("userassist_buffer")
buffer = BufferDataLayer(self.context, self._config_path, userassist_layer_name, userassist_data)
self.context.add_layer(buffer)
userassist_obj = self.context.object(
symbol = self._reg_table_name + constants.BANG + self._userassist_type_name,
layer_name = userassist_layer_name,
offset = 0)
if self._win7:
item["id"] = renderers.NotApplicableValue()
item["count"] = int(userassist_obj.Count)
seconds = (userassist_obj.FocusTime + 500) / 1000.0
time = datetime.timedelta(seconds = seconds) if seconds > 0 else userassist_obj.FocusTime
item["focus"] = int(userassist_obj.FocusCount)
item["time"] = str(time)
else:
item["id"] = int(userassist_obj.ID)
item["count"] = int(userassist_obj.CountStartingAtFive
if userassist_obj.CountStartingAtFive < 5
else userassist_obj.CountStartingAtFive - 5)
item["focus"] = renderers.NotApplicableValue()
item["time"] = renderers.NotApplicableValue()
item["lastupdated"] = conversion.wintime_to_datetime(userassist_obj.LastUpdated.QuadPart)
return item
def _determine_userassist_type(self) -> None:
"""Determine the userassist type and size depending on the OS version"""
if self._win7 is True:
self._userassist_type_name = "_VOL_USERASSIST_TYPES_7"
elif self._win7 is False:
self._userassist_type_name = "_VOL_USERASSIST_TYPES_XP"
self._userassist_size = self.context.symbol_space.get_type(
self._reg_table_name + constants.BANG + self._userassist_type_name).size
def _win7_or_later(self) -> bool:
# TODO: change this if there is a better way of determining the OS version
# _KUSER_SHARED_DATA.CookiePad is in Windows 6.1 (Win7) and later
return self.context.symbol_space.get_type(
self.config['nt_symbols'] + constants.BANG + "_KUSER_SHARED_DATA").has_member('CookiePad')
def list_userassist(self, hive: RegistryHive):
"""Generate userassist data for a registry hive."""
hive_name = hive.hive.cast(self.config["nt_symbols"] + constants.BANG + "_CMHIVE").get_name()
if self._win7 is None:
try:
self._win7 = self._win7_or_later()
except exceptions.SymbolError:
# self._win7 will be None and only registry value rawdata will be output
pass
self._determine_userassist_type()
userassist_node_path = hive.get_key("software\\microsoft\\windows\\currentversion\\explorer\\userassist",
return_list = True)
if not userassist_node_path:
vollog.warning("list_userassist did not find a valid node_path (or None)")
return
userassist_node = userassist_node_path[-1]
# iterate through the GUIDs under the userassist key
for guidkey in userassist_node.get_subkeys():
# each guid key should have a Count key in it
for countkey in guidkey.get_subkeys():
countkey_path = countkey.get_key_path()
countkey_last_write_time = conversion.wintime_to_datetime(countkey.LastWriteTime.QuadPart)
# output the parent Count key
result = (0,
(renderers.format_hints.Hex(hive.hive_offset),
hive_name,
countkey_path,
countkey_last_write_time,
"Key",
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue())) # type: Tuple[int, Tuple[format_hints.Hex, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any]]
yield result
# output any subkeys under Count
for subkey in countkey.get_subkeys():
subkey_name = subkey.get_name()
result = (1, (renderers.format_hints.Hex(hive.hive_offset),
hive_name,
countkey_path,
countkey_last_write_time,
"Subkey",
subkey_name,
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),))
yield result
# output any values under Count
for value in countkey.get_values():
value_name = value.get_name()
try:
value_name = codecs.encode(value_name, "rot_13")
except UnicodeDecodeError:
pass
if self._win7:
guid = value_name.split("\\")[0]
if guid in self._folder_guids:
value_name = value_name.replace(guid, self._folder_guids[guid])
userassist_data_dict = self.parse_userassist_data(value)
result = (1, (renderers.format_hints.Hex(hive.hive_offset),
hive_name,
countkey_path,
countkey_last_write_time,
"Value",
value_name,
userassist_data_dict["id"],
userassist_data_dict["count"],
userassist_data_dict["focus"],
userassist_data_dict["time"],
userassist_data_dict["lastupdated"],
format_hints.HexBytes(userassist_data_dict["rawdata"]),))
yield result
def _generator(self):
# get all the user hive offsets or use the one specified
if self.config.get('offset', None) is None:
try:
import volatility.plugins.windows.registry.hivelist as hivelist
hive_offsets = [hive.vol.offset for hive in
hivelist.HiveList.list_hives(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_string = "ntuser.dat")]
except ImportError:
vollog.warning("Unable to import windows.hivelist plugin, please provide a hive offset")
raise ValueError("Unable to import windows.hivelist plugin, please provide a hive offset")
else:
hive_offsets = [self.config['offset']]
self._reg_table_name = intermed.IntermediateSymbolTable.create(self.context,
self._config_path,
'windows',
'registry')
for hive_offset in hive_offsets:
# Construct the hive
reg_config_path = self.make_subconfig(hive_offset = hive_offset,
base_layer = self.config['primary'],
nt_symbols = self.config['nt_symbols'])
hive_name = None
try:
hive = RegistryHive(self.context, reg_config_path, name = 'hive' + hex(hive_offset))
hive_name = hive.hive.cast(self.config["nt_symbols"] + constants.BANG + "_CMHIVE").get_name()
self.context.memory.add_layer(hive)
yield from self.list_userassist(hive)
continue
except exceptions.PagedInvalidAddressException as excp:
vollog.debug("Invalid address identified in Hive: {}".format(hex(excp.invalid_address)))
except KeyError:
vollog.debug("Key '{}' not found in Hive at offset {}.".format(
"software\\microsoft\\windows\\currentversion\\explorer\\userassist", hex(hive_offset)))
# yield UnreadableValues when an exception occurs for a given hive_offset
result = (0,
(renderers.format_hints.Hex(hive_offset),
hive_name if hive_name else renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue()))
yield result
def run(self):
return renderers.TreeGrid([("Hive Offset", renderers.format_hints.Hex),
("Hive Name", str),
("Path", str),
("Last Write Time", datetime.datetime),
("Type", str),
("Name", str),
("ID", int),
("Count", int),
("Focus Count", int),
("Time Focused", str),
("Last Updated", datetime.datetime),
("Raw Data", format_hints.HexBytes)],
self._generator())
@@ -0,0 +1,117 @@
import os
from typing import Any, Iterator, List, Tuple
from volatility.framework import constants, interfaces
from volatility.framework import contexts
from volatility.framework import exceptions, symbols
from volatility.framework import renderers
from volatility.framework.configuration import requirements
from volatility.framework.constants import windows as windows_constants
from volatility.framework.interfaces import plugins
from volatility.framework.renderers import format_hints
from volatility.plugins.windows import modules
class SSDT(plugins.PluginInterface):
"""Lists the system call table"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self, mods: Iterator[Any]) -> Iterator[Tuple[int, Tuple[int, int, str, str]]]:
layer_name = self.config['primary']
context_modules = []
for mod in mods:
try:
module_name_with_ext = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
# there's no use for a module with no name?
continue
module_name = os.path.splitext(module_name_with_ext)[0]
if module_name in windows_constants.KERNEL_MODULE_NAMES:
symbol_table_name = self.config["nt_symbols"]
else:
symbol_table_name = None
context_module = contexts.SizedModule(self._context,
module_name,
layer_name,
mod.DllBase,
mod.SizeOfImage,
symbol_table_name)
context_modules.append(context_module)
collection = contexts.ModuleCollection(context_modules)
kvo = self.context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = layer_name, offset = kvo)
# this is just one way to enumerate the native (NT) service table.
# to do the same thing for the Win32K service table, we would need Win32K.sys symbol support
## we could also find nt!KeServiceDescriptorTable (NT) and KeServiceDescriptorTableShadow (NT, Win32K)
service_table_address = ntkrnlmp.get_symbol("KiServiceTable").address
service_limit_address = ntkrnlmp.get_symbol("KiServiceLimit").address
service_limit = ntkrnlmp.object(type_name = "int", offset = kvo + service_limit_address)
# on 32-bit systems the table indexes are 32-bits and contain pointers (unsigned)
# on 64-bit systems the indexes are also 32-bits but they're offsets from the
# base address of the table and can be negative, so we need a signed data type
is_kernel_64 = symbols.symbol_table_is_64bit(self.context, self.config["nt_symbols"])
if is_kernel_64:
array_subtype = "long"
def kvo_calulator(func):
return kvo + service_table_address + (func >> 4)
find_address = kvo_calulator
else:
array_subtype = "unsigned long"
def passthrough(func):
return func
find_address = passthrough
functions = ntkrnlmp.object(type_name = "array", offset = kvo + service_table_address,
subtype = ntkrnlmp.get_type(array_subtype),
count = service_limit)
for idx, function in enumerate(functions):
function = find_address(function)
module_symbols = collection.get_module_symbols_by_absolute_location(function)
for module_name, symbol_generator in module_symbols:
symbols_found = False
for symbol in symbol_generator:
symbols_found = True
yield (0, (idx,
format_hints.Hex(function),
module_name,
symbol.split(constants.BANG)[1]))
if not symbols_found:
yield (0, (idx,
format_hints.Hex(function),
module_name,
renderers.NotAvailableValue()))
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid([("Index", int),
("Address", format_hints.Hex),
("Module", str),
("Symbol", str)],
self._generator(modules.Modules.list_modules(self.context,
self.config['primary'],
self.config['nt_symbols'])))
@@ -0,0 +1,92 @@
import logging
import re
from typing import Dict, Generator, List, Set, Tuple
from volatility.framework import interfaces, renderers, layers
from volatility.framework.configuration import requirements
from volatility.framework.layers import intel
from volatility.framework.renderers import format_hints
from volatility.plugins.windows import pslist
vollog = logging.getLogger(__name__)
class Strings(interfaces.plugins.PluginInterface):
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
requirements.URIRequirement(name = "strings_file", description = "Strings file")]
# TODO: Make URLRequirement that can accept a file address which the framework can open
def run(self):
return renderers.TreeGrid([("String", str),
("Physical Address", format_hints.Hex),
("Result", str)],
self._generator())
def _generator(self) -> Generator[Tuple, None, None]:
"""Generates results from a strings file"""
revmap = self.generate_mapping(self.config['primary'])
accessor = layers.ResourceAccessor()
for line in accessor.open(self.config['strings_file'], "rb").readlines():
try:
offset, string = self._parse_line(line)
try:
revmap_list = [name + ":" + hex(offset) for (name, offset) in revmap[offset >> 12]]
except (IndexError, KeyError):
revmap_list = ["FREE MEMORY"]
yield (0, (str(string, 'latin-1'), format_hints.Hex(offset), ", ".join(revmap_list)))
except ValueError:
vollog.error("Strings file is in the wrong format")
return
@staticmethod
def _parse_line(line: bytes) -> Tuple[int, bytes]:
"""Parses a single line from a strings file"""
pattern = re.compile(rb"(?:\W*)([0-9]+)(?:\W*)(\w[\w\W]+)")
match = pattern.search(line)
if not match:
raise ValueError("Strings file contains invalid strings line")
offset, string = match.group(1, 2)
return int(offset), string
def generate_mapping(self, layer_name: str) -> Dict[int, Set[Tuple[str, int]]]:
"""Creates a reverse mapping between virtual addresses and physical addresses"""
layer = self._context.memory[layer_name]
reverse_map = dict() # type: Dict[int, Set[Tuple[str, int]]]
if isinstance(layer, intel.Intel):
# We don't care about errors, we just wanted chunks that map correctly
for mapval in layer.mapping(0x0, layer.maximum_address, ignore_errors = True):
vpage, kpage, page_size, maplayer = mapval
for val in range(kpage, kpage + page_size, 0x1000):
cur_set = reverse_map.get(kpage >> 12, set())
cur_set.add(("kernel", vpage))
reverse_map[kpage >> 12] = cur_set
self._progress_callback((vpage * 100) / layer.maximum_address, "Creating reverse kernel map")
# TODO: Include kernel modules
for process in pslist.PsList.list_processes(self.context,
self.config['primary'],
self.config['nt_symbols']):
proc_layer_name = process.add_process_layer()
proc_layer = self.context.memory[proc_layer_name]
if isinstance(proc_layer, interfaces.layers.TranslationLayerInterface):
for mapval in proc_layer.mapping(0x0, proc_layer.maximum_address, ignore_errors = True):
kpage, vpage, page_size, maplayer = mapval
for val in range(kpage, kpage + page_size, 0x1000):
cur_set = reverse_map.get(kpage >> 12, set())
cur_set.add(("Process {}".format(process.UniqueProcessId), vpage))
reverse_map[kpage >> 12] = cur_set
# FIXME: make the progress for all processes, rather than per-process
self._progress_callback((vpage * 100) / layer.maximum_address,
"Creating mapping for task {}".format(process.UniqueProcessId))
return reverse_map
@@ -0,0 +1,81 @@
import logging
from typing import List
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.pslist as pslist
import volatility.plugins.windows.vadinfo as vadinfo
from volatility.framework import renderers, interfaces, exceptions
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
vollog = logging.getLogger(__name__)
class VadDump(interfaces_plugins.PluginInterface):
"""Dumps process memory ranges"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
# TODO: Convert this to a ListRequirement so that people can filter on sets of ranges
requirements.IntRequirement(name = 'address',
description = "Process virtual memory address to include " \
"(all other address ranges are excluded). This must be " \
"a base address, not an address within the desired range.",
optional = True)]
def _generator(self, procs):
filter_func = lambda _: False
if self.config.get('address', None) is not None:
filter_func = lambda x: x.get_start() not in [self.config['address']]
chunk_size = 1024 * 1024 * 10
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
# TODO: what kind of exceptions could this raise and what should we do?
proc_layer_name = proc.add_process_layer()
proc_layer = self.context.memory[proc_layer_name]
for vad in vadinfo.VadInfo.list_vads(proc, filter_func = filter_func):
try:
filedata = interfaces_plugins.FileInterface(
"pid.{0}.vad.{1:#x}-{2:#x}.dmp".format(proc.UniqueProcessId,
vad.get_start(),
vad.get_end()))
offset = vad.get_start()
out_of_range = vad.get_start() + vad.get_end()
while offset < out_of_range:
to_read = min(chunk_size, out_of_range - offset)
data = proc_layer.read(offset, to_read, pad = True)
if not data:
break
filedata.data.write(data)
offset += to_read
self.produce_file(filedata)
result_text = "Stored {}".format(filedata.preferred_filename)
except exceptions.InvalidAddressException:
result_text = "Unable to dump {0:#x} - {1:#x}".format(vad.get_start(), vad.get_end())
yield (0, (proc.UniqueProcessId,
process_name,
result_text))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Result", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,125 @@
import logging
from typing import Callable, Generator, Iterable
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.pslist as pslist
from volatility.framework import renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
# these are from WinNT.h
winnt_protections = {
"PAGE_NOACCESS": 0x01,
"PAGE_READONLY": 0x02,
"PAGE_READWRITE": 0x04,
"PAGE_WRITECOPY": 0x08,
"PAGE_EXECUTE": 0x10,
"PAGE_EXECUTE_READ": 0x20,
"PAGE_EXECUTE_READWRITE": 0x40,
"PAGE_EXECUTE_WRITECOPY": 0x80,
"PAGE_GUARD": 0x100,
"PAGE_NOCACHE": 0x200,
"PAGE_WRITECOMBINE": 0x400,
"PAGE_TARGETS_INVALID": 0x40000000,
}
class VadInfo(interfaces_plugins.PluginInterface):
"""Lists process memory ranges"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._protect_values = None
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
# TODO: Convert this to a ListRequirement so that people can filter on sets of ranges
requirements.IntRequirement(name = 'address',
description = "Process virtual memory address to include " \
"(all other address ranges are excluded). This must be " \
"a base address, not an address within the desired range.",
optional = True)]
@classmethod
def protect_values(cls,
context: interfaces.context.ContextInterface,
virtual_layer: str,
nt_symbols: str) -> Iterable[int]:
"""Look up the array of memory protection constants from the memory sample.
These don't change often, but if they do in the future, then finding them
# dynamically versus hard-coding here will ensure we parse them properly."""
kvo = context.memory[virtual_layer].config["kernel_virtual_offset"]
ntkrnlmp = context.module(nt_symbols, layer_name = virtual_layer, offset = kvo)
addr = ntkrnlmp.get_symbol("MmProtectToValue").address
values = ntkrnlmp.object(type_name = "array", offset = kvo + addr,
subtype = ntkrnlmp.get_type("int"),
count = 32)
return values # type: ignore
@classmethod
def list_vads(cls, proc: interfaces.objects.ObjectInterface,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Generator[interfaces.objects.ObjectInterface, None, None]:
for vad in proc.get_vad_root().traverse():
if not filter_func(vad):
yield vad
def _generator(self, procs):
def passthrough(_):
return False
filter_func = passthrough
if self.config.get('address', None) is not None:
def filter_function(x):
return x.get_start() not in [self.config['address']]
filter_func = filter_function
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
for vad in self.list_vads(proc, filter_func = filter_func):
yield (0, (proc.UniqueProcessId,
process_name,
format_hints.Hex(vad.vol.offset),
format_hints.Hex(vad.get_start()),
format_hints.Hex(vad.get_end()),
vad.get_tag(),
vad.get_protection(self.protect_values(self.context,
self.config['primary'],
self.config['nt_symbols']), winnt_protections),
vad.get_commit_charge(),
vad.get_private_memory(),
format_hints.Hex(vad.get_parent()),
vad.get_file_name()))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Offset", format_hints.Hex),
("Start VPN", format_hints.Hex),
("End VPN", format_hints.Hex),
("Tag", str),
("Protection", str),
("CommitCharge", int),
("PrivateMemory", int),
("Parent", format_hints.Hex),
("File", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,84 @@
import logging
from typing import Any, Iterable, List, Tuple
from volatility.framework import interfaces, layers, renderers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework.symbols.windows import extensions
from volatility.plugins import yarascan
from volatility.plugins.windows import pslist
vollog = logging.getLogger(__name__)
try:
import yara
except ImportError:
vollog.info("Python Yara module not found, plugin (and dependent plugins) not available")
class VadYaraScan(interfaces.plugins.PluginInterface):
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = "Primary kernel address space",
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
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),
requirements.IntRequirement(name = "max_size",
default = 0x40000000,
description = "Set the maximum size (default is 1GB)",
optional = True)
]
def _generator(self):
layer = self.context.memory[self.config['primary']]
rules = None
if self.config.get('yara_rules', None) is not None:
rule = self.config['yara_rules']
if rule[0] not in ["{", "/"]:
rule = '"{}"'.format(rule)
if self.config.get('case', False):
rule += " nocase"
if self.config.get('wide', False):
rule += " wide ascii"
rules = yara.compile(sources = {'n': 'rule r1 {{strings: $a = {} condition: $a}}'.format(rule)})
elif self.config.get('yara_file', None) is not None:
rules = yara.compile(file = layers.ResourceAccessor().open(self.config['yara_file'], "rb"))
else:
vollog.error("No yara rules, nor yara rules file were specified")
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
for task in pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func):
for offset, name in layer.scan(context = self.context,
scanner = yarascan.YaraScanner(rules = rules),
sections = self.get_vad_maps(task)):
yield format_hints.Hex(offset), name
def get_vad_maps(self, task: Any) -> Iterable[Tuple[int, int]]:
task = self._check_type(task, extensions._EPROCESS)
vad_root = task.get_vad_root()
for vad in vad_root.traverse():
end = vad.get_end()
start = vad.get_start()
yield (start, end - start)
def run(self):
return renderers.TreeGrid([('Offset', format_hints.Hex),
('Rule', str)], self._generator())
@@ -0,0 +1,172 @@
import io
import logging
from typing import Generator, List, Tuple
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.moddump as moddump
import volatility.plugins.windows.modules as modules
from volatility.framework import exceptions, renderers, constants, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
from volatility.plugins.windows import pslist
vollog = logging.getLogger(__name__)
try:
import pefile
except ImportError:
vollog.info("Python pefile module not found, plugin (and dependent plugins) not available")
raise
class VerInfo(interfaces_plugins.PluginInterface):
"""Lists version information from PE files"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
## TODO: we might add a regex option on the name later, but otherwise we're good
## TODO: and we don't want any CLI options from pslist, modules, or moddump
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"), ]
@classmethod
def get_version_information(cls,
context: interfaces.context.ContextInterface,
pe_table_name: str,
layer_name: str,
base_address: int) -> Tuple[int, int, int, int]:
"""Get File and Product version information from PE files
Args:
context: volatility context on which to operate
pe_table_name: name of the PE table
layer_name: name of the layer containing the PE file
base_address: base address of the PE (where MZ is found)
"""
if layer_name is None:
raise ValueError("Layer must be a string not None")
pe_data = io.BytesIO()
dos_header = context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = base_address,
layer_name = layer_name)
for offset, data in dos_header.reconstruct():
pe_data.seek(offset)
pe_data.write(data)
pe = pefile.PE(data = pe_data.getvalue(), fast_load = True)
pe.parse_data_directories([pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_RESOURCE"]])
major = pe.VS_FIXEDFILEINFO.ProductVersionMS >> 16
minor = pe.VS_FIXEDFILEINFO.ProductVersionMS & 0xFFFF
product = pe.VS_FIXEDFILEINFO.ProductVersionLS >> 16
build = pe.VS_FIXEDFILEINFO.ProductVersionLS & 0xFFFF
pe_data.close()
return major, minor, product, build
def _generator(self,
procs: Generator[interfaces.objects.ObjectInterface, None, None],
mods: Generator[interfaces.objects.ObjectInterface, None, None],
session_layers: Generator[str, None, None]):
"""Generates a list of PE file version info for processes, dlls, and modules.
Args:
procs: <generator> of processes
mods: <generator> of modules
session_layers: <generator> of layers in the session to be checked
"""
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
for mod in mods:
try:
BaseDllName = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = renderers.UnreadableValue()
session_layer_name = moddump.ModDump.find_session_layer(self.context, session_layers, mod.DllBase)
(major, minor, product, build) = [
renderers.NotAvailableValue()] * 4 # type: Tuple[Union[int, interfaces.renderers.BaseAbsentValue],Union[int, interfaces.renderers.BaseAbsentValue],Union[int, interfaces.renderers.BaseAbsentValue],Union[int, interfaces.renderers.BaseAbsentValue]]
try:
(major, minor, product, build) = self.get_version_information(self._context,
pe_table_name,
session_layer_name,
mod.DllBase)
except (exceptions.InvalidAddressException, ValueError, AttributeError):
(major, minor, product, build) = [renderers.UnreadableValue()] * 4
# the pid and process are not applicable for kernel modules
yield (0, (renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
format_hints.Hex(mod.DllBase),
BaseDllName,
major,
minor,
product,
build))
# now go through the process and dll lists
for proc in procs:
proc_layer_name = proc.add_process_layer()
for entry in proc.load_order_modules():
try:
BaseDllName = entry.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = renderers.UnreadableValue()
session_layer_name = moddump.ModDump.find_session_layer(self.context, session_layers, mod.DllBase)
(major, minor, product, build) = [renderers.NotAvailableValue()] * 4
try:
(major, minor, product, build) = self.get_version_information(self._context,
pe_table_name,
proc_layer_name,
entry.DllBase)
except (exceptions.InvalidAddressException, ValueError, AttributeError):
(major, minor, product, build) = [renderers.UnreadableValue()] * 4
yield (0, (proc.UniqueProcessId,
proc.ImageFileName.cast("string",
max_length = proc.ImageFileName.vol.count,
errors = "replace"),
format_hints.Hex(entry.DllBase),
BaseDllName,
major,
minor,
product,
build))
def run(self):
procs = pslist.PsList.list_processes(self.context,
self.config["primary"],
self.config["nt_symbols"])
mods = modules.Modules.list_modules(self.context,
self.config["primary"],
self.config["nt_symbols"])
# populate the session layers for kernel modules
session_layers = moddump.ModDump.get_session_layers(self.context,
self.config['primary'],
self.config['nt_symbols'])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Base", format_hints.Hex),
("Name", str),
("Major", int),
("Minor", int),
("Product", int),
("Build", int)],
self._generator(procs, mods, session_layers))
+91
View File
@@ -0,0 +1,91 @@
import logging
from typing import Iterable, Tuple, List
from volatility.framework import interfaces, renderers, layers
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins
from volatility.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
try:
import yara
except ImportError:
vollog.info("Python Yara module not found, plugin (and dependent plugins) not available")
raise
class YaraScanner(interfaces.layers.ScannerInterface):
# yara.Rules isn't exposed, so we can't type this properly
def __init__(self, rules) -> None:
super().__init__()
self._rules = rules
def __call__(self, data: bytes, data_offset: int) -> Iterable[Tuple[int, str]]:
for match in self._rules.match(data = data):
for offset, name, value in match.strings:
yield (offset + data_offset, name)
class YaraScan(plugins.PluginInterface):
"""Runs all relevant plugins that provide time related information and orders the results by time"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = "Primary kernel address space",
architectures = ["Intel32", "Intel64"]),
requirements.BooleanRequirement(name = "all",
description = "Scan both process and kernel memory",
default = False,
optional = True),
requirements.BooleanRequirement(name = "insensitive",
description = "Makes the search case insensitive",
default = False,
optional = True),
requirements.BooleanRequirement(name = "kernel",
description = "Scan kernel modules",
default = False,
optional = True),
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),
requirements.IntRequirement(name = "max_size",
default = 0x40000000,
description = "Set the maximum size (default is 1GB)",
optional = True)
]
def _generator(self):
layer = self.context.memory[self.config['primary']]
rules = None
if self.config.get('yara_rules', None) is not None:
rule = self.config['yara_rules']
if rule[0] not in ["{", "/"]:
rule = '"{}"'.format(rule)
if self.config.get('case', False):
rule += " nocase"
if self.config.get('wide', False):
rule += " wide ascii"
rules = yara.compile(sources = {'n': 'rule r1 {{strings: $a = {} condition: $a}}'.format(rule)})
elif self.config.get('yara_file', None) is not None:
rules = yara.compile(file = layers.ResourceAccessor().open(self.config['yara_file'], "rb"))
else:
vollog.error("No yara rules, nor yara rules file were specified")
for offset, name in layer.scan(context = self.context,
scanner = YaraScanner(rules = rules)):
yield (0, (format_hints.Hex(offset), name))
def run(self):
return renderers.TreeGrid([('Offset', format_hints.Hex),
('Rule', str)], self._generator())