Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: add --verbose option for ldrmodules plugin. #968

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 114 additions & 38 deletions volatility3/framework/plugins/windows/ldrmodules.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class LdrModules(interfaces.plugins.PluginInterface):
"""Lists the loaded modules in a particular windows memory image."""

_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (1, 0, 1)

@classmethod
def get_requirements(cls):
Expand All @@ -32,6 +32,12 @@ def get_requirements(cls):
description="Process IDs to include (all other processes are excluded)",
optional=True,
),
requirements.BooleanRequirement(
name="verbose",
digitalisx marked this conversation as resolved.
Show resolved Hide resolved
description="Print the full paths and base names in verbose mode",
default=False,
optional=True,
),
]

def _generator(self, procs):
Expand Down Expand Up @@ -82,45 +88,115 @@ def filter_function(x: interfaces.objects.ObjectInterface) -> bool:
init_mod = init_order_mod.get(base, None)
mem_mod = mem_order_mod.get(base, None)

yield (
0,
[
int(proc.UniqueProcessId),
str(
proc.ImageFileName.cast(
"string",
max_length=proc.ImageFileName.vol.count,
errors="replace",
)
),
format_hints.Hex(base),
load_mod is not None,
init_mod is not None,
mem_mod is not None,
mapped_files[base],
],
)
load = renderers.NotApplicableValue()
init = renderers.NotApplicableValue()
mem = renderers.NotApplicableValue()

try:
if load_mod:
load = "{0} : {1}".format(
load_mod.FullDllName.get_string(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these two values differ, or is the BaseDllName part of the FullDllName? Either way we try hard to avoid textually combining data into a single column, because it means that a program using volatility as a library has to do work to split the data back up into its components. As such, I'd either change the default column entry to the full one (and forget the flag) or if both bits of information are important, then I'd output a second column.

It's slightly more effort, but it will make it much easier for other code to process the results, and humans will still be able to read it. Since we have version numbers we can change the default output of a plugin without too much issue (and people can check the version number if they depend on specific columns).

load_mod.BaseDllName.get_string(),
)
if init_mod:
init = "{0} : {1}".format(
init_mod.FullDllName.get_string(),
init_mod.BaseDllName.get_string(),
)
if mem_mod:
mem = "{0} : {1}".format(
mem_mod.FullDllName.get_string(),
mem_mod.BaseDllName.get_string(),
)
except exceptions.InvalidAddressException:
continue

if not self.config.get("verbose", True):
yield (
0,
[
int(proc.UniqueProcessId),
str(
proc.ImageFileName.cast(
"string",
max_length=proc.ImageFileName.vol.count,
errors="replace",
)
),
format_hints.Hex(base),
load_mod is not None,
init_mod is not None,
mem_mod is not None,
mapped_files[base],
],
)
else:
yield (
0,
[
int(proc.UniqueProcessId),
str(
proc.ImageFileName.cast(
"string",
max_length=proc.ImageFileName.vol.count,
errors="replace",
)
),
format_hints.Hex(base),
load_mod is not None,
init_mod is not None,
mem_mod is not None,
mapped_files[base],
load,
init,
mem,
],
)

def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]

return renderers.TreeGrid(
[
("Pid", int),
("Process", str),
("Base", format_hints.Hex),
("InLoad", bool),
("InInit", bool),
("InMem", bool),
("MappedPath", str),
],
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=filter_func,
)
),
)
if not self.config.get("verbose", True):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing column outputs is doable, but it potentially makes it tricky for things that use the output of plugins automatically (nothing does that I know of yet, and they could read the data from the TreeGrid they get back, but...). If the extra data isn't too messy, then I'd just output it all, all the time. If it is, then we can go with the optional columns, but again, we're trying to return things as if we're in a database, so separation of things that are separate and as little duplication as possible... 5:)

return renderers.TreeGrid(
[
("Pid", int),
("Process", str),
("Base", format_hints.Hex),
("InLoad", bool),
("InInit", bool),
("InMem", bool),
("MappedPath", str),
],
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=filter_func,
)
),
)
else:
return renderers.TreeGrid(
[
("Pid", int),
("Process", str),
("Base", format_hints.Hex),
("InLoad", bool),
("InInit", bool),
("InMem", bool),
("MappedPath", str),
("LoadPath", str),
("InitPath", str),
("MemPath", str),
],
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=filter_func,
)
),
)