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

Fix for windows.strings revmap offsets #1043

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
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
131 changes: 101 additions & 30 deletions volatility3/framework/plugins/windows/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class Strings(interfaces.plugins.PluginInterface):
"""Reads output from the strings command and indicates which process(es) each string belongs to."""

_version = (1, 2, 0)
_version = (2, 0, 0)
_required_framework_version = (2, 0, 0)
strings_pattern = re.compile(rb"^(?:\W*)([0-9]+)(?:\W*)(\w[\w\W]+)\n?")

Expand Down Expand Up @@ -47,7 +47,13 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]

def run(self):
return renderers.TreeGrid(
[("String", str), ("Physical Address", format_hints.Hex), ("Result", str)],
[
("String", str),
("Region", str),
("PID", int),
("Physical Address", format_hints.Hex),
("Virtual Address", format_hints.Hex),
],
self._generator(),
)

Expand Down Expand Up @@ -81,22 +87,40 @@ def _generator(self) -> Generator[Tuple, None, None]:
last_prog: float = 0
line_count: float = 0
num_strings = len(string_list)
for offset, string in string_list:
for phys_offset, string in string_list:
line_count += 1
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),
),

# calculate the offset for this string within a 4096 page so
# that this offset can be added to mappings which are all
# page aligned. This ensures that a string located at phy
# add 0x1e64cd20 would carry the 0xd20 to the virtual offsets
# displayed in the plugin output. Without this it would show
# only the page that the string was found, rather than the
# actually addr. 0xFFF is 4095 e.g. all lower bits set.
offset_within_page = phys_offset & 0xFFF

mapping_entry = revmap.get(
phys_offset >> 12,
Copy link
Member

Choose a reason for hiding this comment

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

Hard coded 12

[{"region": "Unallocated", "pid": -1, "offset": 0x00}],
)

for item in mapping_entry:
# Get the full virtual address not just the page start
# If the string is in unalloacted memory, we set the offset to 0x00
offset = item.get("offset", 0x00)
virtual_address = offset + offset_within_page

yield (
0,
(
str(string.strip(), "latin-1"),
item.get("region", "Unallocated"),
item.get("pid", -1),
Copy link
Member

Choose a reason for hiding this comment

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

This also should default to a BaseAbsentValue derived class.

format_hints.Hex(phys_offset),
format_hints.Hex(virtual_address),
),
)

prog = line_count / num_strings * 100
if round(prog, 1) > last_prog:
last_prog = round(prog, 1)
Expand Down Expand Up @@ -147,14 +171,46 @@ def generate_mapping(
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):
offset, _, mapped_offset, mapped_size, maplayer = mapval
for val in range(mapped_offset, mapped_offset + mapped_size, 0x1000):
cur_set = reverse_map.get(val >> 12, set())
cur_set.add(("kernel", offset))
reverse_map[val >> 12] = cur_set
(
virt_offset,
_virt_size,
phy_offset,
phy_mapping_size,
_phy_layer_name,
) = mapval

# for each page within the mapping we need to store the phy_offset and
# the matching virt_offset
for offset_to_page_within_mapping in range(0, phy_mapping_size, 0x1000):
Copy link
Member

Choose a reason for hiding this comment

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

Hard coded 0x1000

# calculate the page number for this phy_offset, e.g. the ">> 12"
# drops the bits that would address an offset within the page.
# This means that all offsets within the same page get the same
# physical_page number.
physical_page = (
phy_mapping_size + offset_to_page_within_mapping
) >> 12
Copy link
Member

Choose a reason for hiding this comment

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

Hard coded 12


# get the existing mappings for this physical page from the
# reverse map set.
cur_set = reverse_map.get(physical_page, list())

# add a mapping for this virtual offset, taking care to add the
# offset_to_page_within_mapping to ensure that all pages match correctly.
# Without this the 2nd, 3rd etc pages would all incorrectly map to the same
# virtual offset.
cur_set.append(
{
"region": "Kernel",
"pid": -1,
Copy link
Member

Choose a reason for hiding this comment

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

This should be a BaseAbsentValue derived class. Maybe NotApplicable?

"offset": virt_offset + offset_to_page_within_mapping,
}
)

# store these results back in the reverse_map
reverse_map[physical_page] = cur_set
if progress_callback:
progress_callback(
(offset * 100) / layer.maximum_address,
(virt_offset * 100) / layer.maximum_address,
"Creating reverse kernel map",
)

Expand All @@ -178,22 +234,37 @@ def generate_mapping(

proc_layer = context.layers[proc_layer_name]
if isinstance(proc_layer, linear.LinearlyMappedLayer):
# this follows the same pattern as the kernel mappings above.
for mapval in proc_layer.mapping(
0x0, proc_layer.maximum_address, ignore_errors=True
):
mapped_offset, _, offset, mapped_size, maplayer = mapval
for val in range(
mapped_offset, mapped_offset + mapped_size, 0x1000
(
virt_offset,
_virt_size,
phy_offset,
phy_mapping_size,
_phy_layer_name,
) = mapval
for offset_to_page_within_mapping in range(
0, phy_mapping_size, 0x1000
Copy link
Member

Choose a reason for hiding this comment

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

This is hardcoding the page size (which then agrees with the value 12 also hardcoded everywhere). That's ok, but I think it would be better to pull them out and make them constants in the plugin (just so they stay the same if the code ever changes).

):
cur_set = reverse_map.get(mapped_offset >> 12, set())
cur_set.add(
(f"Process {process.UniqueProcessId}", offset)
physical_page = (
phy_offset + offset_to_page_within_mapping
) >> 12
Copy link
Member

Choose a reason for hiding this comment

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

Hard coded 12

cur_set = reverse_map.get(physical_page, list())
cur_set.append(
{
"region": "Process",
"pid": process.UniqueProcessId,
"offset": virt_offset
+ offset_to_page_within_mapping,
}
)
reverse_map[mapped_offset >> 12] = cur_set
reverse_map[physical_page] = cur_set
# FIXME: make the progress for all processes, rather than per-process
if progress_callback:
progress_callback(
(offset * 100) / layer.maximum_address,
(virt_offset * 100) / proc_layer.maximum_address,
f"Creating mapping for task {process.UniqueProcessId}",
)

Expand Down