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: Allow windows.dlllist to report back DLLs from wow64 processes #1572

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
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
127 changes: 110 additions & 17 deletions volatility3/framework/symbols/windows/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from volatility3.framework.renderers import conversion
from volatility3.framework.symbols import generic
from volatility3.framework.symbols.windows.extensions import pool
from volatility3.framework.symbols import windows

vollog = logging.getLogger(__name__)

Expand Down Expand Up @@ -775,39 +776,131 @@ def get_peb(self) -> interfaces.objects.ObjectInterface:
)
return peb

def get_peb32(self) -> interfaces.objects.ObjectInterface:
"""Constructs a PEB32 object"""
if constants.BANG not in self.vol.type_name:
raise ValueError(
f"Invalid symbol table name syntax (no {constants.BANG} found)"
Copy link
Member

Choose a reason for hiding this comment

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

I can't recall if this is strictly true. I suspect it's true but I think it's possible you could ask the symbol table directly for this type, and it wouldn't necessarily include the table name. Were you running into errors or is this just defensive coding?

)

# add_process_layer can raise InvalidAddressException.
# if that happens, we let the exception propagate upwards
proc_layer_name = self.add_process_layer()
proc_layer = self._context.layers[proc_layer_name]

# Determine if process is running under WOW64.
if self.get_is_wow64():
proc = self.get_wow_64_process()
else:
return None
Copy link
Member

Choose a reason for hiding this comment

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

This means the function signature needs to be Optional[interfaces.objects.ObjectInterface].

# Confirm WoW64Process points to a valid process address
if not proc_layer.is_valid(proc):
raise exceptions.InvalidAddressException(
proc_layer_name, proc, f"Invalid Wow64Process address at {self.Peb:0x}"
)

# Leverage the context of existing symbol table to help configure
# a new symbol table for 32-bit types
sym_table = self.get_symbol_table_name()
config_path = self._context.symbol_space[sym_table].config_path

# Load the 32-bit types into a new symbol space
# We use the WindowsKernelIntermedSymbols class to make
# sure we get all the object helpers. For example, traversing
# linked-lists.
self._32bit_table_name = windows.WindowsKernelIntermedSymbols.create(
self._context, config_path, "windows", "wow64"
)

# windows 10
if self._context.symbol_space.has_type(
sym_table + constants.BANG + "_EWOW64PROCESS"
):
offset = proc.Peb

# vista sp0-sp1 and 2003 sp1-sp2
elif self._context.symbol_space.has_type(
sym_table + constants.BANG + "_WOW64_PROCESS"
):
offset = proc.Wow64

else:
offset = proc

peb32 = self._context.object(
f"{self._32bit_table_name}{constants.BANG}_PEB32",
layer_name=proc_layer_name,
offset=offset,
)
return peb32

def load_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Generator for DLLs in the order that they were loaded."""

try:
peb = self.get_peb()
yield from peb.Ldr.InLoadOrderModuleList.to_list(
f"{self.get_symbol_table_name()}{constants.BANG}_LDR_DATA_TABLE_ENTRY",
"InLoadOrderLinks",
)
pebs = [
self.get_peb(),
self.get_peb32(),
]
for peb in pebs:
if peb:
sym_table = self.get_symbol_table_name()
if peb.Ldr.vol.type_name.endswith("unsigned long"):
ldr_data = self._context.symbol_space.get_type(
self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA"
)
peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data)
sym_table = self._32bit_table_name
yield from peb.Ldr.InLoadOrderModuleList.to_list(
f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY",
"InLoadOrderLinks",
)
except exceptions.InvalidAddressException:
return None

def init_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Generator for DLLs in the order that they were initialized"""

try:
peb = self.get_peb()
yield from peb.Ldr.InInitializationOrderModuleList.to_list(
f"{self.get_symbol_table_name()}{constants.BANG}_LDR_DATA_TABLE_ENTRY",
"InInitializationOrderLinks",
)
pebs = [
self.get_peb(),
self.get_peb32(),
]
for peb in pebs:
if peb:
sym_table = self.get_symbol_table_name()
if peb.Ldr.vol.type_name.endswith("unsigned long"):
Copy link
Member

Choose a reason for hiding this comment

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

Hard coding specific types in may be troublesome in the future. There was an instance in the linux world where the pointers when from unsigned long to long unsigned int because of a change in Clang. This also just verifies the end, rather than everything after the constants.BANG so you might catch unwanted/unexpected types. Probably better to do something like constants.BANG.split(...vol.type_name)[-1] == 'unsigned long' although that would need testing to make sure it works properly in all instances.

Copy link
Member

Choose a reason for hiding this comment

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

Would there be a big problem in just always casting the value, regardless of whether it's already the right type, or just verify it's a point to the right subtype and otherwise cast it?

ldr_data = self._context.symbol_space.get_type(
self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA"
)
peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data)
sym_table = self._32bit_table_name
yield from peb.Ldr.InInitializationOrderModuleList.to_list(
f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY",
"InInitializationOrderLinks",
)
except exceptions.InvalidAddressException:
return None

def mem_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Generator for DLLs in the order that they appear in memory"""

try:
peb = self.get_peb()
yield from peb.Ldr.InMemoryOrderModuleList.to_list(
f"{self.get_symbol_table_name()}{constants.BANG}_LDR_DATA_TABLE_ENTRY",
"InMemoryOrderLinks",
)
pebs = [
Copy link
Member

Choose a reason for hiding this comment

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

This feels like duplicating quite a big more code than the previous version. It might be worth pulling out the pointer check on these into a separate little private subfunction?

self.get_peb(),
self.get_peb32(),
]
for peb in pebs:
if peb:
sym_table = self.get_symbol_table_name()
if peb.Ldr.vol.type_name.endswith("unsigned long"):
ldr_data = self._context.symbol_space.get_type(
self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA"
)
peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data)
sym_table = self._32bit_table_name
yield from peb.Ldr.InMemoryOrderModuleList.to_list(
f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY",
"InMemoryOrderLinks",
)
except exceptions.InvalidAddressException:
return None

Expand Down
Loading
Loading