Skip to content

Commit ff8d6db

Browse files
Merge branch 'develop' into additional_windows_testing
2 parents b80a341 + 4fd0f20 commit ff8d6db

File tree

142 files changed

+2685
-938
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

142 files changed

+2685
-938
lines changed

.github/workflows/ruff.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
steps:
1010
- uses: actions/checkout@v4
1111

12-
- uses: astral-sh/ruff-action@v1
12+
- uses: astral-sh/ruff-action@v3.2.1
1313
with:
1414
args: check
1515
src: "."

doc/source/simple-plugin.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ to be able to run properly. Any that are defined as optional need not necessari
5353
description = "Process IDs to include (all other processes are excluded)",
5454
optional = True
5555
),
56-
requirements.PluginRequirement(
56+
requirements.VersionRequirement(
5757
name = 'pslist',
58-
plugin = pslist.PsList,
58+
component = pslist.PsList,
5959
version = (2, 0, 0)
6060
),
6161
]

volatility3/cli/volshell/generic.py

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import string
99
import struct
1010
import sys
11+
import textwrap
1112
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union
1213
from urllib import parse, request
1314

@@ -23,6 +24,14 @@
2324
except ImportError:
2425
has_capstone = False
2526

27+
try:
28+
from IPython import terminal
29+
from traitlets import config as traitlets_config
30+
31+
has_ipython = True
32+
except ImportError:
33+
has_ipython = False
34+
2635

2736
class Volshell(interfaces.plugins.PluginInterface):
2837
"""Shell environment to directly interact with a memory image."""
@@ -51,7 +60,13 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
5160
description="File to load and execute at start",
5261
default=None,
5362
optional=True,
54-
)
63+
),
64+
requirements.BooleanRequirement(
65+
name="script-only",
66+
description="Exit volshell after the script specified in --script completes",
67+
default=False,
68+
optional=True,
69+
),
5570
]
5671
return reqs + [
5772
requirements.TranslationLayerRequirement(
@@ -69,43 +84,70 @@ def run(
6984
"""
7085

7186
# Try to enable tab completion
72-
try:
73-
import readline
74-
except ImportError:
75-
pass
76-
else:
77-
import rlcompleter
87+
if not has_ipython:
88+
try:
89+
import readline
90+
import rlcompleter
7891

79-
completer = rlcompleter.Completer(namespace=self._construct_locals_dict())
80-
readline.set_completer(completer.complete)
81-
readline.parse_and_bind("tab: complete")
82-
print("Readline imported successfully")
92+
completer = rlcompleter.Completer(
93+
namespace=self._construct_locals_dict()
94+
)
95+
readline.set_completer(completer.complete)
96+
readline.parse_and_bind("tab: complete")
97+
print("Readline imported successfully")
98+
except ImportError:
99+
print(
100+
"Readline or rlcompleter module could not be imported. Tab completion will not be available."
101+
)
83102

84103
# TODO: provide help, consider generic functions (pslist?) and/or providing windows/linux functions
85104

86105
mode = self.__module__.split(".")[-1]
87106
mode = mode[0].upper() + mode[1:]
88107

89-
banner = f"""
90-
Call help() to see available functions
108+
banner = textwrap.dedent(
109+
f"""
110+
Call help() to see available functions
91111
92-
Volshell mode : {mode}
93-
Current Layer : {self.current_layer}
94-
Current Symbol Table : {self.current_symbol_table}
95-
Current Kernel Name : {self.current_kernel_name}
96-
"""
112+
Volshell mode : {mode}
113+
Current Layer : {self.current_layer}
114+
Current Symbol Table : {self.current_symbol_table}
115+
Current Kernel Name : {self.current_kernel_name}
116+
"""
117+
)
97118

98119
sys.ps1 = f"({self.current_layer}) >>> "
99120
# Dict self._construct_locals_dict() will have priority on keys
100121
combined_locals = additional_locals.copy()
101122
combined_locals.update(self._construct_locals_dict())
102-
self.__console = code.InteractiveConsole(locals=combined_locals)
123+
if has_ipython:
124+
125+
class LayerNamePrompt(terminal.prompts.Prompts):
126+
def in_prompt_tokens(self, cli=None):
127+
slf = self.shell.user_ns.get("self")
128+
layer_name = slf.current_layer if slf else "no_layer"
129+
return [(terminal.prompts.Token.Prompt, f"[{layer_name}]> ")]
130+
131+
c = traitlets_config.Config()
132+
c.TerminalInteractiveShell.prompts_class = LayerNamePrompt
133+
c.InteractiveShellEmbed.banner2 = banner
134+
self.__console = terminal.embed.InteractiveShellEmbed(
135+
config=c, user_ns=combined_locals
136+
)
137+
else:
138+
self.__console = code.InteractiveConsole(locals=combined_locals)
103139
# Since we have to do work to add the option only once for all different modes of volshell, we can't
104140
# rely on the default having been set
105141
if self.config.get("script", None) is not None:
106142
self.run_script(location=self.config["script"])
107143

108-
self.__console.interact(banner=banner)
144+
if self.config.get("script-only"):
145+
exit()
146+
147+
if has_ipython:
148+
self.__console()
149+
else:
150+
self.__console.interact(banner=banner)
109151

110152
return renderers.TreeGrid([("Terminating", str)], None)
111153

@@ -277,23 +319,25 @@ def display_bytes(self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None
277319
self._display_data(offset, remaining_data)
278320

279321
def display_quadwords(
280-
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None
322+
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@"
281323
):
282324
"""Displays quad-word values (8 bytes) and corresponding ASCII characters"""
283325
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
284-
self._display_data(offset, remaining_data, format_string="Q")
326+
self._display_data(offset, remaining_data, format_string=f"{byteorder}Q")
285327

286328
def display_doublewords(
287-
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None
329+
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@"
288330
):
289331
"""Displays double-word values (4 bytes) and corresponding ASCII characters"""
290332
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
291-
self._display_data(offset, remaining_data, format_string="I")
333+
self._display_data(offset, remaining_data, format_string=f"{byteorder}I")
292334

293-
def display_words(self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None):
335+
def display_words(
336+
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@"
337+
):
294338
"""Displays word values (2 bytes) and corresponding ASCII characters"""
295339
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
296-
self._display_data(offset, remaining_data, format_string="H")
340+
self._display_data(offset, remaining_data, format_string=f"{byteorder}H")
297341

298342
def regex_scan(self, pattern, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None):
299343
"""Scans for regex pattern in layer using RegExScanner."""
@@ -508,10 +552,13 @@ def run_script(self, location: str):
508552
location = "file:" + request.pathname2url(location)
509553
print(f"Running code from {location}\n")
510554
accessor = resources.ResourceAccessor()
511-
with accessor.open(url=location) as fp:
512-
self.__console.runsource(
513-
io.TextIOWrapper(fp, encoding="utf-8").read(), symbol="exec"
514-
)
555+
with accessor.open(url=location) as handle, io.TextIOWrapper(
556+
handle, encoding="utf-8"
557+
) as fp:
558+
if has_ipython:
559+
self.__console.ex(fp.read())
560+
else:
561+
self.__console.runsource(fp.read(), symbol="exec")
515562
print("\nCode complete")
516563

517564
def load_file(self, location: str):

volatility3/cli/volshell/linux.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def get_requirements(cls):
3030
requirements.ModuleRequirement(
3131
name="kernel", description="Linux kernel module"
3232
),
33-
requirements.PluginRequirement(
34-
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
33+
requirements.VersionRequirement(
34+
name="pslist", component=pslist.PsList, version=(4, 0, 0)
3535
),
3636
requirements.IntRequirement(
3737
name="pid", description="Process ID", optional=True

volatility3/cli/volshell/mac.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ def get_requirements(cls):
1919
requirements.ModuleRequirement(
2020
name="kernel", description="Darwin kernel module"
2121
),
22-
requirements.PluginRequirement(
23-
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
22+
requirements.VersionRequirement(
23+
name="pslist", component=pslist.PsList, version=(3, 0, 0)
2424
),
2525
requirements.IntRequirement(
2626
name="pid", description="Process ID", optional=True

volatility3/cli/volshell/windows.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ class Volshell(generic.Volshell):
1717
def get_requirements(cls):
1818
return [
1919
requirements.ModuleRequirement(name="kernel", description="Windows kernel"),
20-
requirements.PluginRequirement(
21-
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
20+
requirements.VersionRequirement(
21+
name="pslist", component=pslist.PsList, version=(3, 0, 0)
2222
),
2323
requirements.IntRequirement(
2424
name="pid", description="Process ID", optional=True

volatility3/framework/constants/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# We use the SemVer 2.0.0 versioning scheme
22
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
3-
VERSION_MINOR = 23 # Number of changes that only add to the interface
3+
VERSION_MINOR = 25 # Number of changes that only add to the interface
44
VERSION_PATCH = 0 # Number of changes that do not change the interface
55
VERSION_SUFFIX = ""
66

volatility3/framework/objects/__init__.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,13 +402,35 @@ def _unmarshall(
402402
pointer should be recast. The "pointer" must always live within
403403
the space (even if the data provided is invalid).
404404
"""
405+
mask = context.layers[object_info.native_layer_name].address_mask
406+
new = (
407+
cls._get_raw_value(
408+
context, data_format, object_info.layer_name, object_info.offset
409+
)
410+
& mask
411+
)
412+
return new
413+
414+
@classmethod
415+
def _get_raw_value(
416+
cls,
417+
context: interfaces.context.ContextInterface,
418+
data_format: DataFormatInfo,
419+
layer_name: str,
420+
offset: int,
421+
) -> int:
405422
length, endian, signed = data_format
406423
if signed:
407424
raise ValueError("Pointers cannot have signed values")
408-
mask = context.layers[object_info.native_layer_name].address_mask
409-
data = context.layers.read(object_info.layer_name, object_info.offset, length)
425+
data = context.layers.read(layer_name, offset, length)
410426
value = int.from_bytes(data, byteorder=endian, signed=signed)
411-
return value & mask
427+
return value
428+
429+
def get_raw_value(self) -> int:
430+
raw = self._get_raw_value(
431+
self._context, self.vol.data_format, self.vol.layer_name, self.vol.offset
432+
)
433+
return raw
412434

413435
def dereference(
414436
self, layer_name: Optional[str] = None

0 commit comments

Comments
 (0)