mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-12 20:57:39 +02:00
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:
@@ -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())
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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))
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user