mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-10 03:37:39 +02:00
Merge branch 'develop' into issues/issue1418
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name: Black python linter
|
||||
name: Black python formatter
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Ruff
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: astral-sh/ruff-action@v1
|
||||
with:
|
||||
args: check
|
||||
src: "."
|
||||
@@ -42,8 +42,13 @@ jobs:
|
||||
|
||||
- name: Testing...
|
||||
run: |
|
||||
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k test_windows -v
|
||||
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k test_linux -v
|
||||
# VolShell
|
||||
pytest ./test/test_volatility.py --volatility=volshell.py --image-dir=./test_images -k test_windows_volshell -v
|
||||
pytest ./test/test_volatility.py --volatility=volshell.py --image-dir=./test_images -k test_linux_volshell -v
|
||||
|
||||
# Volatility
|
||||
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k "test_windows and not test_windows_volshell" -v
|
||||
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k "test_linux and not test_linux_volshell" -v
|
||||
|
||||
- name: Clean up post-test
|
||||
run: |
|
||||
|
||||
@@ -28,10 +28,10 @@ class BannerCacheGenerator:
|
||||
|
||||
def run(self):
|
||||
context = contexts.Context()
|
||||
json_output = {'version': 1}
|
||||
json_output = {"version": 1}
|
||||
|
||||
path = self._path
|
||||
filename = '*'
|
||||
filename = "*"
|
||||
|
||||
for banner_cache in [linux.LinuxBannerCache, mac.MacBannerCache]:
|
||||
sub_path = banner_cache.os
|
||||
@@ -39,37 +39,54 @@ class BannerCacheGenerator:
|
||||
for extension in constants.ISF_EXTENSIONS:
|
||||
# Hopefully these will not be large lists, otherwise this might be slow
|
||||
try:
|
||||
for found in pathlib.Path(path).joinpath(sub_path).resolve().rglob(filename + extension):
|
||||
for found in (
|
||||
pathlib.Path(path)
|
||||
.joinpath(sub_path)
|
||||
.resolve()
|
||||
.rglob(filename + extension)
|
||||
):
|
||||
potentials.append(found.as_uri())
|
||||
except FileNotFoundError:
|
||||
# If there's no linux symbols, don't cry about it
|
||||
pass
|
||||
|
||||
new_banners = banner_cache.read_new_banners(context, 'BannerServer', potentials, banner_cache.symbol_name,
|
||||
banner_cache.os, progress_callback = PrintedProgress())
|
||||
new_banners = banner_cache.read_new_banners(
|
||||
context,
|
||||
"BannerServer",
|
||||
potentials,
|
||||
banner_cache.symbol_name,
|
||||
banner_cache.os,
|
||||
progress_callback=PrintedProgress(),
|
||||
)
|
||||
result_banners = {}
|
||||
for new_banner in new_banners:
|
||||
# Only accept file schemes
|
||||
value = [self.convert_url(url) for url in new_banners[new_banner] if
|
||||
urllib.parse.urlparse(url).scheme == 'file']
|
||||
value = [
|
||||
self.convert_url(url)
|
||||
for url in new_banners[new_banner]
|
||||
if urllib.parse.urlparse(url).scheme == "file"
|
||||
]
|
||||
if value and new_banner:
|
||||
# Convert files into URLs
|
||||
result_banners[str(base64.b64encode(new_banner), 'latin-1')] = value
|
||||
result_banners[str(base64.b64encode(new_banner), "latin-1")] = value
|
||||
|
||||
json_output[banner_cache.os] = result_banners
|
||||
|
||||
output_path = os.path.join(self._path, 'banners.json')
|
||||
with open(output_path, 'w') as fp:
|
||||
output_path = os.path.join(self._path, "banners.json")
|
||||
with open(output_path, "w") as fp:
|
||||
vollog.warning(f"Banners file written to {output_path}")
|
||||
json.dump(json_output, fp)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--path', default = os.path.dirname(__file__))
|
||||
parser.add_argument('--urlprefix', help = 'Web prefix that will eventually serve the ISF files',
|
||||
default = 'http://localhost/symbols')
|
||||
parser.add_argument("--path", default=os.path.dirname(__file__))
|
||||
parser.add_argument(
|
||||
"--urlprefix",
|
||||
help="Web prefix that will eventually serve the ISF files",
|
||||
default="http://localhost/symbols",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
+224
-116
@@ -15,17 +15,17 @@ class VolatilityImage:
|
||||
filepath: str = ""
|
||||
vol2_profile: str = ""
|
||||
vol2_imageinfo_time: float = None
|
||||
vol2_plugin_parameters: Dict[str, List[str]] = field(default_factory = dict)
|
||||
vol3_plugin_parameters: Dict[str, List[str]] = field(default_factory = dict)
|
||||
rekall_plugin_parameters: Dict[str, List[str]] = field(default_factory = dict)
|
||||
vol2_plugin_parameters: Dict[str, List[str]] = field(default_factory=dict)
|
||||
vol3_plugin_parameters: Dict[str, List[str]] = field(default_factory=dict)
|
||||
rekall_plugin_parameters: Dict[str, List[str]] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VolatilityPlugin:
|
||||
name: str = ""
|
||||
vol2_plugin_parameters: List[str] = field(default_factory = list)
|
||||
vol3_plugin_parameters: List[str] = field(default_factory = list)
|
||||
rekall_plugin_parameters: List[str] = field(default_factory = list)
|
||||
vol2_plugin_parameters: List[str] = field(default_factory=list)
|
||||
vol3_plugin_parameters: List[str] = field(default_factory=list)
|
||||
rekall_plugin_parameters: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
class VolatilityTest:
|
||||
@@ -39,32 +39,50 @@ class VolatilityTest:
|
||||
def result_titles(self) -> List[str]:
|
||||
return [self.long_name]
|
||||
|
||||
def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str) -> None:
|
||||
def create_prerequisites(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def create_results(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str) -> List[float]:
|
||||
def create_results(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str
|
||||
) -> List[float]:
|
||||
self.create_prerequisites(plugin, image, image_hash)
|
||||
|
||||
# Volatility 2 Test
|
||||
print(f"[*] Testing {self.short_name} {plugin.name} with image {image.filepath}")
|
||||
print(
|
||||
f"[*] Testing {self.short_name} {plugin.name} with image {image.filepath}"
|
||||
)
|
||||
os.chdir(self.path)
|
||||
cmd = self.plugin_cmd(plugin, image)
|
||||
start_time = time.perf_counter()
|
||||
try:
|
||||
completed = subprocess.run(cmd, cwd = self.path, capture_output = True, timeout = 420)
|
||||
completed = subprocess.run(
|
||||
cmd, cwd=self.path, capture_output=True, timeout=420
|
||||
)
|
||||
except subprocess.TimeoutExpired as excp:
|
||||
completed = excp
|
||||
end_time = time.perf_counter()
|
||||
total_time = end_time - start_time
|
||||
print(f" Tested {self.short_name} {plugin.name} with image {image.filepath}: {total_time}")
|
||||
print(
|
||||
f" Tested {self.short_name} {plugin.name} with image {image.filepath}: {total_time}"
|
||||
)
|
||||
with open(
|
||||
os.path.join(self.output_directory, f'{self.short_name}_{plugin.name}_{image_hash}_stdout'),
|
||||
"wb") as f:
|
||||
os.path.join(
|
||||
self.output_directory,
|
||||
f"{self.short_name}_{plugin.name}_{image_hash}_stdout",
|
||||
),
|
||||
"wb",
|
||||
) as f:
|
||||
f.write(completed.stdout)
|
||||
if completed.stderr:
|
||||
with open(
|
||||
os.path.join(self.output_directory, f'{self.short_name}_{plugin.name}_{image_hash}_stderr'),
|
||||
"wb") as f:
|
||||
os.path.join(
|
||||
self.output_directory,
|
||||
f"{self.short_name}_{plugin.name}_{image_hash}_stderr",
|
||||
),
|
||||
"wb",
|
||||
) as f:
|
||||
f.write(completed.stderr)
|
||||
return [total_time]
|
||||
|
||||
@@ -77,31 +95,57 @@ class Volatility2Test(VolatilityTest):
|
||||
long_name = "Volatility 2"
|
||||
|
||||
def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage):
|
||||
return ["python2", "-u", "vol.py", "-f", image.filepath, "--profile", image.vol2_profile
|
||||
] + plugin.vol2_plugin_parameters + image.vol2_plugin_parameters.get(plugin.name, [])
|
||||
return (
|
||||
[
|
||||
"python2",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-f",
|
||||
image.filepath,
|
||||
"--profile",
|
||||
image.vol2_profile,
|
||||
]
|
||||
+ plugin.vol2_plugin_parameters
|
||||
+ image.vol2_plugin_parameters.get(plugin.name, [])
|
||||
)
|
||||
|
||||
def result_titles(self):
|
||||
return [self.long_name, "Imageinfo", f"{self.long_name} + Imageinfo"]
|
||||
|
||||
def create_results(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash) -> List[float]:
|
||||
def create_results(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash
|
||||
) -> List[float]:
|
||||
result = super().create_results(plugin, image, image_hash)
|
||||
result += [image.vol2_imageinfo_time, result[0] + image.vol2_imageinfo_time]
|
||||
return result
|
||||
|
||||
def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash):
|
||||
def create_prerequisites(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash
|
||||
):
|
||||
# Volatility 2 image info
|
||||
if not image.vol2_profile:
|
||||
print(f"[*] Testing {self.short_name} imageinfo with image {image.filepath}")
|
||||
print(
|
||||
f"[*] Testing {self.short_name} imageinfo with image {image.filepath}"
|
||||
)
|
||||
os.chdir(self.path)
|
||||
cmd = ["python2", "-u", "vol.py", "-f", image.filepath, "imageinfo"]
|
||||
start_time = time.perf_counter()
|
||||
vol2_completed = subprocess.run(cmd, cwd = self.path, capture_output = True)
|
||||
vol2_completed = subprocess.run(cmd, cwd=self.path, capture_output=True)
|
||||
end_time = time.perf_counter()
|
||||
image.vol2_imageinfo_time = end_time - start_time
|
||||
print(f" Tested volatility2 imageinfo with image {image.filepath}: {end_time - start_time}")
|
||||
with open(os.path.join(self.output_directory, f'vol2_imageinfo_{image_hash}_stdout'), "wb") as f:
|
||||
print(
|
||||
f" Tested volatility2 imageinfo with image {image.filepath}: {end_time - start_time}"
|
||||
)
|
||||
with open(
|
||||
os.path.join(
|
||||
self.output_directory, f"vol2_imageinfo_{image_hash}_stdout"
|
||||
),
|
||||
"wb",
|
||||
) as f:
|
||||
f.write(vol2_completed.stdout)
|
||||
image.vol2_profile = re.search(b"Suggested Profile\(s\) : ([^,]+)", vol2_completed.stdout)[1]
|
||||
image.vol2_profile = re.search(
|
||||
rb"Suggested Profile\(s\) : ([^,]+)", vol2_completed.stdout
|
||||
)[1]
|
||||
|
||||
|
||||
class RekallTest(VolatilityTest):
|
||||
@@ -113,11 +157,16 @@ class RekallTest(VolatilityTest):
|
||||
plugin.rekall_plugin_parameters = plugin.vol2_plugin_parameters
|
||||
if not image.rekall_plugin_parameters:
|
||||
image.rekall_plugin_parameters = image.vol2_plugin_parameters
|
||||
return ["rekall", "-f", image.filepath] + plugin.rekall_plugin_parameters + image.rekall_plugin_parameters.get(
|
||||
plugin.name, [])
|
||||
return (
|
||||
["rekall", "-f", image.filepath]
|
||||
+ plugin.rekall_plugin_parameters
|
||||
+ image.rekall_plugin_parameters.get(plugin.name, [])
|
||||
)
|
||||
|
||||
def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str) -> None:
|
||||
shutil.rmtree('/home/mike/.rekall_cache/sessions')
|
||||
def create_prerequisites(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str
|
||||
) -> None:
|
||||
shutil.rmtree("/home/mike/.rekall_cache/sessions")
|
||||
|
||||
|
||||
class Volatility3Test(VolatilityTest):
|
||||
@@ -125,14 +174,18 @@ class Volatility3Test(VolatilityTest):
|
||||
long_name = "Volatility 3"
|
||||
|
||||
def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage) -> List[str]:
|
||||
return [
|
||||
"python",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-q",
|
||||
"-f",
|
||||
image.filepath,
|
||||
] + plugin.vol3_plugin_parameters + image.vol3_plugin_parameters.get(plugin.name, [])
|
||||
return (
|
||||
[
|
||||
"python",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-q",
|
||||
"-f",
|
||||
image.filepath,
|
||||
]
|
||||
+ plugin.vol3_plugin_parameters
|
||||
+ image.vol3_plugin_parameters.get(plugin.name, [])
|
||||
)
|
||||
|
||||
|
||||
class Volatility3PyPyTest(VolatilityTest):
|
||||
@@ -140,26 +193,32 @@ class Volatility3PyPyTest(VolatilityTest):
|
||||
long_name = "Volatility 3 (PyPy)"
|
||||
|
||||
def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage) -> List[str]:
|
||||
return [
|
||||
"pypy3",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-q",
|
||||
"-f",
|
||||
image.filepath,
|
||||
] + plugin.vol3_plugin_parameters + image.vol3_plugin_parameters.get(plugin.name, [])
|
||||
return (
|
||||
[
|
||||
"pypy3",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-q",
|
||||
"-f",
|
||||
image.filepath,
|
||||
]
|
||||
+ plugin.vol3_plugin_parameters
|
||||
+ image.vol3_plugin_parameters.get(plugin.name, [])
|
||||
)
|
||||
|
||||
|
||||
class VolatilityTester:
|
||||
|
||||
def __init__(self,
|
||||
images: List[VolatilityImage],
|
||||
plugins: List[VolatilityPlugin],
|
||||
frameworks: List[str],
|
||||
output_dir: str,
|
||||
vol2_path: str = None,
|
||||
vol3_path: str = None,
|
||||
rekall_path = None):
|
||||
def __init__(
|
||||
self,
|
||||
images: List[VolatilityImage],
|
||||
plugins: List[VolatilityPlugin],
|
||||
frameworks: List[str],
|
||||
output_dir: str,
|
||||
vol2_path: str = None,
|
||||
vol3_path: str = None,
|
||||
rekall_path=None,
|
||||
):
|
||||
self.images = images
|
||||
self.plugins = plugins
|
||||
if not vol2_path:
|
||||
@@ -172,7 +231,7 @@ class VolatilityTester:
|
||||
Volatility3Test(vol3_path, output_dir),
|
||||
Volatility3PyPyTest(vol3_path, output_dir),
|
||||
Volatility2Test(vol2_path, output_dir),
|
||||
RekallTest(rekall_path, output_dir)
|
||||
RekallTest(rekall_path, output_dir),
|
||||
]
|
||||
self.tests = [x for x in available_tests if x.short_name.lower() in frameworks]
|
||||
self.csv_writer = None
|
||||
@@ -183,7 +242,7 @@ class VolatilityTester:
|
||||
print(f"[?] Frameworks: {[x.long_name for x in self.tests]}")
|
||||
|
||||
def run_tests(self):
|
||||
with open("volatility-timings.csv", 'w') as csvfile:
|
||||
with open("volatility-timings.csv", "w") as csvfile:
|
||||
self.csv_writer = csv.writer(csvfile)
|
||||
titles = ["Image Hash", "Image Path", "Plugin Name"]
|
||||
for test in self.tests:
|
||||
@@ -203,72 +262,121 @@ class VolatilityTester:
|
||||
self.csv_writer.writerow([image_hash, image.filepath, plugin.name] + results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
plugins = [
|
||||
VolatilityPlugin(name = "pslist",
|
||||
vol2_plugin_parameters = ["pslist"],
|
||||
vol3_plugin_parameters = ["windows.pslist"]),
|
||||
VolatilityPlugin(name = "psscan",
|
||||
vol2_plugin_parameters = ["psscan"],
|
||||
vol3_plugin_parameters = ["windows.psscan"],
|
||||
rekall_plugin_parameters = ["psscan", "--scan_kernel"]),
|
||||
VolatilityPlugin(name = "driverscan",
|
||||
vol2_plugin_parameters = ["driverscan"],
|
||||
vol3_plugin_parameters = ["windows.driverscan"],
|
||||
rekall_plugin_parameters = ["driverscan", "--scan_kernel"]),
|
||||
VolatilityPlugin(name = "handles",
|
||||
vol2_plugin_parameters = ["handles"],
|
||||
vol3_plugin_parameters = ["windows.handles"]),
|
||||
VolatilityPlugin(name = "modules",
|
||||
vol2_plugin_parameters = ["modules"],
|
||||
vol3_plugin_parameters = ["windows.modules"]),
|
||||
VolatilityPlugin(name = "hivelist",
|
||||
vol2_plugin_parameters = ["hivelist"],
|
||||
vol3_plugin_parameters = ["registry.hivelist"],
|
||||
rekall_plugin_parameters = ["hives"]),
|
||||
VolatilityPlugin(name = "vadinfo",
|
||||
vol2_plugin_parameters = ["vadinfo"],
|
||||
vol3_plugin_parameters = ["windows.vadinfo"],
|
||||
rekall_plugin_parameters = ["vad"]),
|
||||
VolatilityPlugin(name = "modscan",
|
||||
vol2_plugin_parameters = ["modscan"],
|
||||
vol3_plugin_parameters = ["windows.modscan"],
|
||||
rekall_plugin_parameters = ["modscan", "--scan_kernel"]),
|
||||
VolatilityPlugin(name = "svcscan",
|
||||
vol2_plugin_parameters = ["svcscan"],
|
||||
vol3_plugin_parameters = ["windows.svcscan"],
|
||||
rekall_plugin_parameters = ["svcscan"]),
|
||||
VolatilityPlugin(name = "ssdt", vol2_plugin_parameters = ["ssdt"], vol3_plugin_parameters = ["windows.ssdt"]),
|
||||
VolatilityPlugin(name = "printkey",
|
||||
vol2_plugin_parameters = ["printkey", "-K", "Classes"],
|
||||
vol3_plugin_parameters = ["registry.printkey", "--key", "Classes"],
|
||||
rekall_plugin_parameters = ["printkey", "--key", "Classes"])
|
||||
VolatilityPlugin(
|
||||
name="pslist",
|
||||
vol2_plugin_parameters=["pslist"],
|
||||
vol3_plugin_parameters=["windows.pslist"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="psscan",
|
||||
vol2_plugin_parameters=["psscan"],
|
||||
vol3_plugin_parameters=["windows.psscan"],
|
||||
rekall_plugin_parameters=["psscan", "--scan_kernel"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="driverscan",
|
||||
vol2_plugin_parameters=["driverscan"],
|
||||
vol3_plugin_parameters=["windows.driverscan"],
|
||||
rekall_plugin_parameters=["driverscan", "--scan_kernel"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="handles",
|
||||
vol2_plugin_parameters=["handles"],
|
||||
vol3_plugin_parameters=["windows.handles"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="modules",
|
||||
vol2_plugin_parameters=["modules"],
|
||||
vol3_plugin_parameters=["windows.modules"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="hivelist",
|
||||
vol2_plugin_parameters=["hivelist"],
|
||||
vol3_plugin_parameters=["registry.hivelist"],
|
||||
rekall_plugin_parameters=["hives"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="vadinfo",
|
||||
vol2_plugin_parameters=["vadinfo"],
|
||||
vol3_plugin_parameters=["windows.vadinfo"],
|
||||
rekall_plugin_parameters=["vad"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="modscan",
|
||||
vol2_plugin_parameters=["modscan"],
|
||||
vol3_plugin_parameters=["windows.modscan"],
|
||||
rekall_plugin_parameters=["modscan", "--scan_kernel"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="svcscan",
|
||||
vol2_plugin_parameters=["svcscan"],
|
||||
vol3_plugin_parameters=["windows.svcscan"],
|
||||
rekall_plugin_parameters=["svcscan"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="ssdt",
|
||||
vol2_plugin_parameters=["ssdt"],
|
||||
vol3_plugin_parameters=["windows.ssdt"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="printkey",
|
||||
vol2_plugin_parameters=["printkey", "-K", "Classes"],
|
||||
vol3_plugin_parameters=["registry.printkey", "--key", "Classes"],
|
||||
rekall_plugin_parameters=["printkey", "--key", "Classes"],
|
||||
),
|
||||
]
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--output-dir", type = str, default = os.getcwd(), help = "Directory to store all results")
|
||||
parser.add_argument("--vol3path",
|
||||
type = str,
|
||||
default = os.path.join(os.getcwd(), 'volatility3'),
|
||||
help = "Path ot the volatility 3 directory")
|
||||
parser.add_argument("--vol2path",
|
||||
type = str,
|
||||
default = os.path.join(os.getcwd(), 'volatility'),
|
||||
help = "Path to the volatility 2 directory")
|
||||
parser.add_argument("--rekallpath",
|
||||
type = str,
|
||||
default = os.path.join(os.getcwd(), 'rekall'),
|
||||
help = "Path to the rekall directory")
|
||||
parser.add_argument("--frameworks",
|
||||
nargs = "+",
|
||||
type = str,
|
||||
choices = [x.short_name.lower() for x in VolatilityTest.__subclasses__()],
|
||||
default = [x.short_name.lower() for x in VolatilityTest.__subclasses__()],
|
||||
help = "A comma separated list of frameworks to test")
|
||||
parser.add_argument('images', metavar = 'IMAGE', type = str, nargs = '+', help = 'The list of images to compare')
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=str,
|
||||
default=os.getcwd(),
|
||||
help="Directory to store all results",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vol3path",
|
||||
type=str,
|
||||
default=os.path.join(os.getcwd(), "volatility3"),
|
||||
help="Path ot the volatility 3 directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vol2path",
|
||||
type=str,
|
||||
default=os.path.join(os.getcwd(), "volatility"),
|
||||
help="Path to the volatility 2 directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rekallpath",
|
||||
type=str,
|
||||
default=os.path.join(os.getcwd(), "rekall"),
|
||||
help="Path to the rekall directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--frameworks",
|
||||
nargs="+",
|
||||
type=str,
|
||||
choices=[x.short_name.lower() for x in VolatilityTest.__subclasses__()],
|
||||
default=[x.short_name.lower() for x in VolatilityTest.__subclasses__()],
|
||||
help="A comma separated list of frameworks to test",
|
||||
)
|
||||
parser.add_argument(
|
||||
"images",
|
||||
metavar="IMAGE",
|
||||
type=str,
|
||||
nargs="+",
|
||||
help="The list of images to compare",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
vt = VolatilityTester([VolatilityImage(filepath = x) for x in args.images], plugins,
|
||||
[x.lower() for x in args.frameworks], args.output_dir, args.vol2path, args.vol3path,
|
||||
args.rekallpath)
|
||||
vt = VolatilityTester(
|
||||
[VolatilityImage(filepath=x) for x in args.images],
|
||||
plugins,
|
||||
[x.lower() for x in args.frameworks],
|
||||
args.output_dir,
|
||||
args.vol2path,
|
||||
args.vol3path,
|
||||
args.rekallpath,
|
||||
)
|
||||
vt.run_tests()
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
# Cleaned up C version (as the basis for my code) here, thanks to Pepijn Bruienne / @bruienne
|
||||
# https://gist.github.com/bruienne/029494bbcfb358098b41
|
||||
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def seekread(f, offset = None, length = 0, relative = True):
|
||||
def seekread(f, offset=None, length=0, relative=True):
|
||||
if offset is not None:
|
||||
# offset provided, let's seek
|
||||
f.seek(offset, [0, 1, 2][relative])
|
||||
@@ -22,55 +23,57 @@ def seekread(f, offset = None, length = 0, relative = True):
|
||||
|
||||
def parse_pbzx(pbzx_path):
|
||||
section = 0
|
||||
xar_out_path = '%s.part%02d.cpio.xz' % (pbzx_path, section)
|
||||
with open(pbzx_path, 'rb') as f:
|
||||
xar_out_path = f"{pbzx_path}.part{section:02d}.cpio.xz"
|
||||
with open(pbzx_path, "rb") as f:
|
||||
# pbzx = f.read()
|
||||
# f.close()
|
||||
magic = seekread(f, length = 4)
|
||||
if magic != 'pbzx':
|
||||
magic = seekread(f, length=4)
|
||||
if magic != "pbzx":
|
||||
raise RuntimeError("Error: Not a pbzx file")
|
||||
# Read 8 bytes for initial flags
|
||||
flags = seekread(f, length = 8)
|
||||
flags = seekread(f, length=8)
|
||||
# Interpret the flags as a 64-bit big-endian unsigned int
|
||||
flags = struct.unpack('>Q', flags)[0]
|
||||
flags = struct.unpack(">Q", flags)[0]
|
||||
while flags & (1 << 24):
|
||||
with open(xar_out_path, 'wb') as xar_f:
|
||||
with open(xar_out_path, "wb") as xar_f:
|
||||
xar_f.seek(0, os.SEEK_END)
|
||||
# Read in more flags
|
||||
flags = seekread(f, length = 8)
|
||||
flags = struct.unpack('>Q', flags)[0]
|
||||
flags = seekread(f, length=8)
|
||||
flags = struct.unpack(">Q", flags)[0]
|
||||
# Read in length
|
||||
f_length = seekread(f, length = 8)
|
||||
f_length = struct.unpack('>Q', f_length)[0]
|
||||
xzmagic = seekread(f, length = 6)
|
||||
if xzmagic != '\xfd7zXZ\x00':
|
||||
f_length = seekread(f, length=8)
|
||||
f_length = struct.unpack(">Q", f_length)[0]
|
||||
xzmagic = seekread(f, length=6)
|
||||
if xzmagic != "\xfd7zXZ\x00":
|
||||
# This isn't xz content, this is actually _raw decompressed cpio_ chunk of 16MB in size...
|
||||
# Let's back up ...
|
||||
seekread(f, offset = -6, length = 0)
|
||||
seekread(f, offset=-6, length=0)
|
||||
# ... and split it out ...
|
||||
f_content = seekread(f, length = f_length)
|
||||
f_content = seekread(f, length=f_length)
|
||||
section += 1
|
||||
decomp_out = '%s.part%02d.cpio' % (pbzx_path, section)
|
||||
with open(decomp_out, 'wb') as g:
|
||||
decomp_out = f"{pbzx_path}.part{section:02d}.cpio"
|
||||
with open(decomp_out, "wb") as g:
|
||||
g.write(f_content)
|
||||
# Now to start the next section, which should hopefully be .xz (we'll just assume it is ...)
|
||||
section += 1
|
||||
xar_out_path = '%s.part%02d.cpio.xz' % (pbzx_path, section)
|
||||
xar_out_path = f"{pbzx_path}.part{section:02d}.cpio.xz"
|
||||
else:
|
||||
f_length -= 6
|
||||
# This part needs buffering
|
||||
f_content = seekread(f, length = f_length)
|
||||
tail = seekread(f, offset = -2, length = 2)
|
||||
f_content = seekread(f, length=f_length)
|
||||
tail = seekread(f, offset=-2, length=2)
|
||||
xar_f.write(xzmagic)
|
||||
xar_f.write(f_content)
|
||||
if tail != 'YZ':
|
||||
if tail != "YZ":
|
||||
raise RuntimeError("Error: Footer is not xar file footer")
|
||||
|
||||
|
||||
def main():
|
||||
parse_pbzx(sys.argv[1])
|
||||
print("Now xz decompress the .xz chunks, then 'cat' them all together in order into a single new.cpio file")
|
||||
print(
|
||||
"Now xz decompress the .xz chunks, then 'cat' them all together in order into a single new.cpio file"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+122
-75
@@ -13,10 +13,10 @@ import pdbparse.undecorate
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(1)
|
||||
formatter = logging.Formatter('%(levelname)-8s %(name)-12s: %(message)s')
|
||||
formatter = logging.Formatter("%(levelname)-8s %(name)-12s: %(message)s")
|
||||
console.setFormatter(formatter)
|
||||
logger.addHandler(console)
|
||||
|
||||
@@ -25,19 +25,19 @@ class PDBRetreiver:
|
||||
|
||||
def retreive_pdb(self, guid: str, file_name: str) -> Optional[str]:
|
||||
logger.info("Download PDB file...")
|
||||
file_name = ".".join(file_name.split(".")[:-1] + ['pdb'])
|
||||
for sym_url in ['http://msdl.microsoft.com/download/symbols']:
|
||||
file_name = ".".join(file_name.split(".")[:-1] + ["pdb"])
|
||||
for sym_url in ["http://msdl.microsoft.com/download/symbols"]:
|
||||
url = sym_url + f"/{file_name}/{guid}/"
|
||||
|
||||
result = None
|
||||
for suffix in [file_name[:-1] + '_', file_name]:
|
||||
for suffix in [file_name[:-1] + "_", file_name]:
|
||||
try:
|
||||
logger.debug(f"Attempting to retrieve {url + suffix}")
|
||||
logger.debug("Attempting to retrieve %s", url + suffix)
|
||||
result, _ = request.urlretrieve(url + suffix)
|
||||
except request.HTTPError as excp:
|
||||
logger.debug(f"Failed with {excp}")
|
||||
logger.debug("Failed with %s", excp)
|
||||
if result:
|
||||
logger.debug(f"Successfully written to {result}")
|
||||
logger.debug("Successfully written to %s", result)
|
||||
break
|
||||
return result
|
||||
|
||||
@@ -69,7 +69,7 @@ class PDBConvertor:
|
||||
"float": "float",
|
||||
"double": "float",
|
||||
"long double": "float",
|
||||
"void": "void"
|
||||
"void": "void",
|
||||
}
|
||||
|
||||
base_type_size = {
|
||||
@@ -122,13 +122,18 @@ class PDBConvertor:
|
||||
self._seen_ctypes.add(ctype)
|
||||
return self.ctype[ctype]
|
||||
|
||||
def lookup_ctype_pointers(self, ctype_pointer: str) -> Dict[str, Union[str, Dict[str, str]]]:
|
||||
base_type = ctype_pointer.replace('32P', '').replace('64P', '')
|
||||
def lookup_ctype_pointers(
|
||||
self, ctype_pointer: str
|
||||
) -> Dict[str, Union[str, Dict[str, str]]]:
|
||||
base_type = ctype_pointer.replace("32P", "").replace("64P", "")
|
||||
if base_type == ctype_pointer:
|
||||
# We raise a KeyError, because we've been asked about a type that isn't a pointer
|
||||
raise KeyError
|
||||
self._seen_ctypes.add(base_type)
|
||||
return {"kind": "pointer", "subtype": {"kind": "base", "name": self.ctype[base_type]}}
|
||||
return {
|
||||
"kind": "pointer",
|
||||
"subtype": {"kind": "base", "name": self.ctype[base_type]},
|
||||
}
|
||||
|
||||
def read_pdb(self) -> Dict:
|
||||
"""Reads in the PDB file and forms essentially a python dictionary of necessary data"""
|
||||
@@ -137,32 +142,31 @@ class PDBConvertor:
|
||||
"enums": self.read_enums(),
|
||||
"metadata": self.generate_metadata(),
|
||||
"symbols": self.read_symbols(),
|
||||
"base_types": self.read_basetypes()
|
||||
"base_types": self.read_basetypes(),
|
||||
}
|
||||
return output
|
||||
|
||||
def generate_metadata(self) -> Dict[str, Any]:
|
||||
"""Generates the metadata necessary for this object"""
|
||||
dbg = self._pdb.STREAM_DBI
|
||||
last_bytes = str(binascii.hexlify(self._pdb.STREAM_PDB.GUID.Data4), 'ascii')[-16:]
|
||||
guidstr = u'{:08x}{:04x}{:04x}{}'.format(self._pdb.STREAM_PDB.GUID.Data1, self._pdb.STREAM_PDB.GUID.Data2,
|
||||
self._pdb.STREAM_PDB.GUID.Data3, last_bytes)
|
||||
last_bytes = str(binascii.hexlify(self._pdb.STREAM_PDB.GUID.Data4), "ascii")[
|
||||
-16:
|
||||
]
|
||||
guidstr = f"{self._pdb.STREAM_PDB.GUID.Data1:08x}{self._pdb.STREAM_PDB.GUID.Data2:04x}{self._pdb.STREAM_PDB.GUID.Data3:04x}{last_bytes}"
|
||||
pdb_data = {
|
||||
"GUID": guidstr.upper(),
|
||||
"age": self._pdb.STREAM_PDB.Age,
|
||||
"database": "ntkrnlmp.pdb",
|
||||
"machine_type": int(dbg.machine)
|
||||
"machine_type": int(dbg.machine),
|
||||
}
|
||||
result = {
|
||||
"format": "6.0.0",
|
||||
"producer": {
|
||||
"datetime": datetime.datetime.now().isoformat(),
|
||||
"name": "pdbconv",
|
||||
"version": "0.1.0"
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"windows": {
|
||||
"pdb": pdb_data
|
||||
}
|
||||
"windows": {"pdb": pdb_data},
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -173,16 +177,21 @@ class PDBConvertor:
|
||||
stream = self._pdb.STREAM_TPI
|
||||
for type_index in stream.types:
|
||||
user_type = stream.types[type_index]
|
||||
if (user_type.leaf_type == "LF_ENUM" and not user_type.prop.fwdref):
|
||||
if user_type.leaf_type == "LF_ENUM" and not user_type.prop.fwdref:
|
||||
output.update(self._format_enum(user_type))
|
||||
return output
|
||||
|
||||
def _format_enum(self, user_enum):
|
||||
output = {
|
||||
user_enum.name: {
|
||||
'base': self.lookup_ctype(user_enum.utype),
|
||||
'size': self._determine_size(user_enum.utype),
|
||||
'constants': dict([(enum.name, enum.enum_value) for enum in user_enum.fieldlist.substructs])
|
||||
"base": self.lookup_ctype(user_enum.utype),
|
||||
"size": self._determine_size(user_enum.utype),
|
||||
"constants": dict(
|
||||
[
|
||||
(enum.name, enum.enum_value)
|
||||
for enum in user_enum.fieldlist.substructs
|
||||
]
|
||||
),
|
||||
}
|
||||
}
|
||||
return output
|
||||
@@ -195,14 +204,14 @@ class PDBConvertor:
|
||||
try:
|
||||
sects = self._pdb.STREAM_SECT_HDR_ORIG.sections
|
||||
omap = self._pdb.STREAM_OMAP_FROM_SRC
|
||||
except AttributeError as e:
|
||||
except AttributeError:
|
||||
# In this case there is no OMAP, so we use the given section
|
||||
# headers and use the identity function for omap.remap
|
||||
sects = self._pdb.STREAM_SECT_HDR.sections
|
||||
omap = None
|
||||
|
||||
for sym in self._pdb.STREAM_GSYM.globals:
|
||||
if not hasattr(sym, 'offset'):
|
||||
if not hasattr(sym, "offset"):
|
||||
continue
|
||||
try:
|
||||
virt_base = sects[sym.segment - 1].VirtualAddress
|
||||
@@ -223,9 +232,9 @@ class PDBConvertor:
|
||||
stream = self._pdb.STREAM_TPI
|
||||
for type_index in stream.types:
|
||||
user_type = stream.types[type_index]
|
||||
if (user_type.leaf_type == "LF_STRUCTURE" and not user_type.prop.fwdref):
|
||||
if user_type.leaf_type == "LF_STRUCTURE" and not user_type.prop.fwdref:
|
||||
output.update(self._format_usertype(user_type, "struct"))
|
||||
elif (user_type.leaf_type == "LF_UNION" and not user_type.prop.fwdref):
|
||||
elif user_type.leaf_type == "LF_UNION" and not user_type.prop.fwdref:
|
||||
output.update(self._format_usertype(user_type, "union"))
|
||||
return output
|
||||
|
||||
@@ -233,16 +242,22 @@ class PDBConvertor:
|
||||
"""Produces a single usertype"""
|
||||
fields: Dict[str, Dict[str, Any]] = {}
|
||||
[fields.update(self._format_field(s)) for s in usertype.fieldlist.substructs]
|
||||
return {usertype.name: {'fields': fields, 'kind': kind, 'size': usertype.size}}
|
||||
return {usertype.name: {"fields": fields, "kind": kind, "size": usertype.size}}
|
||||
|
||||
def _format_field(self, field) -> Dict[str, Dict[str, Any]]:
|
||||
return {field.name: {"offset": field.offset, "type": self._format_kind(field.index)}}
|
||||
return {
|
||||
field.name: {"offset": field.offset, "type": self._format_kind(field.index)}
|
||||
}
|
||||
|
||||
def _determine_size(self, field):
|
||||
output = None
|
||||
if isinstance(field, str):
|
||||
output = self.base_type_size[field]
|
||||
elif (field.leaf_type == "LF_STRUCTURE" or field.leaf_type == "LF_ARRAY" or field.leaf_type == "LF_UNION"):
|
||||
elif (
|
||||
field.leaf_type == "LF_STRUCTURE"
|
||||
or field.leaf_type == "LF_ARRAY"
|
||||
or field.leaf_type == "LF_UNION"
|
||||
):
|
||||
output = field.size
|
||||
elif field.leaf_type == "LF_POINTER":
|
||||
output = self.base_type_size[field.ptr_attr.type]
|
||||
@@ -256,6 +271,7 @@ class PDBConvertor:
|
||||
output = self._determine_size(field.index)
|
||||
if output is None:
|
||||
import pdb
|
||||
|
||||
pdb.set_trace()
|
||||
raise ValueError(f"Unknown size for field: {field.name}")
|
||||
return output
|
||||
@@ -267,36 +283,37 @@ class PDBConvertor:
|
||||
output = self.lookup_ctype_pointers(kind)
|
||||
except KeyError:
|
||||
try:
|
||||
output = {'kind': 'base', 'name': self.lookup_ctype(kind)}
|
||||
output = {"kind": "base", "name": self.lookup_ctype(kind)}
|
||||
except KeyError:
|
||||
output = {'kind': 'base', 'name': kind}
|
||||
elif kind.leaf_type == 'LF_MODIFIER':
|
||||
output = {"kind": "base", "name": kind}
|
||||
elif kind.leaf_type == "LF_MODIFIER":
|
||||
output = self._format_kind(kind.modified_type)
|
||||
elif kind.leaf_type == 'LF_STRUCTURE':
|
||||
output = {'kind': 'struct', 'name': kind.name}
|
||||
elif kind.leaf_type == 'LF_UNION':
|
||||
output = {'kind': 'union', 'name': kind.name}
|
||||
elif kind.leaf_type == 'LF_BITFIELD':
|
||||
elif kind.leaf_type == "LF_STRUCTURE":
|
||||
output = {"kind": "struct", "name": kind.name}
|
||||
elif kind.leaf_type == "LF_UNION":
|
||||
output = {"kind": "union", "name": kind.name}
|
||||
elif kind.leaf_type == "LF_BITFIELD":
|
||||
output = {
|
||||
'kind': 'bitfield',
|
||||
'type': self._format_kind(kind.base_type),
|
||||
'bit_length': kind.length,
|
||||
'bit_position': kind.position
|
||||
"kind": "bitfield",
|
||||
"type": self._format_kind(kind.base_type),
|
||||
"bit_length": kind.length,
|
||||
"bit_position": kind.position,
|
||||
}
|
||||
elif kind.leaf_type == 'LF_POINTER':
|
||||
output = {'kind': 'pointer', 'subtype': self._format_kind(kind.utype)}
|
||||
elif kind.leaf_type == 'LF_ARRAY':
|
||||
elif kind.leaf_type == "LF_POINTER":
|
||||
output = {"kind": "pointer", "subtype": self._format_kind(kind.utype)}
|
||||
elif kind.leaf_type == "LF_ARRAY":
|
||||
output = {
|
||||
'kind': 'array',
|
||||
'count': kind.size // self._determine_size(kind.element_type),
|
||||
'subtype': self._format_kind(kind.element_type)
|
||||
"kind": "array",
|
||||
"count": kind.size // self._determine_size(kind.element_type),
|
||||
"subtype": self._format_kind(kind.element_type),
|
||||
}
|
||||
elif kind.leaf_type == 'LF_ENUM':
|
||||
output = {'kind': 'enum', 'name': kind.name}
|
||||
elif kind.leaf_type == 'LF_PROCEDURE':
|
||||
output = {'kind': "function"}
|
||||
elif kind.leaf_type == "LF_ENUM":
|
||||
output = {"kind": "enum", "name": kind.name}
|
||||
elif kind.leaf_type == "LF_PROCEDURE":
|
||||
output = {"kind": "function"}
|
||||
else:
|
||||
import pdb
|
||||
|
||||
pdb.set_trace()
|
||||
return output
|
||||
|
||||
@@ -306,40 +323,70 @@ class PDBConvertor:
|
||||
if "64" in self._pdb.STREAM_DBI.machine:
|
||||
ptr_size = 8
|
||||
|
||||
output = {"pointer": {"endian": "little", "kind": "int", "signed": False, "size": ptr_size}}
|
||||
output = {
|
||||
"pointer": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": False,
|
||||
"size": ptr_size,
|
||||
}
|
||||
}
|
||||
for index in self._seen_ctypes:
|
||||
output[self.ctype[index]] = {
|
||||
"endian": "little",
|
||||
"kind": self.ctype_python_types.get(self.ctype[index], "int"),
|
||||
"signed": False if "_U" in index else True,
|
||||
"size": self.base_type_size[index]
|
||||
"size": self.base_type_size[index],
|
||||
}
|
||||
return output
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description = "Convertor for PDB files to Volatility 3 Intermediate Symbol Format")
|
||||
parser.add_argument("-o", "--output", metavar = "OUTPUT", help = "Filename for data output", required = True)
|
||||
file_group = parser.add_argument_group("file", description = "File-based conversion of PDB to ISF")
|
||||
file_group.add_argument("-f", "--file", metavar = "FILE", help = "PDB file to translate to ISF")
|
||||
data_group = parser.add_argument_group("data", description = "Convert based on a GUID and filename pattern")
|
||||
data_group.add_argument("-p", "--pattern", metavar = "PATTERN", help = "Filename pattern to recover PDB file")
|
||||
data_group.add_argument("-g",
|
||||
"--guid",
|
||||
metavar = "GUID",
|
||||
help = "GUID + Age string for the required PDB file",
|
||||
default = None)
|
||||
data_group.add_argument("-k",
|
||||
"--keep",
|
||||
action = "store_true",
|
||||
default = False,
|
||||
help = "Keep the downloaded PDB file")
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Convertor for PDB files to Volatility 3 Intermediate Symbol Format"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
metavar="OUTPUT",
|
||||
help="Filename for data output",
|
||||
required=True,
|
||||
)
|
||||
file_group = parser.add_argument_group(
|
||||
"file", description="File-based conversion of PDB to ISF"
|
||||
)
|
||||
file_group.add_argument(
|
||||
"-f", "--file", metavar="FILE", help="PDB file to translate to ISF"
|
||||
)
|
||||
data_group = parser.add_argument_group(
|
||||
"data", description="Convert based on a GUID and filename pattern"
|
||||
)
|
||||
data_group.add_argument(
|
||||
"-p",
|
||||
"--pattern",
|
||||
metavar="PATTERN",
|
||||
help="Filename pattern to recover PDB file",
|
||||
)
|
||||
data_group.add_argument(
|
||||
"-g",
|
||||
"--guid",
|
||||
metavar="GUID",
|
||||
help="GUID + Age string for the required PDB file",
|
||||
default=None,
|
||||
)
|
||||
data_group.add_argument(
|
||||
"-k",
|
||||
"--keep",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Keep the downloaded PDB file",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
delfile = False
|
||||
filename = None
|
||||
if args.guid is not None and args.pattern is not None:
|
||||
filename = PDBRetreiver().retreive_pdb(guid = args.guid, file_name = args.pattern)
|
||||
filename = PDBRetreiver().retreive_pdb(guid=args.guid, file_name=args.pattern)
|
||||
delfile = True
|
||||
elif args.file:
|
||||
filename = args.file
|
||||
@@ -352,7 +399,7 @@ if __name__ == '__main__':
|
||||
convertor = PDBConvertor(filename)
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(convertor.read_pdb(), f, indent = 2, sort_keys = True)
|
||||
json.dump(convertor.read_pdb(), f, indent=2, sort_keys=True)
|
||||
|
||||
if args.keep:
|
||||
print(f"Temporary PDB file: {filename}")
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
# TODO: Rather nasty hack, when volatility's actually installed this would be unnecessary
|
||||
sys.path += ".."
|
||||
|
||||
import logging
|
||||
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(levelname)-8s %(name)-12s: %(message)s')
|
||||
formatter = logging.Formatter("%(levelname)-8s %(name)-12s: %(message)s")
|
||||
console.setFormatter(formatter)
|
||||
|
||||
logger = logging.getLogger("")
|
||||
logger.addHandler(console)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
from volatility3 import schemas
|
||||
from volatility3 import schemas # noqa: E402
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser("Validates ")
|
||||
parser.add_argument("-s", "--schema", dest = "schema", default = None)
|
||||
parser.add_argument("filenames", metavar = "FILE", nargs = '+')
|
||||
parser.add_argument("-s", "--schema", dest="schema", default=None)
|
||||
parser.add_argument("filenames", metavar="FILE", nargs="+")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
schema = None
|
||||
if args.schema:
|
||||
with open(os.path.abspath(args.schema), 'r') as s:
|
||||
with open(os.path.abspath(args.schema)) as s:
|
||||
schema = json.load(s)
|
||||
|
||||
failures = []
|
||||
@@ -36,7 +35,7 @@ if __name__ == '__main__':
|
||||
try:
|
||||
if os.path.exists(filename):
|
||||
print(f"[?] Validating file: {filename}")
|
||||
with open(filename, 'r') as t:
|
||||
with open(filename) as t:
|
||||
test = json.load(t)
|
||||
|
||||
if args.schema:
|
||||
|
||||
@@ -9,7 +9,7 @@ import requests
|
||||
import rpmfile
|
||||
from debian import debfile
|
||||
|
||||
DWARF2JSON = './dwarf2json'
|
||||
DWARF2JSON = "./dwarf2json"
|
||||
|
||||
|
||||
class Downloader:
|
||||
@@ -17,7 +17,7 @@ class Downloader:
|
||||
def __init__(self, url_lists: List[List[str]]) -> None:
|
||||
self.url_lists = url_lists
|
||||
|
||||
def download_lists(self, keep = False):
|
||||
def download_lists(self, keep=False):
|
||||
for url_list in self.url_lists:
|
||||
print("Downloading files...")
|
||||
files_for_processing = self.download_list(url_list)
|
||||
@@ -35,43 +35,45 @@ class Downloader:
|
||||
with tempfile.NamedTemporaryFile() as archivedata:
|
||||
archivedata.write(data.content)
|
||||
archivedata.seek(0)
|
||||
if url.endswith('.rpm'):
|
||||
if url.endswith(".rpm"):
|
||||
processed_files[url] = self.process_rpm(archivedata)
|
||||
elif url.endswith('.deb'):
|
||||
elif url.endswith(".deb"):
|
||||
processed_files[url] = self.process_deb(archivedata)
|
||||
|
||||
return processed_files
|
||||
|
||||
def process_rpm(self, archivedata) -> Optional[str]:
|
||||
rpm = rpmfile.RPMFile(fileobj = archivedata)
|
||||
rpm = rpmfile.RPMFile(fileobj=archivedata)
|
||||
member = None
|
||||
extracted = None
|
||||
for member in rpm.getmembers():
|
||||
if 'vmlinux' in member.name or 'System.map' in member.name:
|
||||
if "vmlinux" in member.name or "System.map" in member.name:
|
||||
print(f" - Extracting {member.name}")
|
||||
extracted = rpm.extractfile(member)
|
||||
break
|
||||
if not member or not extracted:
|
||||
return None
|
||||
with tempfile.NamedTemporaryFile(delete = False,
|
||||
prefix = 'vmlinux' if 'vmlinux' in member.name else 'System.map') as output:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, prefix="vmlinux" if "vmlinux" in member.name else "System.map"
|
||||
) as output:
|
||||
print(f" - Writing to {output.name}")
|
||||
output.write(extracted.read())
|
||||
return output.name
|
||||
|
||||
def process_deb(self, archivedata) -> Optional[str]:
|
||||
deb = debfile.DebFile(fileobj = archivedata)
|
||||
deb = debfile.DebFile(fileobj=archivedata)
|
||||
member = None
|
||||
extracted = None
|
||||
for member in deb.data.tgz().getmembers():
|
||||
if member.name.endswith('vmlinux') or 'System.map' in member.name:
|
||||
if member.name.endswith("vmlinux") or "System.map" in member.name:
|
||||
print(f" - Extracting {member.name}")
|
||||
extracted = deb.data.get_file(member.name)
|
||||
break
|
||||
if not member or not extracted:
|
||||
return None
|
||||
with tempfile.NamedTemporaryFile(delete = False,
|
||||
prefix = 'vmlinux' if 'vmlinux' in member.name else 'System.map') as output:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, prefix="vmlinux" if "vmlinux" in member.name else "System.map"
|
||||
) as output:
|
||||
print(f" - Writing to {output.name}")
|
||||
output.write(extracted.read())
|
||||
return output.name
|
||||
@@ -83,43 +85,55 @@ class Downloader:
|
||||
if named_files[i] is None:
|
||||
print(f"FAILURE: None encountered for {i}")
|
||||
return
|
||||
args = [DWARF2JSON, 'linux']
|
||||
output_filename = 'unknown-kernel.json'
|
||||
args = [DWARF2JSON, "linux"]
|
||||
output_filename = "unknown-kernel.json"
|
||||
for named_file in named_files:
|
||||
prefix = '--system-map'
|
||||
if 'System' not in named_files[named_file]:
|
||||
prefix = '--elf'
|
||||
output_filename = './' + '-'.join((named_file.split('/')[-1]).split('-')[2:])[:-4] + '.json.xz'
|
||||
prefix = "--system-map"
|
||||
if "System" not in named_files[named_file]:
|
||||
prefix = "--elf"
|
||||
output_filename = (
|
||||
"./"
|
||||
+ "-".join((named_file.split("/")[-1]).split("-")[2:])[:-4]
|
||||
+ ".json.xz"
|
||||
)
|
||||
args += [prefix, named_files[named_file]]
|
||||
print(f" - Running {args}")
|
||||
proc = subprocess.run(args, capture_output = True)
|
||||
proc = subprocess.run(args, capture_output=True)
|
||||
|
||||
print(f" - Writing to {output_filename}")
|
||||
with lzma.open(output_filename, 'w') as f:
|
||||
with lzma.open(output_filename, "w") as f:
|
||||
f.write(proc.stdout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description = "Takes a list of URLs for Centos and downloads them")
|
||||
parser.add_argument("-f",
|
||||
"--file",
|
||||
dest = 'filename',
|
||||
metavar = "FILENAME",
|
||||
help = "Filename to be read",
|
||||
required = True)
|
||||
parser.add_argument("-d",
|
||||
"--dwarf2json",
|
||||
dest = 'dwarfpath',
|
||||
metavar = "PATH",
|
||||
default = DWARF2JSON,
|
||||
help = "Path to the dwarf2json binary",
|
||||
required = True)
|
||||
parser.add_argument("-k",
|
||||
"--keep",
|
||||
dest = 'keep',
|
||||
action = 'store_true',
|
||||
help = 'Keep extracted temporary files after completion',
|
||||
default = False)
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Takes a list of URLs for Centos and downloads them"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--file",
|
||||
dest="filename",
|
||||
metavar="FILENAME",
|
||||
help="Filename to be read",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--dwarf2json",
|
||||
dest="dwarfpath",
|
||||
metavar="PATH",
|
||||
default=DWARF2JSON,
|
||||
help="Path to the dwarf2json binary",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
"--keep",
|
||||
dest="keep",
|
||||
action="store_true",
|
||||
help="Keep extracted temporary files after completion",
|
||||
default=False,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
DWARF2JSON = args.dwarfpath
|
||||
@@ -132,4 +146,4 @@ if __name__ == '__main__':
|
||||
urls += [[lines[2 * i].strip(), lines[(2 * i) + 1].strip()]]
|
||||
|
||||
d = Downloader(urls)
|
||||
d.download_lists(keep = args.keep)
|
||||
d.download_lists(keep=args.keep)
|
||||
|
||||
+5
-7
@@ -19,6 +19,8 @@ import sys
|
||||
|
||||
import sphinx.ext.apidoc
|
||||
|
||||
from importlib.util import find_spec
|
||||
|
||||
|
||||
def setup(app):
|
||||
volatility_directory = os.path.abspath(
|
||||
@@ -124,7 +126,7 @@ def setup(app):
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
|
||||
from volatility3.framework import constants
|
||||
from volatility3.framework import constants # noqa: E402
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
@@ -147,13 +149,9 @@ extensions = [
|
||||
|
||||
autosectionlabel_prefix_document = True
|
||||
|
||||
try:
|
||||
import sphinx_autodoc_typehints
|
||||
|
||||
if find_spec("sphinx_autodoc_typehints") is not None:
|
||||
extensions.append("sphinx_autodoc_typehints")
|
||||
except ImportError:
|
||||
# If the autodoc typehints extension isn't available, carry on regardless
|
||||
pass
|
||||
# If the autodoc typehints extension isn't available, carry on regardless
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
# templates_path = ['tools/templates']
|
||||
|
||||
+18
-8
@@ -23,7 +23,7 @@ Alignment
|
||||
.. _Array:
|
||||
|
||||
Array
|
||||
This represents a list of items, which can be access by an index, which is zero-based (meaning the first
|
||||
This represents a list of items, which can be accessed by an index, which is zero-based (meaning the first
|
||||
element has index 0). Items in arrays are almost always the same size (it is not a generic list, as in python)
|
||||
even if they are :ref:`pointers<pointer>` to different sized objects.
|
||||
|
||||
@@ -43,7 +43,14 @@ Dereference
|
||||
.. _Domain:
|
||||
|
||||
Domain
|
||||
This the grouping for input values for a mapping or mathematical function.
|
||||
The set of input values for a mapping or mathematical function.
|
||||
|
||||
I
|
||||
-
|
||||
.. _Intermediate Symbol File (ISF):
|
||||
|
||||
Intermediate Symbol File (ISF)
|
||||
They contain kernel structures and specific offsets formatted as JSON. For macOS and Linux analysis, the kernel needs to be added as an ISF file to the volatility 3 symbols directory. For Windows, the required ISF file can often be generated from PDB files automatically downloaded from Microsoft servers, and therefore does not require manual intervention.
|
||||
|
||||
M
|
||||
-
|
||||
@@ -54,9 +61,7 @@ Map, mapping
|
||||
of the :ref:`Range<range>`). Mappings can be seen as a mathematical function, and therefore volatility 3
|
||||
attempts to use mathematical functional notation where possible. Within volatility a mapping is most often
|
||||
used to refer to the function for translating addresses from a higher layer (domain) to a lower layer (range).
|
||||
For further information, please see
|
||||
`Function (mathematics) in wikipedia https://en.wikipedia.org/wiki/Function_(mathematics)`
|
||||
|
||||
For further information, please see `Function (mathematics) in Wikipedia<https://en.wikipedia.org/wiki/Function_(mathematics)>_`.
|
||||
|
||||
.. _Member:
|
||||
|
||||
@@ -69,7 +74,7 @@ O
|
||||
.. _Object:
|
||||
|
||||
Object
|
||||
This has a specific meaning within computer programming (as in Object Oriented Programming), but within the world
|
||||
This has a specific meaning within computer programming (as in object-oriented programming), but within the world
|
||||
of Volatility it is used to refer to a type that has been associated with a chunk of data, or a specific instance
|
||||
of a type. See also :ref:`Type<type>`.
|
||||
|
||||
@@ -116,6 +121,11 @@ Page Table
|
||||
possible to use them as a way to map a particular address within a (potentially larger, but sparsely populated)
|
||||
virtual space to a concrete (and usually contiguous) physical space, through the process of :ref:`mapping<map>`.
|
||||
|
||||
.. _Plugin:
|
||||
|
||||
Plugin
|
||||
Plugins are the "functions" of the volatility framework. They carry out algorithms on data stored in layers using objects constructed from symbols. Broadly, plugins take in a number of TranslationLayers (the data, which is a representation of part of an image, in a specified type described by templates) and outputs a TreeGrid.
|
||||
|
||||
.. _Pointer:
|
||||
|
||||
Pointer
|
||||
@@ -145,9 +155,9 @@ Struct, Structure
|
||||
|
||||
Symbol
|
||||
This is used in many different contexts, as a short term for many things. Within Volatility, a symbol is a
|
||||
construct that usually encompasses a specific type :ref:`type<Type>` at a specific :ref:`offset<Offset>`,
|
||||
construct that usually encompasses a specific :ref:`type<Type>` at a specific :ref:`offset<Offset>`,
|
||||
representing a particular instance of that type within the memory of a compiled and running program. An example
|
||||
would be the location in memory of a list of active tcp endpoints maintained by the networking stack
|
||||
would be the location in memory of a list of active TCP endpoints maintained by the networking stack
|
||||
within an operating system.
|
||||
|
||||
T
|
||||
|
||||
@@ -41,24 +41,36 @@ to be able to run properly. Any that are defined as optional need not necessari
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
return [requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel',
|
||||
architectures = ["Intel32", "Intel64"]),
|
||||
requirements.ListRequirement(name = 'pid',
|
||||
element_type = int,
|
||||
description = "Process IDs to include (all other processes are excluded)",
|
||||
optional = True),
|
||||
requirements.PluginRequirement(name = 'pslist',
|
||||
plugin = pslist.PsList,
|
||||
version = (2, 0, 0))]
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name = 'kernel',
|
||||
description = 'Windows kernel',
|
||||
architectures = ["Intel32", "Intel64"]
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name = 'pid',
|
||||
element_type = int,
|
||||
description = "Process IDs to include (all other processes are excluded)",
|
||||
optional = True
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name = 'pslist',
|
||||
plugin = pslist.PsList,
|
||||
version = (2, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
This is a classmethod, because it is called before the specific plugin object has been instantiated (in order to know how
|
||||
This is a classmethod, so it can be called before the specific plugin object has been instantiated (in order to know how
|
||||
to instantiate the plugin). At the moment these requirements are fairly straightforward:
|
||||
|
||||
::
|
||||
|
||||
requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel',
|
||||
architectures = ["Intel32", "Intel64"]),
|
||||
requirements.ModuleRequirement(
|
||||
name = 'kernel',
|
||||
description = 'Windows kernel',
|
||||
architectures = ["Intel32", "Intel64"]
|
||||
),
|
||||
|
||||
This requirement specifies the need for a particular submodule. Each module requires a
|
||||
:py:class:`TranslationLayer <volatility3.framework.interfaces.layers.TranslationLayerInterface>` and a
|
||||
@@ -85,9 +97,11 @@ not be requested directly from the user.
|
||||
|
||||
::
|
||||
|
||||
requirements.TranslationLayerRequirement(name = 'primary',
|
||||
description = 'Memory layer for the kernel',
|
||||
architectures = ["Intel32", "Intel64"]),
|
||||
requirements.TranslationLayerRequirement(
|
||||
name = 'primary',
|
||||
description = 'Memory layer for the kernel',
|
||||
architectures = ["Intel32", "Intel64"]
|
||||
),
|
||||
|
||||
This requirement indicates that the plugin will operate on a single
|
||||
:py:class:`TranslationLayer <volatility3.framework.interfaces.layers.TranslationLayerInterface>`. The name of the
|
||||
@@ -110,8 +124,10 @@ not be requested directly from the user.
|
||||
|
||||
::
|
||||
|
||||
requirements.SymbolTableRequirement(name = "nt_symbols",
|
||||
description = "Windows kernel symbols"),
|
||||
requirements.SymbolTableRequirement(
|
||||
name = "nt_symbols",
|
||||
description = "Windows kernel symbols"
|
||||
),
|
||||
|
||||
This requirement specifies the need for a particular
|
||||
:py:class:`SymbolTable <volatility3.framework.interfaces.symbols.SymbolTableInterface>`
|
||||
@@ -127,10 +143,12 @@ not be requested directly from the user.
|
||||
|
||||
::
|
||||
|
||||
requirements.ListRequirement(name = 'pid',
|
||||
description = 'Filter on specific process IDs',
|
||||
element_type = int,
|
||||
optional = True),
|
||||
requirements.ListRequirement(
|
||||
name = 'pid',
|
||||
description = 'Filter on specific process IDs',
|
||||
element_type = int,
|
||||
optional = True
|
||||
),
|
||||
|
||||
The next requirement is a List Requirement, populated by integers. The description will be presented to the user to
|
||||
describe what the value represents. The optional flag indicates that the plugin can function without the ``pid`` value
|
||||
@@ -138,9 +156,11 @@ being defined within the configuration tree at all.
|
||||
|
||||
::
|
||||
|
||||
requirements.PluginRequirement(name = 'pslist',
|
||||
plugin = pslist.PsList,
|
||||
version = (2, 0, 0))]
|
||||
requirements.PluginRequirement(
|
||||
name = 'pslist',
|
||||
plugin = pslist.PsList,
|
||||
version = (2, 0, 0)
|
||||
)
|
||||
|
||||
This requirement indicates that the plugin will make use of another plugin's code, and specifies the version requirements
|
||||
on that plugin. The version is specified in terms of Semantic Versioning meaning that, to be compatible, the major
|
||||
@@ -180,16 +200,24 @@ that will be output as part of the :py:class:`~volatility3.framework.interfaces.
|
||||
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),
|
||||
("Size", format_hints.Hex),
|
||||
("Name", str),
|
||||
("Path", str)],
|
||||
self._generator(pslist.PsList.list_processes(self.context,
|
||||
kernel.layer_name,
|
||||
kernel.symbol_table_name,
|
||||
filter_func = filter_func)))
|
||||
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(
|
||||
self.context,
|
||||
kernel.layer_name,
|
||||
kernel.symbol_table_name,
|
||||
filter_func = filter_func
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
In this instance, the plugin constructs a filter (using the PsList plugin's *classmethod* for creating filters).
|
||||
It checks the plugin's configuration for the ``pid`` value, and passes it in as a list if it finds it, or None if
|
||||
@@ -281,5 +309,3 @@ such as ``<table>!_UNICODE``) and the parameters to that type.
|
||||
Since the cast value must populate a string typed column, it had to be a Python string (such as being cast to the native
|
||||
type string) and could not have been a special Structure such as ``_UNICODE``. For the format hint columns, the format
|
||||
hint type must be used to ensure the error checking does not fail.
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Using Volatility 3 as a Library
|
||||
|
||||
This portion of the documentation discusses how to access the Volatility 3 framework from an external application.
|
||||
|
||||
The general process of using volatility as a library is to as follows:
|
||||
The general process of using volatility as a library is as follows:
|
||||
|
||||
1. :ref:`create_context`
|
||||
2. (Optional) :ref:`available_plugins`
|
||||
@@ -21,7 +21,7 @@ Creating a context
|
||||
First we make sure the volatility framework works the way we expect it (and is the version we expect). The
|
||||
versioning used is semantic versioning, meaning any version with the same major number and a higher or equal
|
||||
minor number will satisfy the requirement. An example is below since the CLI doesn't need any of the features
|
||||
from versions 1.1 or 1.2:
|
||||
from version 1.1 or later:
|
||||
|
||||
::
|
||||
|
||||
@@ -86,7 +86,7 @@ List requirements are a list of simple types (integers, booleans, floats and str
|
||||
options, multiple requirements needs all their subrequirements fulfilled and the other types require the names of
|
||||
valid translation layers or symbol tables within the context, respectively. Luckily, each of these requirements can
|
||||
tell you whether they've been fulfilled or not later in the process. For now, they can be used to ask the user to
|
||||
fill in any parameters they made need to. Some requirements are optional, others are not.
|
||||
fill in any parameters they may need to. Some requirements are optional, others are not.
|
||||
|
||||
The plugin is essentially a multiple requirement. It should also be noted that automagic classes can have requirements
|
||||
(as can translation layers).
|
||||
@@ -100,7 +100,7 @@ Once you know what requirements the plugin will need, you can populate them with
|
||||
The configuration is essentially a hierarchical tree of values, much like the windows registry.
|
||||
Each plugin is instantiated at a particular branch within the hierarchy and will look for its configuration
|
||||
options under that hierarchy (if it holds any configurable items, it will likely instantiate those at a point
|
||||
underneaths its own branch). To set the hierarchy, you'll need to know where the configurables will be constructed.
|
||||
underneath its own branch). To set the hierarchy, you'll need to know where the configurables will be constructed.
|
||||
|
||||
For this example, we'll assume plugins' base_config_path is set as `plugins`, and that automagics are configured under
|
||||
the `automagic` tree. We'll see later how to ensure this matches up with the plugins and automagic when they're
|
||||
@@ -139,7 +139,7 @@ A suitable list of automagics for a particular plugin (based on operating system
|
||||
|
||||
This will take the plugin module, extract the operating system (first level of the hierarchy) and then return just
|
||||
the automagics which apply to the operating system. Each automagic can exclude itself from being used for specific
|
||||
operating systems, so that an automagic designed for linux is not used for windows or mac plugins.
|
||||
operating systems, such that an automagic designed for linux is not used for windows or mac plugins.
|
||||
|
||||
These automagics can then be run by providing the list, the context, the plugin to be run, the hierarchy name that
|
||||
the plugin will be constructed on ('plugins' by default) and a progress_callback. This is a callable which takes
|
||||
@@ -157,8 +157,8 @@ Any exceptions that occur during the execution of the automagic will be returned
|
||||
Run the plugin
|
||||
--------------
|
||||
|
||||
Firstly, we should check whether the plugin will be able to run (ie, whether the configuration options it needs
|
||||
have been successfully set). We do this as follow (where plugin_config_path is the base_config_path (which defaults
|
||||
Firstly, we should check whether the plugin will be able to run (i.e., whether the configuration options it needs
|
||||
have been successfully set). We do this as follows, where plugin_config_path is the base_config_path (which defaults
|
||||
to `plugins` and then the name of the class itself):
|
||||
|
||||
::
|
||||
@@ -166,7 +166,7 @@ to `plugins` and then the name of the class itself):
|
||||
unsatisfied = plugin.unsatisfied(context, plugin_config_path)
|
||||
|
||||
If unsatisfied is an empty list, then the plugin has been given everything it requires. If not, it will be a
|
||||
Dictionary of the hierarchy paths and their associated requirements that weren't satisfied.
|
||||
dict of the hierarchy paths and their associated requirements that weren't satisfied.
|
||||
|
||||
The plugin can then be instantiated with the context (containing the plugin's configuration) and the path that the
|
||||
plugin can find its configuration at. This configuration path only needs to be a unique value to identify where the
|
||||
|
||||
+57
-4
@@ -36,7 +36,7 @@ operating system mode for volshell, and the current layer available for use.
|
||||
|
||||
(primary) >>>
|
||||
|
||||
Volshell itself in essentially a plugin, but an interactive one. As such, most values are accessed through `self`
|
||||
Volshell itself is essentially a plugin, but an interactive one. As such, most values are accessed through `self`
|
||||
although there is also a `context` object whenever a context must be provided.
|
||||
|
||||
The prompt for the tool will indicate the name of the current layer (which can be accessed as `self.current_layer`
|
||||
@@ -92,7 +92,7 @@ It can also be provided with an object and will interpret the data for each in t
|
||||
0x2e8 : UniqueProcessId symbol_table_name1!pointer 4
|
||||
...
|
||||
|
||||
These values can be accessed directory as attributes
|
||||
These values can be accessed directly as attributes
|
||||
|
||||
::
|
||||
|
||||
@@ -180,15 +180,68 @@ used:
|
||||
|
||||
layer = cc(mynewlayer.MyNewLayer, on_top_of = 'primary', other_parameter = 'important')
|
||||
with open('output.dmp', 'wb') as fp:
|
||||
for i in range(0, 1073741824, 0x1000):
|
||||
for i in range(0, 0x4000000, 0x1000):
|
||||
data = layer.read(i, 0x1000, pad = True)
|
||||
fp.write(data)
|
||||
|
||||
As this demonstrates, all of the python is accessible, as are the volshell built in functions (such as `cc` which
|
||||
creates a constructable, like a layer or a symbol table).
|
||||
|
||||
User Convenience
|
||||
----------------
|
||||
|
||||
There are functions available that make often-done tasks easier, and generally provide a shell-like experience. These can be listed using `help()` which, as already mentioned, is advertised when volshell starts.
|
||||
|
||||
Loading files
|
||||
-------------
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Files can be loaded as physical layers using the `load_file` or `lf` command, which takes a filename or a URI. This will be added
|
||||
to `context.layers` and can be accessed by the name returned by `lf`.
|
||||
|
||||
Regex
|
||||
^^^^^
|
||||
|
||||
It is easy to scan for some bytes or a pattern using `regex_scan` or `rx`.
|
||||
|
||||
::
|
||||
|
||||
(layer_name) >>> rx(rb"(Linux version|Darwin Kernel Version) [0-9]+\.[0-9]+\.[0-9]+")
|
||||
0x880001400070 4c 69 6e 75 78 20 76 65 72 73 69 6f 6e 20 33 2e Linux.version.3.
|
||||
0x880001400080 32 2e 30 2d 34 2d 61 6d 64 36 34 20 28 64 65 62 2.0-4-amd64.(deb
|
||||
0x880001400090 69 61 6e 2d 6b 65 72 6e 65 6c 40 6c 69 73 74 73 ian-kernel@lists
|
||||
0x8800014000a0 2e 64 65 62 69 61 6e 2e 6f 72 67 29 20 28 67 63 .debian.org).(gc
|
||||
0x8800014000b0 63 20 76 65 72 73 69 6f 6e 20 34 2e 36 2e 33 20 c.version.4.6.3.
|
||||
0x8800014000c0 28 44 65 62 69 61 6e 20 34 2e 36 2e 33 2d 31 34 (Debian.4.6.3-14
|
||||
0x8800014000d0 29 20 29 20 23 31 20 53 4d 50 20 44 65 62 69 61 ).).#1.SMP.Debia
|
||||
0x8800014000e0 6e 20 33 2e 32 2e 35 37 2d 33 2b 64 65 62 37 75 n.3.2.57-3+deb7u
|
||||
|
||||
0x880001769027 4c 69 6e 75 78 20 76 65 72 73 69 6f 6e 20 33 2e Linux.version.3.
|
||||
0x880001769037 32 2e 30 2d 34 2d 61 6d 64 36 34 20 28 64 65 62 2.0-4-amd64.(deb
|
||||
0x880001769047 69 61 6e 2d 6b 65 72 6e 65 6c 40 6c 69 73 74 73 ian-kernel@lists
|
||||
0x880001769057 2e 64 65 62 69 61 6e 2e 6f 72 67 29 20 28 67 63 .debian.org).(gc
|
||||
0x880001769067 63 20 76 65 72 73 69 6f 6e 20 34 2e 36 2e 33 20 c.version.4.6.3.
|
||||
0x880001769077 28 44 65 62 69 61 6e 20 34 2e 36 2e 33 2d 31 34 (Debian.4.6.3-14
|
||||
0x880001769087 29 20 29 20 23 31 20 53 4d 50 20 44 65 62 69 61 ).).#1.SMP.Debia
|
||||
0x880001769097 6e 20 33 2e 32 2e 35 37 2d 33 2b 64 65 62 37 75 n.3.2.57-3+deb7u
|
||||
|
||||
0xffff81400070 4c 69 6e 75 78 20 76 65 72 73 69 6f 6e 20 33 2e Linux.version.3.
|
||||
0xffff81400080 32 2e 30 2d 34 2d 61 6d 64 36 34 20 28 64 65 62 2.0-4-amd64.(deb
|
||||
0xffff81400090 69 61 6e 2d 6b 65 72 6e 65 6c 40 6c 69 73 74 73 ian-kernel@lists
|
||||
0xffff814000a0 2e 64 65 62 69 61 6e 2e 6f 72 67 29 20 28 67 63 .debian.org).(gc
|
||||
0xffff814000b0 63 20 76 65 72 73 69 6f 6e 20 34 2e 36 2e 33 20 c.version.4.6.3.
|
||||
0xffff814000c0 28 44 65 62 69 61 6e 20 34 2e 36 2e 33 2d 31 34 (Debian.4.6.3-14
|
||||
0xffff814000d0 29 20 29 20 23 31 20 53 4d 50 20 44 65 62 69 61 ).).#1.SMP.Debia
|
||||
0xffff814000e0 6e 20 33 2e 32 2e 35 37 2d 33 2b 64 65 62 37 75 n.3.2.57-3+deb7u
|
||||
|
||||
0xffff81769027 4c 69 6e 75 78 20 76 65 72 73 69 6f 6e 20 33 2e Linux.version.3.
|
||||
0xffff81769037 32 2e 30 2d 34 2d 61 6d 64 36 34 20 28 64 65 62 2.0-4-amd64.(deb
|
||||
0xffff81769047 69 61 6e 2d 6b 65 72 6e 65 6c 40 6c 69 73 74 73 ian-kernel@lists
|
||||
0xffff81769057 2e 64 65 62 69 61 6e 2e 6f 72 67 29 20 28 67 63 .debian.org).(gc
|
||||
0xffff81769067 63 20 76 65 72 73 69 6f 6e 20 34 2e 36 2e 33 20 c.version.4.6.3.
|
||||
0xffff81769077 28 44 65 62 69 61 6e 20 34 2e 36 2e 33 2d 31 34 (Debian.4.6.3-14
|
||||
0xffff81769087 29 20 29 20 23 31 20 53 4d 50 20 44 65 62 69 61 ).).#1.SMP.Debia
|
||||
0xffff81769097 6e 20 33 2e 32 2e 35 37 2d 33 2b 64 65 62 37 75 n.3.2.57-3+deb7u
|
||||
|
||||
An optional size can be given for the displayed results as with the other fuctions (db, dw, dd, dq, etc).
|
||||
|
||||
You can, of course, specify a different layer name as well.
|
||||
|
||||
+22
-2
@@ -20,6 +20,10 @@ full = [
|
||||
"capstone>=5.0.3,<6",
|
||||
"pycryptodome>=3.21.0,<4",
|
||||
"leechcorepyc>=2.19.2,<3; sys_platform != 'darwin'",
|
||||
# https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst
|
||||
# 10.0.0 dropped support for Python3.7
|
||||
# 11.0.0 dropped support for Python3.8, which is still supported by Volatility3
|
||||
"pillow>=10.0.0,<11.0.0",
|
||||
]
|
||||
|
||||
cloud = [
|
||||
@@ -32,6 +36,7 @@ dev = [
|
||||
"jsonschema>=4.23.0,<5",
|
||||
"pyinstaller>=6.11.0,<7",
|
||||
"pyinstaller-hooks-contrib>=2024.9",
|
||||
"types-jsonschema>=4.23.0,<5",
|
||||
]
|
||||
|
||||
test = [
|
||||
@@ -68,8 +73,23 @@ include = ["volatility3*"]
|
||||
mypy_path = "./stubs"
|
||||
show_traceback = true
|
||||
|
||||
[tool.mypy.overrides]
|
||||
ignore_missing_imports = true
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py38"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"F", # pyflakes
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"G", # flake8-logging-format
|
||||
"PIE", # flake8-pie
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"E501", # ignore due to conflict with formatter
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=68"]
|
||||
|
||||
@@ -2,9 +2,11 @@ import sys
|
||||
import struct
|
||||
import traceback
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, "../../volatility3")
|
||||
from volatility3.plugins.windows import scheduled_tasks
|
||||
|
||||
|
||||
class TestActionsDecoding(unittest.TestCase):
|
||||
def test_decode_exe_action(self):
|
||||
# fmt: off
|
||||
@@ -84,8 +86,7 @@ class TestActionsDecoding(unittest.TestCase):
|
||||
self.assertEqual(actions[0].action_type, scheduled_tasks.ActionType.Exe)
|
||||
except Exception:
|
||||
self.fail(
|
||||
"ActionDecoder.decode should not raise exception:\n%s"
|
||||
% traceback.format_exc()
|
||||
f"ActionDecoder.decode should not raise exception:\n{traceback.format_exc()}"
|
||||
)
|
||||
|
||||
|
||||
|
||||
+231
-50
@@ -39,7 +39,9 @@ def runvol(args, volatility, python):
|
||||
return p.returncode, stdout, stderr
|
||||
|
||||
|
||||
def runvol_plugin(plugin, img, volatility, python, pluginargs=[], globalargs=[]):
|
||||
def runvol_plugin(plugin, img, volatility, python, pluginargs=None, globalargs=None):
|
||||
pluginargs = pluginargs or []
|
||||
globalargs = globalargs or []
|
||||
args = (
|
||||
globalargs
|
||||
+ [
|
||||
@@ -54,13 +56,68 @@ def runvol_plugin(plugin, img, volatility, python, pluginargs=[], globalargs=[])
|
||||
return runvol(args, volatility, python)
|
||||
|
||||
|
||||
def runvolshell(img, volshell, python, volshellargs=None, globalargs=None):
|
||||
volshellargs = volshellargs or []
|
||||
globalargs = globalargs or []
|
||||
args = (
|
||||
globalargs
|
||||
+ [
|
||||
"--single-location",
|
||||
img,
|
||||
"-q",
|
||||
]
|
||||
+ volshellargs
|
||||
)
|
||||
|
||||
return runvol(args, volshell, python)
|
||||
|
||||
|
||||
#
|
||||
# TESTS
|
||||
#
|
||||
|
||||
|
||||
def basic_volshell_test(image, volatility, python, globalargs):
|
||||
# Basic VolShell test to verify requirements and ensure VolShell runs without crashing
|
||||
|
||||
volshell_commands = [
|
||||
"print(ps())",
|
||||
"exit()",
|
||||
]
|
||||
|
||||
# FIXME: When the minimum Python version includes 3.12, replace the following with:
|
||||
# with tempfile.NamedTemporaryFile(delete_on_close=False) as fd: ...
|
||||
fd, filename = tempfile.mkstemp(suffix=".txt")
|
||||
try:
|
||||
volshell_script = "\n".join(volshell_commands)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(volshell_script)
|
||||
|
||||
rc, out, _err = runvolshell(
|
||||
img=image,
|
||||
volshell=volatility,
|
||||
python=python,
|
||||
volshellargs=["--script", filename],
|
||||
globalargs=globalargs,
|
||||
)
|
||||
finally:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(filename)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# WINDOWS
|
||||
|
||||
|
||||
def test_windows_volshell(image, volatility, python):
|
||||
out = basic_volshell_test(image, volatility, python, globalargs=["-w"])
|
||||
assert out.count(b"<EPROCESS") > 40
|
||||
|
||||
|
||||
def test_windows_pslist(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("windows.pslist.PsList", image, volatility, python)
|
||||
out = out.lower()
|
||||
@@ -332,86 +389,91 @@ def test_windows_vadyarascan_yara_string(image, volatility, python):
|
||||
# LINUX
|
||||
|
||||
|
||||
def test_linux_volshell(image, volatility, python):
|
||||
out = basic_volshell_test(image, volatility, python, globalargs=["-l"])
|
||||
assert out.count(b"<task_struct") > 100
|
||||
|
||||
|
||||
def test_linux_pslist(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.pslist.PsList", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
|
||||
assert out.find(b"watchdog") != -1
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_check_idt(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.check_idt.Check_idt", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"__kernel__") >= 10
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_check_syscall(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.check_syscall.Check_syscall", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.find(b"sys_close") != -1
|
||||
assert out.find(b"sys_open") != -1
|
||||
assert out.count(b"\n") > 100
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_lsmod(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.lsmod.Lsmod", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
def test_linux_lsof(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.lsof.Lsof", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"socket:") >= 10
|
||||
assert out.count(b"\n") > 35
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_proc_maps(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.proc.Maps", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"anonymous mapping") >= 10
|
||||
assert out.count(b"\n") > 100
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_tty_check(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.tty_check.tty_check", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.find(b"__kernel__") != -1
|
||||
assert out.count(b"\n") >= 5
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_sockstat(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.sockstat.Sockstat", image, volatility, python)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"AF_UNIX") >= 354
|
||||
assert out.count(b"AF_BLUETOOTH") >= 5
|
||||
assert out.count(b"AF_INET") >= 32
|
||||
assert out.count(b"AF_INET6") >= 20
|
||||
assert out.count(b"AF_PACKET") >= 1
|
||||
assert out.count(b"AF_NETLINK") >= 43
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_library_list(image, volatility, python):
|
||||
@@ -423,49 +485,48 @@ def test_linux_library_list(image, volatility, python):
|
||||
pluginargs=["--pids", "2363"],
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert re.search(
|
||||
rb"NetworkManager\s2363\s0x7f52cdda0000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
|
||||
out,
|
||||
)
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_pstree(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.pstree.PsTree", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_pidhashtable(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.pidhashtable.PIDHashTable", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_bash(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.bash.Bash", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
def test_linux_boottime(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.boottime.Boottime", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"utc") >= 1
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"utc") >= 1
|
||||
|
||||
|
||||
def test_linux_capabilities(image, volatility, python):
|
||||
@@ -482,36 +543,33 @@ def test_linux_capabilities(image, volatility, python):
|
||||
# However, we can still check that the plugin requirements are met.
|
||||
return None
|
||||
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
def test_linux_check_creds(image, volatility, python):
|
||||
rc, _out, _err = runvol_plugin(
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.check_creds.Check_creds", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no processes sharing credentials.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
def test_linux_elfs(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.elfs.Elfs", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
def test_linux_envars(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.envars.Envars", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
def test_linux_kthreads(image, volatility, python):
|
||||
@@ -528,44 +586,42 @@ def test_linux_kthreads(image, volatility, python):
|
||||
# However, we can still check that the plugin requirements are met.
|
||||
return None
|
||||
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
def test_linux_malfind(image, volatility, python):
|
||||
rc, _out, _err = runvol_plugin("linux.malfind.Malfind", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("linux.malfind.Malfind", image, volatility, python)
|
||||
|
||||
# linux-sample-1.bin has no process memory ranges with potential injected code.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
def test_linux_mountinfo(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.mountinfo.MountInfo", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
def test_linux_psaux(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.psaux.PsAux", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 50
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 50
|
||||
|
||||
|
||||
def test_linux_ptrace(image, volatility, python):
|
||||
rc, _out, _err = runvol_plugin("linux.ptrace.Ptrace", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("linux.ptrace.Ptrace", image, volatility, python)
|
||||
|
||||
# linux-sample-1.bin has no processes being ptreaced.
|
||||
# linux-sample-1.bin has no processes being ptraced.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
def test_linux_vmaregexscan(image, volatility, python):
|
||||
@@ -576,10 +632,9 @@ def test_linux_vmaregexscan(image, volatility, python):
|
||||
python,
|
||||
pluginargs=["--pid", "1", "--pattern", "\\x7fELF"],
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
def test_linux_vmayarascan_yara_rule(image, volatility, python):
|
||||
@@ -613,9 +668,8 @@ def test_linux_vmayarascan_yara_rule(image, volatility, python):
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(filename)
|
||||
|
||||
out = out.lower()
|
||||
assert out.count(b"\n") > 4
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 4
|
||||
|
||||
|
||||
def test_linux_vmayarascan_yara_string(image, volatility, python):
|
||||
@@ -626,10 +680,9 @@ def test_linux_vmayarascan_yara_string(image, volatility, python):
|
||||
python,
|
||||
pluginargs=["--pid", "1", "--yara-string", "ELF"],
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
def test_linux_page_cache_files(image, volatility, python):
|
||||
@@ -640,8 +693,8 @@ def test_linux_page_cache_files(image, volatility, python):
|
||||
python,
|
||||
pluginargs=["--find", "/etc/passwd"],
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 4
|
||||
|
||||
# inode_num inode_addr ... file_path
|
||||
@@ -649,12 +702,140 @@ def test_linux_page_cache_files(image, volatility, python):
|
||||
rb"146829\s0x88001ab5c270.*?/etc/passwd",
|
||||
out,
|
||||
)
|
||||
|
||||
|
||||
def test_linux_page_cache_inodepages(image, volatility, python):
|
||||
|
||||
inode_address = hex(0x88001AB5C270)
|
||||
inode_dump_filename = f"inode_{inode_address}.dmp"
|
||||
try:
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.pagecache.InodePages",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=["--inode", inode_address, "--dump"],
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 4
|
||||
|
||||
# PageVAddr PagePAddr MappingAddr .. DumpSafe
|
||||
assert re.search(
|
||||
rb"0xea000054c5f8\s0x18389000\s0x88001ab5c3b0.*?True",
|
||||
out,
|
||||
)
|
||||
assert os.path.exists(inode_dump_filename)
|
||||
with open(inode_dump_filename, "rb") as fp:
|
||||
inode_contents = fp.read()
|
||||
assert inode_contents.count(b"\n") > 30
|
||||
assert inode_contents.count(b"root:x:0:0:root:/root:/bin/bash") > 0
|
||||
finally:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(inode_dump_filename)
|
||||
|
||||
|
||||
def test_linux_check_afinfo(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.check_afinfo.Check_afinfo", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no suspicious results.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
def test_linux_check_modules(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.check_modules.Check_modules", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no suspicious results.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
def test_linux_ebpf_progs(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"linux.ebpf.EBPF",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
globalargs=["-vvv"],
|
||||
)
|
||||
|
||||
if rc != 0 and err.count(b"Unsupported kernel") > 0:
|
||||
# The linux-sample-1.bin kernel implementation isn't supported.
|
||||
# However, we can still check that the plugin requirements are met.
|
||||
return None
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 4
|
||||
|
||||
|
||||
def test_linux_iomem(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.iomem.IOMem", image, volatility, python)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 100
|
||||
|
||||
|
||||
def test_linux_keyboard_notifiers(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.keyboard_notifiers.Keyboard_notifiers", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no suspicious results for this plugin.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
def test_linux_kmesg(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.kmsg.Kmsg", image, volatility, python)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 100
|
||||
|
||||
|
||||
def test_linux_netfilter(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.netfilter.Netfilter", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no suspicious results for this plugin.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
def test_linux_psscan(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.psscan.PsScan", image, volatility, python)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 100
|
||||
|
||||
|
||||
def test_linux_hidden_modules(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin(
|
||||
"linux.hidden_modules.Hidden_modules", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no hidden modules.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
# MAC
|
||||
|
||||
|
||||
def test_mac_volshell(image, volatility, python):
|
||||
basic_volshell_test(image, volatility, python, globalargs=["-m"])
|
||||
|
||||
|
||||
def test_mac_pslist(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("mac.pslist.PsList", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
+11
-19
@@ -19,7 +19,7 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
from typing import Any, Dict, List, Tuple, Type, Union
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
||||
from urllib import parse, request
|
||||
|
||||
try:
|
||||
@@ -57,14 +57,14 @@ formatter = logging.Formatter("%(levelname)-8s %(name)-12s: %(message)s")
|
||||
console.setFormatter(formatter)
|
||||
|
||||
|
||||
class PrintedProgress(object):
|
||||
class PrintedProgress:
|
||||
"""A progress handler that prints the progress value and the description
|
||||
onto the command line."""
|
||||
|
||||
def __init__(self):
|
||||
self._max_message_len = 0
|
||||
|
||||
def __call__(self, progress: Union[int, float], description: str = None):
|
||||
def __call__(self, progress: Union[int, float], description: Optional[str] = None):
|
||||
"""A simple function for providing text-based feedback.
|
||||
|
||||
.. warning:: Only for development use.
|
||||
@@ -81,7 +81,7 @@ class PrintedProgress(object):
|
||||
class MuteProgress(PrintedProgress):
|
||||
"""A dummy progress handler that produces no output when called."""
|
||||
|
||||
def __call__(self, progress: Union[int, float], description: str = None):
|
||||
def __call__(self, progress: Union[int, float], description: Optional[str] = None):
|
||||
pass
|
||||
|
||||
|
||||
@@ -126,9 +126,7 @@ class CommandLine:
|
||||
"--help",
|
||||
action="help",
|
||||
default=argparse.SUPPRESS,
|
||||
help="Show this help message and exit, for specific plugin options use '{} <pluginname> --help'".format(
|
||||
parser.prog
|
||||
),
|
||||
help=f"Show this help message and exit, for specific plugin options use '{parser.prog} <pluginname> --help'",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
@@ -360,9 +358,7 @@ class CommandLine:
|
||||
subparser = parser.add_subparsers(
|
||||
title="Plugins",
|
||||
dest="plugin",
|
||||
description="For plugin specific options, run '{} <plugin> --help'".format(
|
||||
self.CLI_NAME
|
||||
),
|
||||
description=f"For plugin specific options, run '{self.CLI_NAME} <plugin> --help'",
|
||||
action=volargparse.HelpfulSubparserAction,
|
||||
metavar="PLUGIN",
|
||||
)
|
||||
@@ -416,7 +412,7 @@ class CommandLine:
|
||||
|
||||
# UI fills in the config, here we load it from the config file and do it before we process the CL parameters
|
||||
if args.config:
|
||||
with open(args.config, "r") as f:
|
||||
with open(args.config) as f:
|
||||
json_val = json.load(f)
|
||||
ctx.config.splice(
|
||||
plugin_config_path,
|
||||
@@ -722,9 +718,7 @@ class CommandLine:
|
||||
if isinstance(requirement, requirements.ListRequirement):
|
||||
if not isinstance(value, list):
|
||||
raise TypeError(
|
||||
"Configuration for ListRequirement was not a list: {}".format(
|
||||
requirement.name
|
||||
)
|
||||
f"Configuration for ListRequirement was not a list: {requirement.name}"
|
||||
)
|
||||
value = [requirement.element_type(x) for x in value]
|
||||
if not inspect.isclass(configurables_list[configurable]):
|
||||
@@ -797,7 +791,7 @@ class CommandLine:
|
||||
fd, self._name = tempfile.mkstemp(
|
||||
suffix=".vol3", prefix="tmp_", dir=output_dir
|
||||
)
|
||||
self._file = io.open(fd, mode="w+b")
|
||||
self._file = open(fd, mode="w+b")
|
||||
CLIFileHandler.__init__(self, filename)
|
||||
for item in dir(self._file):
|
||||
if not item.startswith("_") and item not in (
|
||||
@@ -870,9 +864,7 @@ class CommandLine:
|
||||
requirement, interfaces.configuration.RequirementInterface
|
||||
):
|
||||
raise TypeError(
|
||||
"Plugin contains requirements that are not RequirementInterfaces: {}".format(
|
||||
configurable.__name__
|
||||
)
|
||||
f"Plugin contains requirements that are not RequirementInterfaces: {configurable.__name__}"
|
||||
)
|
||||
if isinstance(requirement, interfaces.configuration.SimpleTypeRequirement):
|
||||
additional["type"] = requirement.instance_type
|
||||
@@ -887,7 +879,7 @@ class CommandLine:
|
||||
volatility3.framework.configuration.requirements.ListRequirement,
|
||||
):
|
||||
# Allow a list of integers, specified with the convenient 0x hexadecimal format
|
||||
if requirement.element_type == int:
|
||||
if requirement.element_type is int:
|
||||
additional["type"] = lambda x: int(x, 0)
|
||||
else:
|
||||
additional["type"] = requirement.element_type
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import logging
|
||||
from typing import Any, List, Optional
|
||||
from volatility3.framework import constants, interfaces
|
||||
import re
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -67,16 +68,16 @@ class ColumnFilter:
|
||||
) -> None:
|
||||
self.column_num = column_num
|
||||
self.pattern = pattern
|
||||
self.exclude = exclude
|
||||
self.regex = regex
|
||||
self.exclude = exclude
|
||||
|
||||
def find(self, item) -> bool:
|
||||
"""Identifies whether an item is found in the appropriate column"""
|
||||
try:
|
||||
if self.regex:
|
||||
return re.search(self.pattern, f"{item}")
|
||||
return bool(re.search(self.pattern, f"{item}"))
|
||||
return self.pattern in f"{item}"
|
||||
except IOError:
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def found(self, row: List[Any]) -> bool:
|
||||
|
||||
@@ -176,7 +176,7 @@ class QuickTextRenderer(CLIRenderer):
|
||||
format_hints.HexBytes: optional(hex_bytes_as_text),
|
||||
format_hints.MultiTypeData: quoted_optional(multitypedata_as_text),
|
||||
interfaces.renderers.Disassembly: optional(display_disassembly),
|
||||
bytes: optional(lambda x: " ".join([f"{b:02x}" for b in x])),
|
||||
bytes: optional(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
datetime.datetime: optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
|
||||
"default": optional(lambda x: f"{x}"),
|
||||
}
|
||||
@@ -256,7 +256,7 @@ class CSVRenderer(CLIRenderer):
|
||||
format_hints.HexBytes: optional(hex_bytes_as_text),
|
||||
format_hints.MultiTypeData: optional(multitypedata_as_text),
|
||||
interfaces.renderers.Disassembly: optional(display_disassembly),
|
||||
bytes: optional(lambda x: " ".join([f"{b:02x}" for b in x])),
|
||||
bytes: optional(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
datetime.datetime: optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
|
||||
"default": optional(lambda x: f"{x}"),
|
||||
}
|
||||
@@ -450,7 +450,7 @@ class JsonRenderer(CLIRenderer):
|
||||
format_hints.HexBytes: quoted_optional(hex_bytes_as_text),
|
||||
interfaces.renderers.Disassembly: quoted_optional(display_disassembly),
|
||||
format_hints.MultiTypeData: quoted_optional(multitypedata_as_text),
|
||||
bytes: optional(lambda x: " ".join([f"{b:02x}" for b in x])),
|
||||
bytes: optional(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
datetime.datetime: lambda x: (
|
||||
x.isoformat()
|
||||
if not isinstance(x, interfaces.renderers.BaseAbsentValue)
|
||||
@@ -467,7 +467,7 @@ class JsonRenderer(CLIRenderer):
|
||||
|
||||
def output_result(self, outfd, result):
|
||||
"""Outputs the JSON data to a file in a particular format"""
|
||||
outfd.write("{}\n".format(json.dumps(result, indent=2, sort_keys=True)))
|
||||
outfd.write(f"{json.dumps(result, indent=2, sort_keys=True)}\n")
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid):
|
||||
outfd = sys.stdout
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import argparse
|
||||
import gettext
|
||||
import re
|
||||
from typing import List, Optional, Sequence, Any, Union
|
||||
from typing import Optional, Sequence, Any, Union
|
||||
|
||||
|
||||
# This effectively overrides/monkeypatches the core argparse module to provide more helpful output around choices
|
||||
|
||||
@@ -282,9 +282,7 @@ class VolShell(cli.CommandLine):
|
||||
for plugin in volshell_plugin_list:
|
||||
subparser = parser.add_argument_group(
|
||||
title=plugin.capitalize(),
|
||||
description="Configuration options based on {} options".format(
|
||||
plugin.capitalize()
|
||||
),
|
||||
description=f"Configuration options based on {plugin.capitalize()} options",
|
||||
)
|
||||
self.populate_requirements_argparse(subparser, volshell_plugin_list[plugin])
|
||||
configurables_list[plugin] = volshell_plugin_list[plugin]
|
||||
@@ -331,7 +329,7 @@ class VolShell(cli.CommandLine):
|
||||
|
||||
# UI fills in the config, here we load it from the config file and do it before we process the CL parameters
|
||||
if args.config:
|
||||
with open(args.config, "r") as f:
|
||||
with open(args.config) as f:
|
||||
json_val = json.load(f)
|
||||
ctx.config.splice(
|
||||
plugin_config_path,
|
||||
|
||||
@@ -203,7 +203,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
connector = " "
|
||||
if chunk_size < 2:
|
||||
connector = ""
|
||||
ascii_data = connector.join([self._ascii_bytes(x) for x in valid_data])
|
||||
ascii_data = connector.join(self._ascii_bytes(x) for x in valid_data)
|
||||
|
||||
print(hex(offset), " ", hex_data, " ", ascii_data)
|
||||
offset += 16
|
||||
@@ -240,7 +240,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
return None
|
||||
return self.context.modules[self.current_kernel_name]
|
||||
|
||||
def change_layer(self, layer_name: str = None):
|
||||
def change_layer(self, layer_name: Optional[str] = None):
|
||||
"""Changes the current default layer"""
|
||||
if not layer_name:
|
||||
layer_name = self.current_layer
|
||||
@@ -250,7 +250,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
self.__current_layer = layer_name
|
||||
sys.ps1 = f"({self.current_layer}) >>> "
|
||||
|
||||
def change_symbol_table(self, symbol_table_name: str = None):
|
||||
def change_symbol_table(self, symbol_table_name: Optional[str] = None):
|
||||
"""Changes the current_symbol_table"""
|
||||
if not symbol_table_name:
|
||||
print("No symbol table provided, not changing current symbol table")
|
||||
@@ -262,7 +262,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
self.__current_symbol_table = symbol_table_name
|
||||
print(f"Current Symbol Table: {self.current_symbol_table}")
|
||||
|
||||
def change_kernel(self, kernel_name: str = None):
|
||||
def change_kernel(self, kernel_name: Optional[str] = None):
|
||||
if not kernel_name:
|
||||
print("No kernel module name provided, not changing current kernel")
|
||||
if kernel_name not in self.context.modules:
|
||||
@@ -347,7 +347,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
object: Union[
|
||||
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
):
|
||||
"""Display Type describes the members of a particular object in alphabetical order"""
|
||||
if not isinstance(
|
||||
@@ -479,7 +479,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
if treegrid is not None:
|
||||
self.render_treegrid(treegrid)
|
||||
|
||||
def display_symbols(self, symbol_table: str = None):
|
||||
def display_symbols(self, symbol_table: Optional[str] = None):
|
||||
"""Prints an alphabetical list of symbols for a symbol table"""
|
||||
if symbol_table is None:
|
||||
print("No symbol table provided")
|
||||
@@ -553,17 +553,16 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
if argname in kwargs:
|
||||
del kwargs[argname]
|
||||
|
||||
for keyword in kwargs:
|
||||
val = kwargs[keyword]
|
||||
if not isinstance(
|
||||
val, interfaces.configuration.BasicTypes
|
||||
) and not isinstance(val, list):
|
||||
if not isinstance(val, list) or all(
|
||||
isinstance(x, interfaces.configuration.BasicTypes) for x in val
|
||||
):
|
||||
raise TypeError(
|
||||
"Configurable values must be simple types (int, bool, str, bytes)"
|
||||
)
|
||||
for keyword, val in kwargs.items():
|
||||
BasicType_or_list_of_BasicType = False # excludes list of lists
|
||||
if isinstance(val, interfaces.configuration.BasicTypes):
|
||||
BasicType_or_list_of_BasicType = True
|
||||
if all(isinstance(x, interfaces.configuration.BasicTypes) for x in val):
|
||||
BasicType_or_list_of_BasicType = True
|
||||
if not BasicType_or_list_of_BasicType:
|
||||
raise TypeError(
|
||||
"Configurable values must be simple types (int, bool, str, bytes)"
|
||||
)
|
||||
self.context.config[config_path + "." + keyword] = val
|
||||
|
||||
constructed = clazz(self.context, config_path, **constructor_args)
|
||||
@@ -585,7 +584,6 @@ class NullFileHandler(io.BytesIO, interfaces.plugins.FileHandlerInterface):
|
||||
|
||||
def writelines(self, lines: Iterable[bytes]):
|
||||
"""Dummy method"""
|
||||
pass
|
||||
|
||||
def write(self, b: bytes):
|
||||
"""Dummy method"""
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from typing import Any, List, Tuple, Union
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
from volatility3.cli.volshell import generic
|
||||
from volatility3.framework import constants, interfaces
|
||||
@@ -20,7 +20,7 @@ class Volshell(generic.Volshell):
|
||||
name="kernel", description="Linux kernel module"
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="pid", description="Process ID", optional=True
|
||||
@@ -61,7 +61,7 @@ class Volshell(generic.Volshell):
|
||||
object: Union[
|
||||
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
):
|
||||
"""Display Type describes the members of a particular object in alphabetical order"""
|
||||
if isinstance(object, str):
|
||||
@@ -69,7 +69,7 @@ class Volshell(generic.Volshell):
|
||||
object = self.current_symbol_table + constants.BANG + object
|
||||
return super().display_type(object, offset)
|
||||
|
||||
def display_symbols(self, symbol_table: str = None):
|
||||
def display_symbols(self, symbol_table: Optional[str] = None):
|
||||
"""Prints an alphabetical list of symbols for a symbol table"""
|
||||
if symbol_table is None:
|
||||
symbol_table = self.current_symbol_table
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from typing import Any, List, Tuple, Union
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
from volatility3.cli.volshell import generic
|
||||
from volatility3.framework import constants, interfaces
|
||||
@@ -63,7 +63,7 @@ class Volshell(generic.Volshell):
|
||||
object: Union[
|
||||
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
):
|
||||
"""Display Type describes the members of a particular object in alphabetical order"""
|
||||
if isinstance(object, str):
|
||||
@@ -71,7 +71,7 @@ class Volshell(generic.Volshell):
|
||||
object = self.current_symbol_table + constants.BANG + object
|
||||
return super().display_type(object, offset)
|
||||
|
||||
def display_symbols(self, symbol_table: str = None):
|
||||
def display_symbols(self, symbol_table: Optional[str] = None):
|
||||
"""Prints an alphabetical list of symbols for a symbol table"""
|
||||
if symbol_table is None:
|
||||
symbol_table = self.current_symbol_table
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from typing import Any, List, Tuple, Union
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
from volatility3.cli.volshell import generic
|
||||
from volatility3.framework import constants, interfaces
|
||||
@@ -60,7 +60,7 @@ class Volshell(generic.Volshell):
|
||||
object: Union[
|
||||
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
):
|
||||
"""Display Type describes the members of a particular object in alphabetical order"""
|
||||
if isinstance(object, str):
|
||||
@@ -68,7 +68,7 @@ class Volshell(generic.Volshell):
|
||||
object = self.current_symbol_table + constants.BANG + object
|
||||
return super().display_type(object, offset)
|
||||
|
||||
def display_symbols(self, symbol_table: str = None):
|
||||
def display_symbols(self, symbol_table: Optional[str] = None):
|
||||
"""Prints an alphabetical list of symbols for a symbol table"""
|
||||
if symbol_table is None:
|
||||
symbol_table = self.current_symbol_table
|
||||
|
||||
@@ -6,28 +6,12 @@
|
||||
import glob
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
required_python_version = (3, 8, 0)
|
||||
if (
|
||||
sys.version_info.major != required_python_version[0]
|
||||
or sys.version_info.minor < required_python_version[1]
|
||||
or (
|
||||
sys.version_info.minor == required_python_version[1]
|
||||
and sys.version_info.micro < required_python_version[2]
|
||||
)
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Volatility framework requires python version {}.{}.{} or greater".format(
|
||||
*required_python_version
|
||||
)
|
||||
)
|
||||
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
from typing import Any, Dict, Generator, List, Tuple, Type, TypeVar
|
||||
from typing import Any, Dict, Generator, List, Optional, Tuple, Type, TypeVar
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
|
||||
@@ -56,27 +40,25 @@ def require_interface_version(*args) -> None:
|
||||
if len(args):
|
||||
if args[0] != interface_version()[0]:
|
||||
raise RuntimeError(
|
||||
"Framework interface version {} is incompatible with required version {}".format(
|
||||
interface_version()[0], args[0]
|
||||
)
|
||||
f"Framework interface version {interface_version()[0]} is incompatible with required version {args[0]}"
|
||||
)
|
||||
if len(args) > 1:
|
||||
if args[1] > interface_version()[1]:
|
||||
raise RuntimeError(
|
||||
"Framework interface version {} is an older revision than the required version {}".format(
|
||||
".".join([str(x) for x in interface_version()[0:2]]),
|
||||
".".join([str(x) for x in args[0:2]]),
|
||||
".".join(str(x) for x in interface_version()[0:2]),
|
||||
".".join(str(x) for x in args[0:2]),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class NonInheritable(object):
|
||||
class NonInheritable:
|
||||
def __init__(self, value: Any, cls: Type) -> None:
|
||||
self.default_value = value
|
||||
self.cls = cls
|
||||
|
||||
def __get__(self, obj: Any, get_type: Type = None) -> Any:
|
||||
if type == self.cls:
|
||||
def __get__(self, obj: Any, get_type: Optional[Type] = None) -> Any:
|
||||
if type is self.cls:
|
||||
if hasattr(self.default_value, "__get__"):
|
||||
return self.default_value.__get__(obj, get_type)
|
||||
return self.default_value
|
||||
@@ -99,8 +81,7 @@ def class_subclasses(cls: Type[T]) -> Generator[Type[T], None, None]:
|
||||
# The typing system is not clever enough to realize that clazz has a hidden attr after the hasattr check
|
||||
if not hasattr(clazz, "hidden") or not clazz.hidden: # type: ignore
|
||||
yield clazz
|
||||
for return_value in class_subclasses(clazz):
|
||||
yield return_value
|
||||
yield from class_subclasses(clazz)
|
||||
|
||||
|
||||
def import_files(base_module, ignore_errors: bool = False) -> List[str]:
|
||||
@@ -161,9 +142,7 @@ def import_files(base_module, ignore_errors: bool = False) -> List[str]:
|
||||
|
||||
def _filter_files(filename: str):
|
||||
"""Ensures that a filename traversed is an importable python file"""
|
||||
return (
|
||||
filename.endswith(".py") or filename.endswith(".pyc")
|
||||
) and not filename.startswith("__")
|
||||
return (filename.endswith((".py", ".pyc"))) and not filename.startswith("__")
|
||||
|
||||
|
||||
def import_file(module: str, path: str, ignore_errors: bool = False) -> List[str]:
|
||||
@@ -187,9 +166,7 @@ def import_file(module: str, path: str, ignore_errors: bool = False) -> List[str
|
||||
traceback.TracebackException.from_exception(e).format(chain=True)
|
||||
)
|
||||
)
|
||||
vollog.debug(
|
||||
"Failed to import module {} based on file: {}".format(module, path)
|
||||
)
|
||||
vollog.debug(f"Failed to import module {module} based on file: {path}")
|
||||
failures.append(module)
|
||||
if not ignore_errors:
|
||||
raise
|
||||
@@ -207,8 +184,7 @@ def _zipwalk(path: str):
|
||||
zip_results[os.path.join(path, os.path.dirname(file.filename))] = (
|
||||
dirlist
|
||||
)
|
||||
for value in zip_results:
|
||||
yield value, zip_results[value]
|
||||
yield from zip_results.items()
|
||||
|
||||
|
||||
def list_plugins() -> Dict[str, Type[interfaces.plugins.PluginInterface]]:
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional, Tuple, Type
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
from volatility3.framework.automagic import symbol_cache, symbol_finder
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers import intel, scanners
|
||||
from volatility3.framework.symbols import linux
|
||||
|
||||
@@ -173,9 +171,7 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
if aslr_shift & 0xFFF != 0 or kaslr_shift & 0xFFF != 0:
|
||||
continue
|
||||
vollog.debug(
|
||||
"Linux ASLR shift values determined: physical {:0x} virtual {:0x}".format(
|
||||
kaslr_shift, aslr_shift
|
||||
)
|
||||
f"Linux ASLR shift values determined: physical {kaslr_shift:0x} virtual {aslr_shift:0x}"
|
||||
)
|
||||
return kaslr_shift, aslr_shift
|
||||
|
||||
@@ -198,5 +194,8 @@ class LinuxSymbolFinder(symbol_finder.SymbolFinder):
|
||||
banner_config_key = "kernel_banner"
|
||||
operating_system = "linux"
|
||||
symbol_class = "volatility3.framework.symbols.linux.LinuxKernelIntermedSymbols"
|
||||
find_aslr = lambda cls, *args: LinuxIntelStacker.find_aslr(*args)[1]
|
||||
exclusion_list = ["mac", "windows"]
|
||||
|
||||
@classmethod
|
||||
def find_aslr(cls, *args):
|
||||
return LinuxIntelStacker.find_aslr(*args)[1]
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
import os
|
||||
import struct
|
||||
from typing import Optional
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces, layers
|
||||
from volatility3.framework.automagic import symbol_cache, symbol_finder
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers import intel, scanners
|
||||
from volatility3.framework.symbols import mac
|
||||
|
||||
@@ -184,7 +182,7 @@ class MacIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
aslr_shift = 0
|
||||
|
||||
for offset, banner in offset_generator:
|
||||
banner_major, banner_minor = [int(x) for x in banner[22:].split(b".")[0:2]]
|
||||
banner_major, banner_minor = (int(x) for x in banner[22:].split(b".")[0:2])
|
||||
|
||||
tmp_aslr_shift = offset - cls.virtual_to_physical_address(
|
||||
version_json_address
|
||||
|
||||
@@ -215,9 +215,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
return (virtual_layer_name, kvo, kernel)
|
||||
else:
|
||||
vollog.debug(
|
||||
"Potential kernel_virtual_offset did not map to expected location: {}".format(
|
||||
hex(kvo)
|
||||
)
|
||||
f"Potential kernel_virtual_offset did not map to expected location: {hex(kvo)}"
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
|
||||
@@ -166,7 +166,9 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
initial_layer: str,
|
||||
stack_set: List[Type[interfaces.automagic.StackerLayerInterface]] = None,
|
||||
stack_set: Optional[
|
||||
List[Type[interfaces.automagic.StackerLayerInterface]]
|
||||
] = None,
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
):
|
||||
"""Stacks as many possible layers on top of the initial layer as can be done.
|
||||
|
||||
@@ -104,10 +104,11 @@ class CacheManagerInterface(interfaces.configuration.VersionableInterface):
|
||||
for subclazz in framework.class_subclasses(IdentifierProcessor):
|
||||
self._classifiers[subclazz.operating_system] = subclazz
|
||||
|
||||
@abstractmethod
|
||||
def add_identifier(self, location: str, operating_system: str, identifier: str):
|
||||
"""Adds an identifier to the store"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def find_location(
|
||||
self, identifier: bytes, operating_system: Optional[str]
|
||||
) -> Optional[str]:
|
||||
@@ -120,19 +121,19 @@ class CacheManagerInterface(interfaces.configuration.VersionableInterface):
|
||||
Returns:
|
||||
The location of the symbols file that matches the identifier
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_local_locations(self) -> Iterable[str]:
|
||||
"""Returns a list of all the local locations"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update(self):
|
||||
"""Locates all files under the symbol directories. Updates the cache with additions, modifications and removals.
|
||||
This also updates remote locations based on a cache timeout.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_identifier_dictionary(
|
||||
self, operating_system: Optional[str] = None, local_only: bool = False
|
||||
) -> Dict[bytes, str]:
|
||||
@@ -145,16 +146,16 @@ class CacheManagerInterface(interfaces.configuration.VersionableInterface):
|
||||
Returns:
|
||||
A dictionary of identifiers mapped to a location
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_identifier(self, location: str) -> Optional[bytes]:
|
||||
"""Returns an identifier based on a specific location or None"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_identifiers(self, operating_system: Optional[str]) -> List[bytes]:
|
||||
"""Returns all identifiers for a particular operating system"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_location_statistics(
|
||||
self, location: str
|
||||
) -> Optional[Tuple[int, int, int, int]]:
|
||||
@@ -164,6 +165,7 @@ class CacheManagerInterface(interfaces.configuration.VersionableInterface):
|
||||
A tuple of base_types, types, enums, symbols, or None is location not found
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_hash(self, location: str) -> Optional[str]:
|
||||
"""Returns the hash of the JSON from within a location ISF"""
|
||||
|
||||
@@ -572,6 +574,6 @@ class RemoteIdentifierFormat:
|
||||
try:
|
||||
subrbf = RemoteIdentifierFormat(location)
|
||||
yield from subrbf.process(identifiers, operating_system)
|
||||
except IOError:
|
||||
except OSError:
|
||||
vollog.debug(f"Remote file not found: {location}")
|
||||
return identifiers
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Callable, Iterable, List, Optional, Tuple
|
||||
from typing import Callable, List, Optional, Tuple
|
||||
|
||||
from volatility3.framework import constants, interfaces, layers
|
||||
from volatility3.framework.automagic import symbol_cache
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import sys
|
||||
|
||||
required_python_version = (3, 8, 0)
|
||||
if (
|
||||
sys.version_info.major != required_python_version[0]
|
||||
or sys.version_info.minor < required_python_version[1]
|
||||
or (
|
||||
sys.version_info.minor == required_python_version[1]
|
||||
and sys.version_info.micro < required_python_version[2]
|
||||
)
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Volatility framework requires python version {required_python_version[0]}.{required_python_version[1]}.{required_python_version[2]} or greater"
|
||||
)
|
||||
@@ -2,4 +2,4 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.configuration import requirements as requirements
|
||||
|
||||
@@ -11,7 +11,7 @@ expect to be in the context (such as particular layers or symboltables).
|
||||
import abc
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type
|
||||
from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple, Type
|
||||
from urllib import parse, request
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
@@ -111,7 +111,7 @@ class ListRequirement(interfaces.configuration.RequirementInterface):
|
||||
|
||||
Args:
|
||||
element_type: The (requirement) type of each element within the list
|
||||
max_elements; The maximum number of acceptable elements this list can contain
|
||||
max_elements: The maximum number of acceptable elements this list can contain
|
||||
min_elements: The minimum number of acceptable elements this list can contain
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -314,11 +314,11 @@ class TranslationLayerRequirement(
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str = None,
|
||||
description: Optional[str] = None,
|
||||
default: interfaces.configuration.ConfigSimpleType = None,
|
||||
optional: bool = False,
|
||||
oses: List = None,
|
||||
architectures: List = None,
|
||||
oses: Optional[List] = None,
|
||||
architectures: Optional[List[str]] = None,
|
||||
) -> None:
|
||||
"""Constructs a Translation Layer Requirement.
|
||||
|
||||
@@ -526,18 +526,18 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
|
||||
description: Optional[str] = None,
|
||||
default: bool = False,
|
||||
optional: bool = False,
|
||||
component: Type[interfaces.configuration.VersionableInterface] = None,
|
||||
component: Optional[Type[interfaces.configuration.VersionableInterface]] = None,
|
||||
version: Optional[Tuple[int, ...]] = None,
|
||||
) -> None:
|
||||
if version is None:
|
||||
raise TypeError("Version cannot be None")
|
||||
if component is None:
|
||||
raise TypeError("Component cannot be None")
|
||||
if description is None:
|
||||
description = f"Version {'.'.join([str(x) for x in version])} dependency on {component.__module__}.{component.__name__} unmet"
|
||||
description = f"Version {'.'.join(str(x) for x in version)} dependency on {component.__module__}.{component.__name__} unmet"
|
||||
super().__init__(
|
||||
name=name, description=description, default=default, optional=optional
|
||||
)
|
||||
if component is None:
|
||||
raise TypeError("Component cannot be None")
|
||||
self._component: Type[interfaces.configuration.VersionableInterface] = component
|
||||
self._version = version
|
||||
|
||||
@@ -546,7 +546,7 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
accumulator: Optional[
|
||||
List[interfaces.configuration.VersionableInterface]
|
||||
Set[interfaces.configuration.VersionableInterface]
|
||||
] = None,
|
||||
) -> Dict[str, interfaces.configuration.RequirementInterface]:
|
||||
# Mypy doesn't appreciate our classproperty implementation, self._plugin.version has no type
|
||||
@@ -580,7 +580,7 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
|
||||
)
|
||||
|
||||
if result:
|
||||
result.update({config_path: self})
|
||||
result[config_path] = self
|
||||
return result
|
||||
|
||||
context.config[interfaces.configuration.path_join(config_path, self.name)] = (
|
||||
@@ -604,10 +604,10 @@ class PluginRequirement(VersionRequirement):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str = None,
|
||||
description: Optional[str] = None,
|
||||
default: bool = False,
|
||||
optional: bool = False,
|
||||
plugin: Type[interfaces.plugins.PluginInterface] = None,
|
||||
plugin: Optional[Type[interfaces.plugins.PluginInterface]] = None,
|
||||
version: Optional[Tuple[int, ...]] = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
@@ -627,7 +627,7 @@ class ModuleRequirement(
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str = None,
|
||||
description: Optional[str] = None,
|
||||
default: bool = False,
|
||||
architectures: Optional[List[str]] = None,
|
||||
optional: bool = False,
|
||||
@@ -664,9 +664,7 @@ class ModuleRequirement(
|
||||
if value is not None:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_V,
|
||||
"TypeError - Module Requirement only accepts string labels: {}".format(
|
||||
repr(value)
|
||||
),
|
||||
f"TypeError - Module Requirement only accepts string labels: {repr(value)}",
|
||||
)
|
||||
return {config_path: self}
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ import sys
|
||||
import warnings
|
||||
from typing import Callable, Optional
|
||||
|
||||
import volatility3.framework.constants.linux
|
||||
import volatility3.framework.constants.windows
|
||||
from volatility3.framework.constants import linux as linux
|
||||
from volatility3.framework.constants import windows as windows
|
||||
from volatility3.framework.constants._version import (
|
||||
PACKAGE_VERSION,
|
||||
VERSION_MAJOR,
|
||||
VERSION_MINOR,
|
||||
VERSION_PATCH,
|
||||
VERSION_SUFFIX,
|
||||
PACKAGE_VERSION as PACKAGE_VERSION,
|
||||
VERSION_MAJOR as VERSION_MAJOR,
|
||||
VERSION_MINOR as VERSION_MINOR,
|
||||
VERSION_PATCH as VERSION_PATCH,
|
||||
VERSION_SUFFIX as VERSION_SUFFIX,
|
||||
)
|
||||
|
||||
PLUGINS_PATH = [
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# We use the SemVer 2.0.0 versioning scheme
|
||||
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
|
||||
VERSION_MINOR = 12 # Number of changes that only add to the interface
|
||||
VERSION_MINOR = 15 # Number of changes that only add to the interface
|
||||
VERSION_PATCH = 1 # Number of changes that do not change the interface
|
||||
VERSION_SUFFIX = ""
|
||||
|
||||
PACKAGE_VERSION = (
|
||||
".".join([str(x) for x in [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH]])
|
||||
".".join(str(x) for x in [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH])
|
||||
+ VERSION_SUFFIX
|
||||
)
|
||||
"""The canonical version of the volatility3 package"""
|
||||
|
||||
@@ -229,7 +229,7 @@ class Module(interfaces.context.ModuleInterface):
|
||||
def object(
|
||||
self,
|
||||
object_type: str,
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
native_layer_name: Optional[str] = None,
|
||||
absolute: bool = False,
|
||||
**kwargs,
|
||||
@@ -356,7 +356,7 @@ class SizedModule(Module):
|
||||
return size or 0
|
||||
|
||||
@property # type: ignore # FIXME: mypy #5107
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def hash(self) -> str:
|
||||
"""Hashes the module for equality checks.
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ components of volatility to write plugins.
|
||||
# This will also avoid namespace issues, because people can use interfaces.layers to
|
||||
# avoid clashing with the layers package
|
||||
from volatility3.framework.interfaces import (
|
||||
renderers,
|
||||
configuration,
|
||||
context,
|
||||
layers,
|
||||
objects,
|
||||
plugins,
|
||||
symbols,
|
||||
automagic,
|
||||
renderers as renderers,
|
||||
configuration as configuration,
|
||||
context as context,
|
||||
layers as layers,
|
||||
objects as objects,
|
||||
plugins as plugins,
|
||||
symbols as symbols,
|
||||
automagic as automagic,
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ class AutomagicInterface(
|
||||
priority = 10
|
||||
"""An ordering to indicate how soon this automagic should be run"""
|
||||
|
||||
exclusion_list = []
|
||||
exclusion_list: List[str] = []
|
||||
"""A list of plugin categories (typically operating systems) which the plugin will not operate on"""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -53,7 +53,7 @@ ConfigSimpleType = Optional[Union[SimpleTypes, List[SimpleTypes]]]
|
||||
def path_join(*args) -> str:
|
||||
"""Joins configuration paths together."""
|
||||
# If a path element (particularly the first) is empty, then remove it from the list
|
||||
args = tuple([arg for arg in args if arg])
|
||||
args = tuple(arg for arg in args if arg)
|
||||
return CONFIG_SEPARATOR.join(args)
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
initial_dict: Dict[str, "SimpleTypeRequirement"] = None,
|
||||
initial_dict: Optional[Dict[str, "SimpleTypeRequirement"]] = None,
|
||||
separator: str = CONFIG_SEPARATOR,
|
||||
) -> None:
|
||||
"""
|
||||
@@ -94,7 +94,7 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
raise TypeError(f"Separator must be a one character string: {separator}")
|
||||
self._separator = separator
|
||||
self._data: Dict[str, ConfigSimpleType] = {}
|
||||
self._subdict: Dict[str, "HierarchicalDict"] = {}
|
||||
self._subdict: Dict[str, HierarchicalDict] = {}
|
||||
if isinstance(initial_dict, str):
|
||||
initial_dict = json.loads(initial_dict)
|
||||
if isinstance(initial_dict, dict):
|
||||
@@ -182,9 +182,7 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
else:
|
||||
if not isinstance(value, HierarchicalDict):
|
||||
raise TypeError(
|
||||
"HierarchicalDicts can only store HierarchicalDicts within their structure: {}".format(
|
||||
type(value)
|
||||
)
|
||||
f"HierarchicalDicts can only store HierarchicalDicts within their structure: {type(value)}"
|
||||
)
|
||||
self._subdict[key] = value
|
||||
|
||||
@@ -330,7 +328,7 @@ class RequirementInterface(metaclass=ABCMeta):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str = None,
|
||||
description: Optional[str] = None,
|
||||
default: ConfigSimpleType = None,
|
||||
optional: bool = False,
|
||||
) -> None:
|
||||
@@ -498,9 +496,7 @@ class SimpleTypeRequirement(RequirementInterface):
|
||||
if not isinstance(value, self.instance_type):
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_V,
|
||||
"TypeError - {} requirements only accept {} type: {}".format(
|
||||
self.name, self.instance_type.__name__, repr(value)
|
||||
),
|
||||
f"TypeError - {self.name} requirements only accept {self.instance_type.__name__} type: {repr(value)}",
|
||||
)
|
||||
return {config_path: self}
|
||||
return {}
|
||||
@@ -622,7 +618,7 @@ class ConstructableRequirementInterface(RequirementInterface):
|
||||
self,
|
||||
context: "interfaces.context.ContextInterface",
|
||||
config_path: str,
|
||||
requirement_dict: Dict[str, object] = None,
|
||||
requirement_dict: Optional[Dict[str, object]] = None,
|
||||
) -> Optional["interfaces.objects.ObjectInterface"]:
|
||||
"""Constructs the class, handing args and the subrequirements as
|
||||
parameters to __init__"""
|
||||
@@ -656,6 +652,7 @@ class ConstructableRequirementInterface(RequirementInterface):
|
||||
class ConfigurableRequirementInterface(RequirementInterface):
|
||||
"""Simple Abstract class to provide build_required_config."""
|
||||
|
||||
@abstractmethod
|
||||
def build_configuration(
|
||||
self,
|
||||
context: "interfaces.context.ContextInterface",
|
||||
@@ -775,17 +772,16 @@ class ConfigurableInterface(metaclass=ABCMeta):
|
||||
str: The newly generated full configuration path
|
||||
"""
|
||||
random_config_dict = "".join(
|
||||
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(8)
|
||||
random.SystemRandom().choices(string.ascii_uppercase + string.digits, k=8)
|
||||
)
|
||||
new_config_path = path_join(base_config_path, random_config_dict)
|
||||
# TODO: Check that the new_config_path is empty, although it's not critical if it's not since the values are merged in
|
||||
|
||||
# This should check that each k corresponds to a requirement and each v is of the appropriate type
|
||||
# This would require knowledge of the new configurable itself to verify, and they should do validation in the
|
||||
# constructor anyway, however, to prevent bad types getting into the config tree we just verify that v is a simple type
|
||||
# constructor anyway, however, to prevent bad types getting into the config tree we just verify that v is a basic type
|
||||
for k, v in kwargs.items():
|
||||
if not isinstance(v, (int, str, bool, float, bytes)):
|
||||
if not isinstance(v, BasicTypes):
|
||||
raise TypeError(
|
||||
"Config values passed to make_subconfig can only be simple types"
|
||||
)
|
||||
|
||||
@@ -85,7 +85,7 @@ class ContextInterface(metaclass=ABCMeta):
|
||||
object_type: Union[str, "interfaces.objects.Template"],
|
||||
layer_name: str,
|
||||
offset: int,
|
||||
native_layer_name: str = None,
|
||||
native_layer_name: Optional[str] = None,
|
||||
**arguments,
|
||||
) -> "interfaces.objects.ObjectInterface":
|
||||
"""Object factory, takes a context, symbol, offset and optional
|
||||
@@ -114,6 +114,7 @@ class ContextInterface(metaclass=ABCMeta):
|
||||
"""
|
||||
return copy.deepcopy(self)
|
||||
|
||||
@abstractmethod
|
||||
def module(
|
||||
self,
|
||||
module_name: str,
|
||||
@@ -232,7 +233,7 @@ class ModuleInterface(interfaces.configuration.ConfigurableInterface):
|
||||
def object(
|
||||
self,
|
||||
object_type: str,
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
native_layer_name: Optional[str] = None,
|
||||
absolute: bool = False,
|
||||
**kwargs,
|
||||
@@ -277,28 +278,36 @@ class ModuleInterface(interfaces.configuration.ConfigurableInterface):
|
||||
symbol = self.get_symbol(name)
|
||||
return self.offset + symbol.address
|
||||
|
||||
@abstractmethod
|
||||
def get_type(self, name: str) -> "interfaces.objects.Template":
|
||||
"""Returns a type from the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def get_symbol(self, name: str) -> "interfaces.symbols.SymbolInterface":
|
||||
"""Returns a symbol object from the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def get_enumeration(self, name: str) -> "interfaces.objects.Template":
|
||||
"""Returns an enumeration from the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def has_type(self, name: str) -> bool:
|
||||
"""Determines whether a type is present in the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def has_symbol(self, name: str) -> bool:
|
||||
"""Determines whether a symbol is present in the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def has_enumeration(self, name: str) -> bool:
|
||||
"""Determines whether an enumeration is present in the module's symbol table."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def symbols(self) -> List:
|
||||
"""Lists the symbols contained in the symbol table for this module"""
|
||||
|
||||
@abstractmethod
|
||||
def get_symbols_by_absolute_location(self, offset: int, size: int = 0) -> List[str]:
|
||||
"""Returns the symbols within table_name (or this module if not specified) that live at the specified
|
||||
absolute offset provided."""
|
||||
@@ -344,6 +353,7 @@ class ModuleContainer(collections.abc.Mapping):
|
||||
def __iter__(self):
|
||||
return iter(self._modules)
|
||||
|
||||
@abstractmethod
|
||||
def free_module_name(self, prefix: str = "module") -> str:
|
||||
"""Returns an unused table name to ensure no collision occurs when
|
||||
inserting a symbol table."""
|
||||
|
||||
@@ -188,7 +188,6 @@ class DataLayerInterface(
|
||||
the object unreadable (exceptions will be thrown using a
|
||||
DataLayer after destruction)
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -211,7 +210,7 @@ class DataLayerInterface(
|
||||
context: interfaces.context.ContextInterface,
|
||||
scanner: ScannerInterface,
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
sections: Iterable[Tuple[int, int]] = None,
|
||||
sections: Optional[Iterable[Tuple[int, int]]] = None,
|
||||
) -> Iterable[Any]:
|
||||
"""Scans a Translation layer by chunk.
|
||||
|
||||
@@ -361,9 +360,7 @@ class DataLayerInterface(
|
||||
data += self.context.layers[layer_name].read(address, chunk_size)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
"Invalid address in layer {} found scanning {} at address {:x}".format(
|
||||
layer_name, self.name, address
|
||||
)
|
||||
f"Invalid address in layer {layer_name} found scanning {self.name} at address {address:x}"
|
||||
)
|
||||
|
||||
if len(data) > scanner.chunk_size + scanner.overlap:
|
||||
@@ -721,7 +718,7 @@ class LayerContainer(collections.abc.Mapping):
|
||||
raise NotImplementedError("Cycle checking has not yet been implemented")
|
||||
|
||||
|
||||
class DummyProgress(object):
|
||||
class DummyProgress:
|
||||
"""A class to emulate Multiprocessing/threading Value objects."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
@@ -374,6 +374,7 @@ class Template:
|
||||
f"{self.__class__.__name__} object has no attribute {attr}"
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
def __call__(
|
||||
self,
|
||||
context: "interfaces.context.ContextInterface",
|
||||
|
||||
@@ -46,7 +46,7 @@ class FileHandlerInterface(io.RawIOBase):
|
||||
def preferred_filename(self, filename: str):
|
||||
"""Sets the preferred filename"""
|
||||
if self.closed:
|
||||
raise IOError("FileHandler name cannot be changed once closed")
|
||||
raise OSError("FileHandler name cannot be changed once closed")
|
||||
if not isinstance(filename, str):
|
||||
raise TypeError("FileHandler preferred filenames must be strings")
|
||||
if os.path.sep in filename:
|
||||
|
||||
@@ -26,7 +26,11 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
Column = NamedTuple("Column", [("name", str), ("type", Any)])
|
||||
|
||||
class Column(NamedTuple):
|
||||
name: str
|
||||
type: Any
|
||||
|
||||
|
||||
RenderOption = Any
|
||||
|
||||
@@ -98,11 +102,11 @@ class TreeNode(abc.Sequence, metaclass=ABCMeta):
|
||||
"""
|
||||
|
||||
|
||||
class BaseAbsentValue(object):
|
||||
class BaseAbsentValue:
|
||||
"""Class that represents values which are not present for some reason."""
|
||||
|
||||
|
||||
class Disassembly(object):
|
||||
class Disassembly:
|
||||
"""A class to indicate that the bytes provided should be disassembled
|
||||
(based on the architecture)"""
|
||||
|
||||
@@ -137,7 +141,7 @@ ColumnsType = List[Tuple[str, BaseTypes]]
|
||||
VisitorSignature = Callable[[TreeNode, _Type], _Type]
|
||||
|
||||
|
||||
class TreeGrid(object, metaclass=ABCMeta):
|
||||
class TreeGrid(metaclass=ABCMeta):
|
||||
"""Class providing the interface for a TreeGrid (which contains TreeNodes)
|
||||
|
||||
The structure of a TreeGrid is designed to maintain the structure of the tree in a single object.
|
||||
@@ -179,7 +183,7 @@ class TreeGrid(object, metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
def populate(
|
||||
self,
|
||||
function: VisitorSignature = None,
|
||||
function: Optional[VisitorSignature] = None,
|
||||
initial_accumulator: Any = None,
|
||||
fail_on_errors: bool = True,
|
||||
) -> Optional[Exception]:
|
||||
@@ -231,7 +235,7 @@ class TreeGrid(object, metaclass=ABCMeta):
|
||||
node: Optional[TreeNode],
|
||||
function: VisitorSignature,
|
||||
initial_accumulator: _Type,
|
||||
sort_key: ColumnSortKey = None,
|
||||
sort_key: Optional[ColumnSortKey] = None,
|
||||
) -> None:
|
||||
"""Visits all the nodes in a tree, calling function on each one.
|
||||
|
||||
|
||||
@@ -250,13 +250,13 @@ class BaseSymbolTableInterface:
|
||||
|
||||
def clear_symbol_cache(self) -> None:
|
||||
"""Clears the symbol cache of this symbol table."""
|
||||
pass
|
||||
|
||||
|
||||
class SymbolSpaceInterface(collections.abc.Mapping):
|
||||
"""An interface for the container that holds all the symbol-containing
|
||||
tables for use within a context."""
|
||||
|
||||
@abstractmethod
|
||||
def free_table_name(self, prefix: str = "layer") -> str:
|
||||
"""Returns an unused table name to ensure no collision occurs when
|
||||
inserting a symbol table."""
|
||||
@@ -378,7 +378,7 @@ class NativeTableInterface(BaseSymbolTableInterface):
|
||||
return []
|
||||
|
||||
|
||||
class MetadataInterface(object):
|
||||
class MetadataInterface:
|
||||
"""Interface for accessing metadata stored within a symbol table."""
|
||||
|
||||
def __init__(self, json_data: Dict) -> None:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
import contextlib
|
||||
import logging
|
||||
import struct
|
||||
from typing import Tuple, Optional
|
||||
@@ -138,7 +137,7 @@ class WindowsCrashDump32Layer(segmented.SegmentedLayer):
|
||||
ulong_bitmap_array = summary_header.get_buffer_long()
|
||||
# outer_index points to a 32 bits array inside a list of arrays,
|
||||
# each bit indicating a page mapping state
|
||||
for outer_index in range(0, ulong_bitmap_array.vol.count):
|
||||
for outer_index in range(ulong_bitmap_array.vol.count):
|
||||
ulong_bitmap = ulong_bitmap_array[outer_index]
|
||||
# All pages in this 32 bits array are mapped (speedup iteration process)
|
||||
if ulong_bitmap == 0xFFFFFFFF:
|
||||
@@ -166,7 +165,7 @@ class WindowsCrashDump32Layer(segmented.SegmentedLayer):
|
||||
seg_first_bit = None
|
||||
# Some pages in this 32 bits array are mapped and some aren't
|
||||
else:
|
||||
for inner_bit_position in range(0, 32):
|
||||
for inner_bit_position in range(32):
|
||||
current_bit = outer_index * 32 + inner_bit_position
|
||||
page_mapped = ulong_bitmap & (1 << inner_bit_position)
|
||||
if page_mapped:
|
||||
@@ -220,9 +219,7 @@ class WindowsCrashDump32Layer(segmented.SegmentedLayer):
|
||||
for idx, (start_position, mapped_offset, length, _) in enumerate(segments):
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
"Segment {}: Position {:#x} Offset {:#x} Length {:#x}".format(
|
||||
idx, start_position, mapped_offset, length
|
||||
),
|
||||
f"Segment {idx}: Position {start_position:#x} Offset {mapped_offset:#x} Length {length:#x}",
|
||||
)
|
||||
|
||||
self._segments = segments
|
||||
|
||||
@@ -76,13 +76,13 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
self._index_shift = math.ceil(math.log2(struct.calcsize(self._entry_format)))
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def page_shift(cls) -> int:
|
||||
"""Page shift for the intel memory layers."""
|
||||
return cls._page_size_in_bits
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def page_size(cls) -> int:
|
||||
"""Page size for the intel memory layers.
|
||||
|
||||
@@ -91,25 +91,25 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
return 1 << cls._page_size_in_bits
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def page_mask(cls) -> int:
|
||||
"""Page mask for the intel memory layers."""
|
||||
return ~(cls.page_size - 1)
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def bits_per_register(cls) -> int:
|
||||
"""Returns the bits_per_register to determine the range of an
|
||||
IntelTranslationLayer."""
|
||||
return cls._bits_per_register
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def minimum_address(cls) -> int:
|
||||
return 0
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def maximum_address(cls) -> int:
|
||||
return (1 << cls._maxvirtaddr) - 1
|
||||
|
||||
@@ -251,12 +251,7 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
if INTEL_TRANSLATION_DEBUGGING:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
"Entry {} at index {} gives data {} as {}".format(
|
||||
hex(entry),
|
||||
hex(index),
|
||||
hex(struct.unpack(self._entry_format, entry_data)[0]),
|
||||
name,
|
||||
),
|
||||
f"Entry {hex(entry)} at index {hex(index)} gives data {hex(struct.unpack(self._entry_format, entry_data)[0])} as {name}",
|
||||
)
|
||||
|
||||
# Read out the new entry from memory
|
||||
|
||||
@@ -48,7 +48,7 @@ if HAS_LEECHCORE:
|
||||
try:
|
||||
self._handle = leechcorepyc.LeechCore(self._device)
|
||||
except TypeError:
|
||||
raise IOError(f"Unable to open LeechCore device {self._device}")
|
||||
raise OSError(f"Unable to open LeechCore device {self._device}")
|
||||
return self._handle
|
||||
|
||||
def fileno(self):
|
||||
|
||||
@@ -194,7 +194,7 @@ class PdbMSFStream(linear.LinearlyMappedLayer):
|
||||
) -> None:
|
||||
super().__init__(context, config_path, name, metadata)
|
||||
self._base_layer = self.config["base_layer"]
|
||||
self._pages = self.config.get("pages", None)
|
||||
self._pages = self.config.get("pages", [])
|
||||
self._pages_len = len(self._pages)
|
||||
if not self._pages:
|
||||
raise PDBFormatException(name, "Invalid/no pages specified")
|
||||
@@ -225,7 +225,7 @@ class PdbMSFStream(linear.LinearlyMappedLayer):
|
||||
returned = 0
|
||||
page_size = self._pdb_layer.page_size
|
||||
while length > 0:
|
||||
page = math.floor((offset + returned) / page_size)
|
||||
page = (offset + returned) // page_size
|
||||
page_position = (offset + returned) % page_size
|
||||
chunk_size = min(page_size - page_position, length)
|
||||
if page >= self._pages_len:
|
||||
|
||||
@@ -236,7 +236,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
|
||||
if self._architecture is None:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VV,
|
||||
f"QEVM architecture could not be determined",
|
||||
"QEVM architecture could not be determined",
|
||||
)
|
||||
|
||||
# Once all segments have been read, determine the PCI hole if any
|
||||
|
||||
@@ -140,7 +140,13 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
"""Returns the appropriate Node, interpreted from the Cell based on its
|
||||
Signature."""
|
||||
cell = self.get_cell(cell_offset)
|
||||
signature = cell.cast("string", max_length=2, encoding="latin-1")
|
||||
try:
|
||||
signature = cell.cast("string", max_length=2, encoding="latin-1")
|
||||
except (RegistryInvalidIndex, exceptions.InvalidAddressException):
|
||||
vollog.debug(
|
||||
f"Failed to get cell signature for cell (0x{cell.vol.offset:x})"
|
||||
)
|
||||
return cell
|
||||
if signature == "nk":
|
||||
return cell.u.KeyNode
|
||||
elif signature == "sk":
|
||||
@@ -156,9 +162,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
else:
|
||||
# It doesn't matter that we use KeyNode, we're just after the first two bytes
|
||||
vollog.debug(
|
||||
"Unknown Signature {} (0x{:x}) at offset {}".format(
|
||||
signature, cell.u.KeyNode.Signature, cell_offset
|
||||
)
|
||||
f"Unknown Signature {signature} (0x{cell.u.KeyNode.Signature:x}) at offset {cell_offset}"
|
||||
)
|
||||
return cell
|
||||
|
||||
@@ -178,9 +182,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
if not root_node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"):
|
||||
raise RegistryFormatException(
|
||||
self.name,
|
||||
"Encountered {} instead of _CM_KEY_NODE".format(
|
||||
root_node.vol.type_name
|
||||
),
|
||||
f"Encountered {root_node.vol.type_name} instead of _CM_KEY_NODE",
|
||||
)
|
||||
node_key = [root_node]
|
||||
if key.endswith("\\"):
|
||||
|
||||
@@ -29,7 +29,7 @@ except ImportError:
|
||||
|
||||
try:
|
||||
# Import so that the handler is found by the framework.class_subclasses callc
|
||||
import smb.SMBHandler # lgtm [py/unused-import]
|
||||
from smb import SMBHandler as SMBHandler # lgtm [py/unused-import]
|
||||
except ImportError:
|
||||
# If we fail to import this, it means that SMB handling won't be available
|
||||
pass
|
||||
@@ -57,7 +57,7 @@ def cascadeCloseFile(new_fp: IO[bytes], original_fp: IO[bytes]) -> IO[bytes]:
|
||||
return new_fp
|
||||
|
||||
|
||||
class ResourceAccessor(object):
|
||||
class ResourceAccessor:
|
||||
"""Object for opening URLs as files (downloading locally first if
|
||||
necessary)"""
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import re
|
||||
from typing import Generator, List, Tuple, Dict, Optional
|
||||
|
||||
from volatility3.framework.interfaces import layers
|
||||
from volatility3.framework.layers.scanners import multiregexp
|
||||
from volatility3.framework.layers.scanners import multiregexp as multiregexp
|
||||
|
||||
|
||||
class BytesScanner(layers.ScannerInterface):
|
||||
@@ -72,7 +72,7 @@ class MultiStringScanner(layers.ScannerInterface):
|
||||
return None
|
||||
|
||||
for char in value:
|
||||
trie[char] = trie.get(char, {})
|
||||
trie.setdefault(char, {})
|
||||
trie = trie[char]
|
||||
|
||||
# Mark the end of a string
|
||||
|
||||
@@ -6,7 +6,7 @@ import re
|
||||
from typing import Generator, List, Tuple
|
||||
|
||||
|
||||
class MultiRegexp(object):
|
||||
class MultiRegexp:
|
||||
"""Algorithm for multi-string matching."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
@@ -57,6 +57,10 @@ class VmwareLayer(segmented.SegmentedLayer):
|
||||
)
|
||||
|
||||
meta_layer = self.context.layers.get(self._meta_layer, None)
|
||||
if meta_layer is None:
|
||||
raise exceptions.LayerException(
|
||||
self._meta_layer, "VMware: Meta layer not found"
|
||||
)
|
||||
header_size = struct.calcsize(self.header_structure)
|
||||
data = meta_layer.read(0, header_size)
|
||||
magic, unknown, groupCount = struct.unpack(self.header_structure, data)
|
||||
|
||||
@@ -54,6 +54,7 @@ class XenCoreDumpLayer(elf.Elf64Layer):
|
||||
|
||||
segments = []
|
||||
self._segment_headers = []
|
||||
segment_names = None
|
||||
|
||||
for sindex in range(ehdr.e_shnum):
|
||||
shdr = self.context.object(
|
||||
|
||||
@@ -35,13 +35,13 @@ def convert_data_to_value(
|
||||
data_format: DataFormatInfo,
|
||||
) -> TUnion[int, float, bytes, str, bool]:
|
||||
"""Converts a series of bytes to a particular type of value."""
|
||||
if struct_type == int:
|
||||
if struct_type is int:
|
||||
return int.from_bytes(
|
||||
data, byteorder=data_format.byteorder, signed=data_format.signed
|
||||
)
|
||||
if struct_type == bool:
|
||||
if struct_type is bool:
|
||||
struct_format = "?"
|
||||
elif struct_type == float:
|
||||
elif struct_type is float:
|
||||
float_vals = "zzezfzzzd"
|
||||
if (
|
||||
data_format.length > len(float_vals)
|
||||
@@ -70,7 +70,7 @@ def convert_value_to_data(
|
||||
f"Written value is not of the correct type for {struct_type.__name__}"
|
||||
)
|
||||
|
||||
if struct_type == int and isinstance(value, int):
|
||||
if struct_type is int and isinstance(value, int):
|
||||
# Doubling up on the isinstance is for mypy
|
||||
return int.to_bytes(
|
||||
value,
|
||||
@@ -78,9 +78,9 @@ def convert_value_to_data(
|
||||
byteorder=data_format.byteorder,
|
||||
signed=data_format.signed,
|
||||
)
|
||||
if struct_type == bool:
|
||||
if struct_type is bool:
|
||||
struct_format = "?"
|
||||
elif struct_type == float:
|
||||
elif struct_type is float:
|
||||
float_vals = "zzezfzzzd"
|
||||
if (
|
||||
data_format.length > len(float_vals)
|
||||
@@ -152,7 +152,7 @@ class PrimitiveObject(interfaces.objects.ObjectInterface):
|
||||
type_name: str,
|
||||
object_info: interfaces.objects.ObjectInformation,
|
||||
data_format: DataFormatInfo,
|
||||
new_value: TUnion[int, float, bool, bytes, str] = None,
|
||||
new_value: Optional[TUnion[int, float, bool, bytes, str]] = None,
|
||||
**kwargs,
|
||||
) -> "PrimitiveObject":
|
||||
"""Creates the appropriate class and returns it so that the native type
|
||||
@@ -601,7 +601,7 @@ class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
inverse_choices[v] = k
|
||||
return inverse_choices
|
||||
|
||||
def lookup(self, value: int = None) -> str:
|
||||
def lookup(self, value: Optional[int] = None) -> str:
|
||||
"""Looks up an individual value and returns the associated name.
|
||||
|
||||
If multiple identifiers map to the same value, the first matching identifier will be returned
|
||||
@@ -690,7 +690,7 @@ class Array(interfaces.objects.ObjectInterface, collections.abc.Sequence):
|
||||
type_name: str,
|
||||
object_info: interfaces.objects.ObjectInformation,
|
||||
count: int = 0,
|
||||
subtype: templates.ObjectTemplate = None,
|
||||
subtype: Optional[templates.ObjectTemplate] = None,
|
||||
) -> None:
|
||||
super().__init__(context=context, type_name=type_name, object_info=object_info)
|
||||
self._vol["count"] = count
|
||||
|
||||
@@ -22,8 +22,8 @@ def bswap_32(value: int) -> int:
|
||||
|
||||
|
||||
def bswap_64(value: int) -> int:
|
||||
low = bswap_32((value >> 32))
|
||||
high = bswap_32((value & 0xFFFFFFFF))
|
||||
low = bswap_32(value >> 32)
|
||||
high = bswap_32(value & 0xFFFFFFFF)
|
||||
|
||||
return ((high << 32) | low) & 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import os
|
||||
import pathlib
|
||||
import zipfile
|
||||
from typing import Generator, List
|
||||
from importlib.util import find_spec
|
||||
|
||||
from volatility3 import schemas, symbols
|
||||
from volatility3.framework import constants, interfaces, renderers
|
||||
@@ -96,16 +97,12 @@ class IsfInfo(plugins.PluginInterface):
|
||||
if filter_item in isf_file:
|
||||
filtered_list.append(isf_file)
|
||||
|
||||
try:
|
||||
import jsonschema
|
||||
|
||||
if not self.config["validate"]:
|
||||
raise ImportError # Act as if we couldn't import if validation is turned off
|
||||
if find_spec("jsonschema") and self.config["validate"]:
|
||||
|
||||
def check_valid(data):
|
||||
return "True" if schemas.validate(data, True) else "False"
|
||||
|
||||
except ImportError:
|
||||
else:
|
||||
|
||||
def check_valid(data):
|
||||
return "Unknown"
|
||||
@@ -135,6 +132,7 @@ class IsfInfo(plugins.PluginInterface):
|
||||
valid = check_valid(data)
|
||||
except (UnicodeDecodeError, json.decoder.JSONDecodeError):
|
||||
vollog.warning(f"Invalid ISF: {entry}")
|
||||
continue
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
|
||||
@@ -119,7 +119,7 @@ class LayerWriter(plugins.PluginInterface):
|
||||
# Update the filename, which may have changed if a file
|
||||
# with the same name already existed.
|
||||
output_name = file_handle.preferred_filename
|
||||
except IOError as excp:
|
||||
except OSError as excp:
|
||||
yield 0, (f"Layer cannot be written to {output_name}: {excp}",)
|
||||
|
||||
yield 0, (f"Layer has been written to {output_name}",)
|
||||
|
||||
@@ -22,7 +22,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Recovers bash command history from memory."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -33,7 +33,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
|
||||
@@ -15,7 +15,7 @@ class Boottime(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
|
||||
"""Shows the time the system was started"""
|
||||
|
||||
_required_framework_version = (2, 11, 0)
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -26,7 +26,7 @@ class Boottime(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -49,8 +49,8 @@ class CapabilitiesData:
|
||||
class Capabilities(plugins.PluginInterface):
|
||||
"""Lists process capabilities"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_required_framework_version = (2, 13, 0)
|
||||
_version = (1, 1, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -61,7 +61,7 @@ class Capabilities(plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pids",
|
||||
@@ -136,7 +136,7 @@ class Capabilities(plugins.PluginInterface):
|
||||
comm=utility.array_to_string(task.comm),
|
||||
pid=int(task.pid),
|
||||
tgid=int(task.tgid),
|
||||
ppid=int(task.parent.pid),
|
||||
ppid=int(task.get_parent_pid()),
|
||||
euid=int(task.cred.euid),
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class Check_creds(interfaces.plugins.PluginInterface):
|
||||
"""Checks if any processes are sharing credential structures"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (2, 0, 1)
|
||||
_version = (2, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -23,7 +23,7 @@ class Check_creds(interfaces.plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
@@ -55,7 +55,7 @@ class Check_creds(interfaces.plugins.PluginInterface):
|
||||
|
||||
for cred_addr, pids in creds.items():
|
||||
if len(pids) > 1:
|
||||
pid_str = ", ".join([str(pid) for pid in pids])
|
||||
pid_str = ", ".join(str(pid) for pid in pids)
|
||||
|
||||
fields = [
|
||||
format_hints.Hex(cred_addr),
|
||||
|
||||
@@ -53,7 +53,7 @@ class Check_idt(interfaces.plugins.PluginInterface):
|
||||
address_mask = self.context.layers[vmlinux.layer_name].address_mask
|
||||
|
||||
# hw handlers + system call
|
||||
check_idxs = list(range(0, 20)) + [128]
|
||||
check_idxs = list(range(20)) + [128]
|
||||
|
||||
if is_32bit:
|
||||
if vmlinux.has_type("gate_struct"):
|
||||
|
||||
@@ -103,7 +103,7 @@ class Check_syscall(plugins.PluginInterface):
|
||||
|
||||
try:
|
||||
func_addr = vmlinux.get_symbol(syscall_entry_func).address
|
||||
except exceptions.SymbolError as e:
|
||||
except exceptions.SymbolError:
|
||||
# if we can't find the disassemble function then bail and rely on a different method
|
||||
return 0
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class Elfs(plugins.PluginInterface):
|
||||
"""Lists all memory mapped ELF files for all processes."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (2, 0, 2)
|
||||
_version = (2, 0, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -36,7 +36,7 @@ class Elfs(plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from typing import Iterable, Tuple
|
||||
|
||||
from volatility3.framework import exceptions, renderers
|
||||
from volatility3.framework import renderers, interfaces
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import plugins
|
||||
from volatility3.framework.objects import utility
|
||||
@@ -16,8 +17,8 @@ vollog = logging.getLogger(__name__)
|
||||
class Envars(plugins.PluginInterface):
|
||||
"""Lists processes with their environment variables"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_required_framework_version = (2, 13, 0)
|
||||
_version = (2, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -29,7 +30,7 @@ class Envars(plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
@@ -39,84 +40,98 @@ class Envars(plugins.PluginInterface):
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_task_env_variables(
|
||||
context: interfaces.context.ContextInterface,
|
||||
task: interfaces.objects.ObjectInterface,
|
||||
env_area_max_size: int = 8192,
|
||||
) -> Iterable[Tuple[str, str]]:
|
||||
"""Yields environment variables for a given task.
|
||||
|
||||
Args:
|
||||
context: The plugin's operational context.
|
||||
task: The task object from which to extract environment variables.
|
||||
env_area_max_size: Maximum allowable size for the environment variables area.
|
||||
Tasks exceeding this size will be skipped. Default is 8192.
|
||||
|
||||
Yields:
|
||||
Tuples of (key, value) representing each environment variable.
|
||||
"""
|
||||
|
||||
task_name = utility.array_to_string(task.comm)
|
||||
task_pid = task.pid
|
||||
env_start = task.mm.env_start
|
||||
env_end = task.mm.env_end
|
||||
env_area_size = env_end - env_start
|
||||
if not (0 < env_area_size <= env_area_max_size):
|
||||
vollog.debug(
|
||||
f"Task {task_pid} {task_name} appears to have environment variables of size "
|
||||
f"{env_area_size} bytes which fails the sanity checking, will not extract "
|
||||
"any envars."
|
||||
)
|
||||
return None
|
||||
|
||||
# Get process layer to read envars from
|
||||
proc_layer_name = task.add_process_layer()
|
||||
if proc_layer_name is None:
|
||||
return None
|
||||
proc_layer = context.layers[proc_layer_name]
|
||||
|
||||
# Ensure the entire buffer is readable to prevent relying on exception handling
|
||||
if not proc_layer.is_valid(env_start, env_area_size):
|
||||
# Not mapped / swapped out
|
||||
vollog.debug(
|
||||
f"Unable to read environment variables for {task_pid} {task_name} starting at "
|
||||
f" virtual address 0x{env_start:x} for {env_area_size} bytes, will not "
|
||||
"extract any envars."
|
||||
)
|
||||
return None
|
||||
|
||||
# Read the full task environment variable buffer.
|
||||
envar_data = proc_layer.read(env_start, env_area_size)
|
||||
|
||||
# Parse envar data, envars are null terminated, keys and values are separated by '='
|
||||
envar_data = envar_data.rstrip(b"\x00")
|
||||
for envar_pair in envar_data.split(b"\x00"):
|
||||
try:
|
||||
env_key, env_value = envar_pair.decode().split("=", 1)
|
||||
except ValueError:
|
||||
# Some legitimate programs, like 'avahi-daemon', avoid reallocating the args
|
||||
# and instead exploit the fact that the environment variables area is contiguous
|
||||
# to the args. This allows them to include a longer process name in the listing,
|
||||
# causing overwrites and incorrect results. In such cases, it's better to abort
|
||||
# the current task rather than displaying misleading or incorrect output.
|
||||
break
|
||||
|
||||
yield env_key, env_value
|
||||
|
||||
def _generator(self, tasks):
|
||||
"""Generates a listing of processes along with environment variables"""
|
||||
|
||||
# walk the process list and return the envars
|
||||
for task in tasks:
|
||||
pid = task.pid
|
||||
|
||||
# get process name as string
|
||||
name = utility.array_to_string(task.comm)
|
||||
|
||||
# try and get task parent
|
||||
try:
|
||||
ppid = task.parent.pid
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"Unable to read parent pid for task {pid} {name}, setting ppid to 0."
|
||||
)
|
||||
ppid = 0
|
||||
|
||||
# kernel threads never have an mm as they do not have userland mappings
|
||||
try:
|
||||
mm = task.mm
|
||||
except exceptions.InvalidAddressException:
|
||||
# no mm so cannot get envars
|
||||
vollog.debug(
|
||||
f"Unable to access mm for task {pid} {name} it is likely a kernel thread, will not extract any envars."
|
||||
)
|
||||
mm = None
|
||||
if task.is_kernel_thread:
|
||||
continue
|
||||
|
||||
# if mm exists attempt to get envars
|
||||
if mm:
|
||||
# get process layer to read envars from
|
||||
proc_layer_name = task.add_process_layer()
|
||||
if proc_layer_name is None:
|
||||
vollog.debug(
|
||||
f"Unable to construct process layer for task {pid} {name}, will not extract any envars."
|
||||
)
|
||||
continue
|
||||
proc_layer = self.context.layers[proc_layer_name]
|
||||
task_pid = task.pid
|
||||
task_name = utility.array_to_string(task.comm)
|
||||
task_ppid = task.get_parent_pid()
|
||||
|
||||
# get the size of the envars with sanity checking
|
||||
envars_size = task.mm.env_end - task.mm.env_start
|
||||
if not (0 < envars_size <= 8192):
|
||||
vollog.debug(
|
||||
f"Task {pid} {name} appears to have envars of size {envars_size} bytes which fails the sanity checking, will not extract any envars."
|
||||
)
|
||||
continue
|
||||
|
||||
# attempt to read all envars data
|
||||
try:
|
||||
envar_data = proc_layer.read(task.mm.env_start, envars_size)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"Unable to read full envars for {pid} {name} starting at virtual offset {hex(task.mm.env_start)} for {envars_size} bytes, will not extract any envars."
|
||||
)
|
||||
continue
|
||||
|
||||
# parse envar data, envars are null terminated, keys and values are separated by '='
|
||||
envar_data = envar_data.rstrip(b"\x00")
|
||||
for envar_pair in envar_data.split(b"\x00"):
|
||||
try:
|
||||
key, value = envar_pair.decode().split("=", 1)
|
||||
except ValueError:
|
||||
vollog.debug(
|
||||
f"Unable to extract envars for {pid} {name} starting at virtual offset {hex(task.mm.env_start)}, they don't appear to be '=' separated"
|
||||
)
|
||||
continue
|
||||
yield (0, (pid, ppid, name, key, value))
|
||||
for env_key, env_value in self.get_task_env_variables(self.context, task):
|
||||
yield (0, (task_pid, task_ppid, task_name, env_key, env_value))
|
||||
|
||||
def run(self):
|
||||
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
|
||||
|
||||
return renderers.TreeGrid(
|
||||
[("PID", int), ("PPID", int), ("COMM", str), ("KEY", str), ("VALUE", str)],
|
||||
self._generator(
|
||||
pslist.PsList.list_tasks(
|
||||
self.context, self.config["kernel"], filter_func=filter_func
|
||||
)
|
||||
),
|
||||
tasks = pslist.PsList.list_tasks(
|
||||
self.context, self.config["kernel"], filter_func=filter_func
|
||||
)
|
||||
|
||||
headers = [
|
||||
("PID", int),
|
||||
("PPID", int),
|
||||
("COMM", str),
|
||||
("KEY", str),
|
||||
("VALUE", str),
|
||||
]
|
||||
|
||||
return renderers.TreeGrid(headers, self._generator(tasks))
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
import logging
|
||||
import io
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Type, List, Dict, Tuple
|
||||
from volatility3.framework import constants, exceptions, interfaces
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import (
|
||||
format_hints,
|
||||
TreeGrid,
|
||||
NotAvailableValue,
|
||||
UnreadableValue,
|
||||
)
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.constants import architectures
|
||||
from volatility3.framework.symbols import linux
|
||||
|
||||
# Image manipulation functions are kept in the plugin,
|
||||
# to prevent a general exit on missing PIL (pillow) dependency.
|
||||
try:
|
||||
from PIL import Image
|
||||
|
||||
has_pil = True
|
||||
except ImportError:
|
||||
has_pil = False
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Framebuffer:
|
||||
"""Framebuffer object internal representation. This is useful to unify a framebuffer with precalculated
|
||||
properties and pass it through functions conveniently."""
|
||||
|
||||
id: str
|
||||
xres_virtual: int
|
||||
yres_virtual: int
|
||||
line_length: int
|
||||
bpp: int
|
||||
"""Bits Per Pixel"""
|
||||
size: int
|
||||
color_fields: Dict[str, Tuple[int, int, int]]
|
||||
fb_info: interfaces.objects.ObjectInterface
|
||||
|
||||
|
||||
class Fbdev(interfaces.plugins.PluginInterface):
|
||||
"""Extract framebuffers from the fbdev graphics subsystem"""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_required_framework_version = (2, 11, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Linux kernel",
|
||||
architectures=architectures.LINUX_ARCHS,
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linuxutils", component=linux.LinuxUtilities, version=(2, 2, 0)
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="dump",
|
||||
description="Dump framebuffers",
|
||||
default=False,
|
||||
optional=True,
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def parse_fb_pixel_bitfields(
|
||||
cls, fb_var_screeninfo: interfaces.objects.ObjectInterface
|
||||
) -> Dict[str, Tuple[int, int, int]]:
|
||||
"""Organize a framebuffer pixel format into a dictionary.
|
||||
This is needed to know the position and bitlength of a color inside
|
||||
a pixel.
|
||||
|
||||
Args:
|
||||
fb_var_screeninfo: a fb_var_screeninfo kernel object instance
|
||||
|
||||
Returns:
|
||||
The color fields mappings
|
||||
|
||||
Documentation:
|
||||
include/uapi/linux/fb.h:
|
||||
struct fb_bitfield {
|
||||
__u32 offset; /* beginning of bitfield */
|
||||
__u32 length; /* length of bitfield */
|
||||
__u32 msb_right; /* != 0 : Most significant bit is right */
|
||||
};
|
||||
"""
|
||||
# Naturally order by RGBA
|
||||
color_mappings = [
|
||||
("R", fb_var_screeninfo.red),
|
||||
("G", fb_var_screeninfo.green),
|
||||
("B", fb_var_screeninfo.blue),
|
||||
("A", fb_var_screeninfo.transp),
|
||||
]
|
||||
color_fields = {}
|
||||
for color_code, fb_bitfield in color_mappings:
|
||||
color_fields[color_code] = (
|
||||
int(fb_bitfield.offset),
|
||||
int(fb_bitfield.length),
|
||||
int(fb_bitfield.msb_right),
|
||||
)
|
||||
return color_fields
|
||||
|
||||
@classmethod
|
||||
def convert_fb_raw_buffer_to_image(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
kernel_name: str,
|
||||
fb: Framebuffer,
|
||||
):
|
||||
"""Convert raw framebuffer pixels to an image.
|
||||
|
||||
Args:
|
||||
fb: the relevant Framebuffer object
|
||||
|
||||
Returns:
|
||||
A PIL Image object
|
||||
|
||||
Documentation:
|
||||
include/uapi/linux/fb.h:
|
||||
/* Interpretation of offset for color fields: All offsets are from the right,
|
||||
* inside a "pixel" value, which is exactly 'bits_per_pixel' wide (means: you
|
||||
* can use the offset as right argument to <<). A pixel afterwards is a bit
|
||||
* stream and is written to video memory as that unmodified.
|
||||
"""
|
||||
kernel = context.modules[kernel_name]
|
||||
kernel_layer = context.layers[kernel.layer_name]
|
||||
|
||||
raw_pixels = io.BytesIO(kernel_layer.read(fb.fb_info.screen_base, fb.size))
|
||||
bytes_per_pixel = fb.bpp // 8
|
||||
image = Image.new("RGBA", (fb.xres_virtual, fb.yres_virtual))
|
||||
|
||||
# This is not designed to be extremely fast (numpy isn't available),
|
||||
# but convenient and dynamic for any color field layout.
|
||||
for y in range(fb.yres_virtual):
|
||||
for x in range(fb.xres_virtual):
|
||||
raw_pixel = int.from_bytes(raw_pixels.read(bytes_per_pixel), "little")
|
||||
pixel = [0, 0, 0, 255]
|
||||
# The framebuffer is expected to have been correctly constructed,
|
||||
# especially by parse_fb_pixel_bitfields, to get the needed RGBA mappings.
|
||||
for i, color_code in enumerate(["R", "G", "B", "A"]):
|
||||
offset, length, msb_right = fb.color_fields[color_code]
|
||||
if length == 0:
|
||||
continue
|
||||
color_value = (raw_pixel >> offset) & (2**length - 1)
|
||||
if msb_right:
|
||||
# Reverse bit order
|
||||
color_value = int(
|
||||
"{:0{length}b}".format(color_value, length=length)[::-1], 2
|
||||
)
|
||||
pixel[i] = color_value
|
||||
image.putpixel((x, y), tuple(pixel))
|
||||
|
||||
return image
|
||||
|
||||
@classmethod
|
||||
def dump_fb(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
kernel_name: str,
|
||||
open_method: Type[interfaces.plugins.FileHandlerInterface],
|
||||
fb: Framebuffer,
|
||||
convert_to_png_image: bool,
|
||||
) -> str:
|
||||
"""Dump a Framebuffer buffer to disk.
|
||||
|
||||
Args:
|
||||
fb: the relevant Framebuffer object
|
||||
convert_to_image: a boolean specifying if the buffer should be converted to an image
|
||||
|
||||
Returns:
|
||||
The filename of the dumped buffer.
|
||||
"""
|
||||
kernel = context.modules[kernel_name]
|
||||
kernel_layer = context.layers[kernel.layer_name]
|
||||
id = "N-A" if isinstance(fb.id, NotAvailableValue) else fb.id
|
||||
base_filename = f"{id}_{fb.xres_virtual}x{fb.yres_virtual}_{fb.bpp}bpp"
|
||||
if convert_to_png_image:
|
||||
image_object = cls.convert_fb_raw_buffer_to_image(context, kernel_name, fb)
|
||||
raw_io_output = io.BytesIO()
|
||||
image_object.save(raw_io_output, "PNG")
|
||||
final_fb_buffer = raw_io_output.getvalue()
|
||||
filename = f"{base_filename}.png"
|
||||
else:
|
||||
final_fb_buffer = kernel_layer.read(fb.fb_info.screen_base, fb.size)
|
||||
filename = f"{base_filename}.raw"
|
||||
|
||||
with open_method(filename) as f:
|
||||
f.write(final_fb_buffer)
|
||||
return f.preferred_filename
|
||||
|
||||
@classmethod
|
||||
def parse_fb_info(
|
||||
cls,
|
||||
fb_info: interfaces.objects.ObjectInterface,
|
||||
) -> Framebuffer:
|
||||
"""Parse an fb_info struct
|
||||
Args:
|
||||
fb_info: an fb_info kernel object live instance
|
||||
|
||||
Returns:
|
||||
A Framebuffer object
|
||||
|
||||
Documentation:
|
||||
https://docs.kernel.org/fb/api.html:
|
||||
- struct fb_fix_screeninfo stores device independent unchangeable information about the frame buffer device and the current format.
|
||||
Those information can't be directly modified by applications, but can be changed by the driver when an application modifies the format.
|
||||
- struct fb_var_screeninfo stores device independent changeable information about a frame buffer device, its current format and video mode,
|
||||
as well as other miscellaneous parameters.
|
||||
"""
|
||||
id = utility.array_to_string(fb_info.fix.id) or NotAvailableValue()
|
||||
color_fields = None
|
||||
|
||||
# 0 = color, 1 = grayscale, >1 = FOURCC
|
||||
if fb_info.var.grayscale in [0, 1]:
|
||||
color_fields = cls.parse_fb_pixel_bitfields(fb_info.var)
|
||||
|
||||
# There a lot of tricky pixel formats used by drivers and vendors in include/uapi/linux/videodev2.h.
|
||||
# As Volatility3 is not a video format converter, it is best to play it safe and let the user parse
|
||||
# the raw data manually (with ffmpeg for example).
|
||||
elif fb_info.var.grayscale > 1:
|
||||
fourcc = linux.LinuxUtilities.convert_fourcc_code(fb_info.var.grayscale)
|
||||
warn_msg = f"""Framebuffer "{id}" uses a FOURCC pixel format "{fourcc}" that isn't natively supported.
|
||||
You can try using ffmpeg to decode the raw buffer. Example usage:
|
||||
"ffmpeg -pix_fmts" to list supported formats, then
|
||||
"ffmpeg -f rawvideo -video_size {fb_info.var.xres_virtual}x{fb_info.var.yres_virtual} -i <FILENAME>.raw -pix_fmt <FORMAT> output.png"."""
|
||||
vollog.warning(warn_msg)
|
||||
|
||||
# Prefer using the virtual resolution, instead of the visible one.
|
||||
# This prevents missing non-visible data stored in the framebuffer.
|
||||
fb = Framebuffer(
|
||||
id,
|
||||
xres_virtual=fb_info.var.xres_virtual,
|
||||
yres_virtual=fb_info.var.yres_virtual,
|
||||
line_length=fb_info.fix.line_length,
|
||||
bpp=fb_info.var.bits_per_pixel,
|
||||
size=fb_info.var.yres_virtual * fb_info.fix.line_length,
|
||||
color_fields=color_fields,
|
||||
fb_info=fb_info,
|
||||
)
|
||||
|
||||
return fb
|
||||
|
||||
def _generator(self):
|
||||
|
||||
if not has_pil:
|
||||
vollog.error(
|
||||
"PIL (pillow) module is required to use this plugin. Please install it manually or through pyproject.toml."
|
||||
)
|
||||
return None
|
||||
|
||||
kernel_name = self.config["kernel"]
|
||||
kernel = self.context.modules[kernel_name]
|
||||
|
||||
if not kernel.has_symbol("num_registered_fb"):
|
||||
raise exceptions.SymbolError(
|
||||
"num_registered_fb",
|
||||
kernel.symbol_table_name,
|
||||
"The provided symbol does not exist in the symbol table. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt.",
|
||||
)
|
||||
|
||||
num_registered_fb = kernel.object_from_symbol("num_registered_fb")
|
||||
if num_registered_fb < 1:
|
||||
vollog.info("No registered framebuffer in the fbdev API.")
|
||||
return None
|
||||
|
||||
registered_fb = kernel.object_from_symbol("registered_fb")
|
||||
fb_info_list = utility.array_of_pointers(
|
||||
registered_fb,
|
||||
num_registered_fb,
|
||||
kernel.symbol_table_name + constants.BANG + "fb_info",
|
||||
self.context,
|
||||
)
|
||||
|
||||
for fb_info in fb_info_list:
|
||||
fb = self.parse_fb_info(fb_info)
|
||||
file_output = "Disabled"
|
||||
if self.config["dump"]:
|
||||
try:
|
||||
file_output = self.dump_fb(
|
||||
self.context, kernel_name, self.open, fb, bool(fb.color_fields)
|
||||
)
|
||||
file_output = str(file_output)
|
||||
except exceptions.InvalidAddressException as excp:
|
||||
vollog.error(
|
||||
f'Layer {excp.layer_name} failed to read address {hex(excp.invalid_address)} when dumping framebuffer "{fb.id}".'
|
||||
)
|
||||
file_output = UnreadableValue()
|
||||
|
||||
try:
|
||||
fb_device_name = utility.pointer_to_string(
|
||||
fb.fb_info.dev.kobj.name, 256
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
fb_device_name = NotAvailableValue()
|
||||
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
format_hints.Hex(fb.fb_info.screen_base),
|
||||
fb_device_name,
|
||||
fb.id,
|
||||
fb.size,
|
||||
f"{fb.xres_virtual}x{fb.yres_virtual}",
|
||||
fb.bpp,
|
||||
"RUNNING" if fb.fb_info.state == 0 else "SUSPENDED",
|
||||
file_output,
|
||||
),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
columns = [
|
||||
("Address", format_hints.Hex),
|
||||
("Device", str),
|
||||
("ID", str),
|
||||
("Size", int),
|
||||
("Virtual resolution", str),
|
||||
("BPP", int),
|
||||
("State", str),
|
||||
("Filename", str),
|
||||
]
|
||||
|
||||
return TreeGrid(
|
||||
columns,
|
||||
self._generator(),
|
||||
)
|
||||
@@ -149,7 +149,7 @@ class ABCKmsg(ABC):
|
||||
# This might seem insignificant but it could cause some issues
|
||||
# when compared with userland tool results or when used in
|
||||
# timelines.
|
||||
return "%lu.%06lu" % (nsec / 1000000000, (nsec % 1000000000) / 1000)
|
||||
return f"{nsec / 1000000000:lu}.{(nsec % 1000000000) / 1000:06lu}"
|
||||
|
||||
def get_timestamp_in_sec_str(self, obj) -> str:
|
||||
# obj could be log, printk_log or printk_info
|
||||
@@ -166,7 +166,7 @@ class ABCKmsg(ABC):
|
||||
|
||||
def get_caller_text(self, caller_id):
|
||||
caller_name = "CPU" if caller_id & 0x80000000 else "Task"
|
||||
caller = "%s(%u)" % (caller_name, caller_id & ~0x80000000)
|
||||
caller = f"{caller_name}({caller_id & ~0x80000000:u})"
|
||||
return caller
|
||||
|
||||
def get_prefix(self, obj) -> Tuple[int, int, str, str]:
|
||||
|
||||
@@ -20,7 +20,7 @@ class Kthreads(plugins.PluginInterface):
|
||||
"""Enumerates kthread functions"""
|
||||
|
||||
_required_framework_version = (2, 11, 0)
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -34,7 +34,7 @@ class Kthreads(plugins.PluginInterface):
|
||||
name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0)
|
||||
|
||||
@@ -21,7 +21,7 @@ class LibraryList(interfaces.plugins.PluginInterface):
|
||||
"""Enumerate libraries loaded into processes"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -32,7 +32,7 @@ class LibraryList(interfaces.plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pids",
|
||||
|
||||
@@ -54,8 +54,7 @@ class Lsmod(plugins.PluginInterface):
|
||||
|
||||
table_name = modules.vol.type_name.split(constants.BANG)[0]
|
||||
|
||||
for module in modules.to_list(table_name + constants.BANG + "module", "list"):
|
||||
yield module
|
||||
yield from modules.to_list(table_name + constants.BANG + "module", "list")
|
||||
|
||||
def _generator(self):
|
||||
try:
|
||||
|
||||
@@ -110,7 +110,7 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Lists open files for each processes."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (2, 0, 1)
|
||||
_version = (2, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -121,7 +121,7 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0)
|
||||
|
||||
@@ -18,7 +18,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -29,7 +29,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
|
||||
@@ -36,7 +36,7 @@ class MountInfo(plugins.PluginInterface):
|
||||
"""Lists mount points on processes mount namespaces"""
|
||||
|
||||
_required_framework_version = (2, 2, 0)
|
||||
_version = (1, 2, 2)
|
||||
_version = (1, 2, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -47,7 +47,7 @@ class MountInfo(plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0)
|
||||
|
||||
@@ -462,7 +462,7 @@ class InodePages(plugins.PluginInterface):
|
||||
f.seek(current_fp)
|
||||
f.write(page_bytes)
|
||||
|
||||
except IOError as e:
|
||||
except OSError as e:
|
||||
vollog.error("Unable to write to file (%s): %s", filename, e)
|
||||
|
||||
def _generator(self):
|
||||
@@ -483,6 +483,9 @@ class InodePages(plugins.PluginInterface):
|
||||
if inode_in.path == self.config["find"]:
|
||||
inode = inode_in.inode
|
||||
break # Only the first match
|
||||
else:
|
||||
vollog.error("Unable to find inode with path %s", self.config["find"])
|
||||
return None
|
||||
|
||||
elif self.config["inode"]:
|
||||
inode = vmlinux.object("inode", self.config["inode"], absolute=True)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
from typing import List, Iterable
|
||||
|
||||
from volatility3.framework import renderers, interfaces, constants
|
||||
from volatility3.framework.symbols import linux
|
||||
@@ -19,7 +19,7 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
"""Enumerates processes through the PID hash table"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 2)
|
||||
_version = (1, 0, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -30,7 +30,7 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0)
|
||||
@@ -218,7 +218,7 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
|
||||
return None
|
||||
|
||||
def get_tasks(self) -> interfaces.objects.ObjectInterface:
|
||||
def get_tasks(self) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Enumerates processes through the PID hash table
|
||||
|
||||
Yields:
|
||||
@@ -231,14 +231,16 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
|
||||
yield from sorted(pid_func(), key=lambda t: (t.tgid, t.pid))
|
||||
|
||||
def _generator(
|
||||
self, decorate_comm: bool = False
|
||||
) -> interfaces.objects.ObjectInterface:
|
||||
def _generator(self, decorate_comm: bool = False):
|
||||
for task in self.get_tasks():
|
||||
offset, pid, tid, ppid, name, _creation_time = (
|
||||
pslist.PsList.get_task_fields(task, decorate_comm)
|
||||
task_fields = pslist.PsList.get_task_fields(task, decorate_comm)
|
||||
fields = (
|
||||
format_hints.Hex(task_fields.offset),
|
||||
task_fields.user_pid,
|
||||
task_fields.user_tid,
|
||||
task_fields.user_ppid,
|
||||
task_fields.name,
|
||||
)
|
||||
fields = format_hints.Hex(offset), pid, tid, ppid, name
|
||||
yield 0, fields
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -21,7 +21,7 @@ class Maps(plugins.PluginInterface):
|
||||
"""Lists all memory maps for all processes."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
MAXSIZE_DEFAULT = 1024 * 1024 * 1024 # 1 Gb
|
||||
|
||||
@@ -35,7 +35,7 @@ class Maps(plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
@@ -125,9 +125,7 @@ class Maps(plugins.PluginInterface):
|
||||
proc_layer_name = task.add_process_layer()
|
||||
except exceptions.InvalidAddressException as excp:
|
||||
vollog.debug(
|
||||
"Process {}: invalid address {} in layer {}".format(
|
||||
pid, excp.invalid_address, excp.layer_name
|
||||
)
|
||||
f"Process {pid}: invalid address {excp.invalid_address} in layer {excp.layer_name}"
|
||||
)
|
||||
return None
|
||||
vm_size = vm_end - vm_start
|
||||
@@ -165,7 +163,9 @@ class Maps(plugins.PluginInterface):
|
||||
address_list = self.config.get("address", None)
|
||||
if not address_list:
|
||||
# do not filter as no address_list was supplied
|
||||
vma_filter_func = lambda _: True
|
||||
def vma_filter_func(_):
|
||||
return True
|
||||
|
||||
else:
|
||||
# filter for any vm_start that matches the supplied address config
|
||||
def vma_filter_function(x: interfaces.objects.ObjectInterface) -> bool:
|
||||
|
||||
@@ -14,8 +14,8 @@ from volatility3.plugins.linux import pslist
|
||||
class PsAux(plugins.PluginInterface):
|
||||
"""Lists processes with their command line arguments"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_required_framework_version = (2, 13, 0)
|
||||
_version = (1, 1, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -27,7 +27,7 @@ class PsAux(plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
@@ -98,14 +98,8 @@ class PsAux(plugins.PluginInterface):
|
||||
# walk the process list and report the arguments
|
||||
for task in tasks:
|
||||
pid = task.pid
|
||||
|
||||
try:
|
||||
ppid = task.parent.pid
|
||||
except exceptions.InvalidAddressException:
|
||||
ppid = 0
|
||||
|
||||
ppid = task.get_parent_pid()
|
||||
name = utility.array_to_string(task.comm)
|
||||
|
||||
args = self._get_command_line_args(task, name)
|
||||
|
||||
yield (0, (pid, ppid, name, args))
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
import datetime
|
||||
from typing import Any, Callable, Iterable, List, Tuple
|
||||
import dataclasses
|
||||
import contextlib
|
||||
from typing import Any, Callable, Iterable, List, Optional
|
||||
|
||||
from volatility3.framework import interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -14,11 +16,25 @@ from volatility3.plugins import timeliner
|
||||
from volatility3.plugins.linux import elfs
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class TaskFields:
|
||||
offset: int
|
||||
user_pid: int
|
||||
user_tid: int
|
||||
user_ppid: int
|
||||
name: str
|
||||
uid: Optional[int]
|
||||
gid: Optional[int]
|
||||
euid: Optional[int]
|
||||
egid: Optional[int]
|
||||
creation_time: Optional[datetime.datetime]
|
||||
|
||||
|
||||
class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Lists the processes present in a particular linux memory image."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (3, 0, 0)
|
||||
_required_framework_version = (2, 13, 0)
|
||||
_version = (4, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -58,7 +74,9 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[Any], bool]:
|
||||
def create_pid_filter(
|
||||
cls, pid_list: Optional[List[int]] = None
|
||||
) -> Callable[[Any], bool]:
|
||||
"""Constructs a filter function for process IDs.
|
||||
|
||||
Args:
|
||||
@@ -82,7 +100,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
@classmethod
|
||||
def get_task_fields(
|
||||
cls, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False
|
||||
) -> Tuple[int, int, int, int, str, datetime.datetime]:
|
||||
) -> TaskFields:
|
||||
"""Extract the fields needed for the final output
|
||||
|
||||
Args:
|
||||
@@ -91,21 +109,34 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
and of Kernel threads in square brackets.
|
||||
Defaults to False.
|
||||
Returns:
|
||||
A tuple with the fields to show in the plugin output.
|
||||
A TaskFields object with the fields to show in the plugin output.
|
||||
"""
|
||||
pid = task.tgid
|
||||
tid = task.pid
|
||||
ppid = task.parent.tgid if task.parent else 0
|
||||
name = utility.array_to_string(task.comm)
|
||||
start_time = task.get_create_time()
|
||||
if decorate_comm:
|
||||
if task.is_kernel_thread:
|
||||
name = f"[{name}]"
|
||||
elif task.is_user_thread:
|
||||
name = f"{{{name}}}"
|
||||
|
||||
task_fields = (task.vol.offset, pid, tid, ppid, name, start_time)
|
||||
return task_fields
|
||||
# This function may be called with a partially initialized/uninitialized task.
|
||||
# Ensure it always returns a valid TaskFields object, ready for use in a plugin.
|
||||
valid_cred = task.cred and task.cred.is_readable()
|
||||
creation_time = None
|
||||
with contextlib.suppress(Exception):
|
||||
creation_time = task.get_create_time()
|
||||
|
||||
return TaskFields(
|
||||
offset=task.vol.offset,
|
||||
user_pid=task.tgid,
|
||||
user_tid=task.pid,
|
||||
user_ppid=task.get_parent_pid(),
|
||||
name=name,
|
||||
uid=task.cred.uid if valid_cred else None,
|
||||
gid=task.cred.gid if valid_cred else None,
|
||||
euid=task.cred.euid if valid_cred else None,
|
||||
egid=task.cred.egid if valid_cred else None,
|
||||
creation_time=creation_time,
|
||||
)
|
||||
|
||||
def _get_file_output(self, task: interfaces.objects.ObjectInterface) -> str:
|
||||
"""Extract the elf for the process if requested
|
||||
@@ -179,17 +210,19 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
else:
|
||||
file_output = "Disabled"
|
||||
|
||||
offset, pid, tid, ppid, name, creation_time = self.get_task_fields(
|
||||
task, decorate_comm
|
||||
)
|
||||
task_fields = self.get_task_fields(task, decorate_comm)
|
||||
|
||||
yield 0, (
|
||||
format_hints.Hex(offset),
|
||||
pid,
|
||||
tid,
|
||||
ppid,
|
||||
name,
|
||||
creation_time or renderers.NotAvailableValue(),
|
||||
format_hints.Hex(task_fields.offset),
|
||||
task_fields.user_pid,
|
||||
task_fields.user_tid,
|
||||
task_fields.user_ppid,
|
||||
task_fields.name,
|
||||
task_fields.uid or renderers.NotAvailableValue(),
|
||||
task_fields.gid or renderers.NotAvailableValue(),
|
||||
task_fields.euid or renderers.NotAvailableValue(),
|
||||
task_fields.egid or renderers.NotAvailableValue(),
|
||||
task_fields.creation_time or renderers.NotAvailableValue(),
|
||||
file_output,
|
||||
)
|
||||
|
||||
@@ -238,6 +271,10 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
("TID", int),
|
||||
("PPID", int),
|
||||
("COMM", str),
|
||||
("UID", int),
|
||||
("GID", int),
|
||||
("EUID", int),
|
||||
("EGID", int),
|
||||
("CREATION TIME", datetime.datetime),
|
||||
("File output", str),
|
||||
]
|
||||
@@ -251,10 +288,11 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
for task in self.list_tasks(
|
||||
self.context, self.config["kernel"], filter_func, include_threads=True
|
||||
):
|
||||
offset, user_pid, user_tid, _user_ppid, name, creation_time = (
|
||||
self.get_task_fields(task)
|
||||
task_fields = self.get_task_fields(task)
|
||||
description = f"Process {task_fields.user_pid}/{task_fields.user_tid} {task_fields.name} ({task_fields.offset})"
|
||||
|
||||
yield (
|
||||
description,
|
||||
timeliner.TimeLinerType.CREATED,
|
||||
task_fields.creation_time,
|
||||
)
|
||||
|
||||
description = f"Process {user_pid}/{user_tid} {name} ({offset})"
|
||||
|
||||
yield (description, timeliner.TimeLinerType.CREATED, creation_time)
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
import logging
|
||||
from typing import Iterable, List, Tuple
|
||||
from typing import Iterable, List
|
||||
import struct
|
||||
from enum import Enum
|
||||
|
||||
from volatility3.framework import renderers, interfaces, symbols, constants, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.layers import scanners
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -27,8 +27,8 @@ class DescExitStateEnum(Enum):
|
||||
class PsScan(interfaces.plugins.PluginInterface):
|
||||
"""Scans for processes present in a particular linux image."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_required_framework_version = (2, 13, 0)
|
||||
_version = (2, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -38,37 +38,11 @@ class PsScan(interfaces.plugins.PluginInterface):
|
||||
description="Linux kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
def _get_task_fields(
|
||||
self, task: interfaces.objects.ObjectInterface
|
||||
) -> Tuple[int, int, int, str, str]:
|
||||
"""Extract the fields needed for the final output
|
||||
|
||||
Args:
|
||||
task: A task object from where to get the fields.
|
||||
Returns:
|
||||
A tuple with the fields to show in the plugin output.
|
||||
"""
|
||||
pid = task.tgid
|
||||
tid = task.pid
|
||||
ppid = 0
|
||||
|
||||
if task.parent.is_readable():
|
||||
ppid = task.parent.tgid
|
||||
name = utility.array_to_string(task.comm)
|
||||
exit_state = DescExitStateEnum(task.exit_state).name
|
||||
|
||||
task_fields = (
|
||||
format_hints.Hex(task.vol.offset),
|
||||
pid,
|
||||
tid,
|
||||
ppid,
|
||||
name,
|
||||
exit_state,
|
||||
)
|
||||
return task_fields
|
||||
|
||||
def _generator(self):
|
||||
"""Generates the tasks found from scanning."""
|
||||
|
||||
@@ -78,8 +52,18 @@ class PsScan(interfaces.plugins.PluginInterface):
|
||||
for task in self.scan_tasks(
|
||||
self.context, vmlinux_module_name, vmlinux.layer_name
|
||||
):
|
||||
row = self._get_task_fields(task)
|
||||
yield (0, row)
|
||||
task_fields = pslist.PsList.get_task_fields(task)
|
||||
exit_state = DescExitStateEnum(task.exit_state).name
|
||||
fields = (
|
||||
format_hints.Hex(task_fields.offset),
|
||||
task_fields.user_pid,
|
||||
task_fields.user_tid,
|
||||
task_fields.user_ppid,
|
||||
task_fields.name,
|
||||
exit_state,
|
||||
)
|
||||
|
||||
yield (0, fields)
|
||||
|
||||
@classmethod
|
||||
def scan_tasks(
|
||||
@@ -133,7 +117,7 @@ class PsScan(interfaces.plugins.PluginInterface):
|
||||
)
|
||||
elif len(kernel_layer.dependencies) == 0:
|
||||
vollog.error(
|
||||
f"Kernel layer has no dependencies, meaning there is no memory layer for this plugin to scan."
|
||||
"Kernel layer has no dependencies, meaning there is no memory layer for this plugin to scan."
|
||||
)
|
||||
raise exceptions.LayerException(
|
||||
kernel_layer_name, f"Layer {kernel_layer_name} has no dependencies"
|
||||
|
||||
@@ -12,8 +12,8 @@ class PsTree(interfaces.plugins.PluginInterface):
|
||||
"""Plugin for listing processes in a tree based on their parent process
|
||||
ID."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_required_framework_version = (2, 13, 0)
|
||||
_version = (1, 1, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -25,7 +25,7 @@ class PsTree(interfaces.plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
@@ -56,9 +56,9 @@ class PsTree(interfaces.plugins.PluginInterface):
|
||||
seen = set([pid])
|
||||
level = 0
|
||||
proc = self._tasks.get(pid)
|
||||
while proc and proc.parent and proc.parent.pid not in seen:
|
||||
while proc and proc.get_parent_pid() not in seen:
|
||||
if proc.is_thread_group_leader:
|
||||
parent_pid = proc.parent.pid
|
||||
parent_pid = proc.get_parent_pid()
|
||||
else:
|
||||
parent_pid = proc.tgid
|
||||
|
||||
@@ -101,13 +101,17 @@ class PsTree(interfaces.plugins.PluginInterface):
|
||||
def yield_processes(pid):
|
||||
task = self._tasks[pid]
|
||||
|
||||
offset, pid, tid, ppid, name, _creation_time = (
|
||||
pslist.PsList.get_task_fields(task, decorate_comm)
|
||||
task_fields = pslist.PsList.get_task_fields(task, decorate_comm)
|
||||
fields = (
|
||||
format_hints.Hex(task_fields.offset),
|
||||
task_fields.user_pid,
|
||||
task_fields.user_tid,
|
||||
task_fields.user_ppid,
|
||||
task_fields.name,
|
||||
)
|
||||
fields = format_hints.Hex(offset), pid, tid, ppid, name
|
||||
yield (self._levels[tid] - 1, fields)
|
||||
yield (self._levels[task_fields.user_tid] - 1, fields)
|
||||
|
||||
for child_pid in sorted(self._children.get(tid, [])):
|
||||
for child_pid in sorted(self._children.get(task_fields.user_tid, [])):
|
||||
yield from yield_processes(child_pid)
|
||||
|
||||
for pid, level in self._levels.items():
|
||||
|
||||
@@ -19,7 +19,7 @@ class Ptrace(plugins.PluginInterface):
|
||||
"""Enumerates ptrace's tracer and tracee tasks"""
|
||||
|
||||
_required_framework_version = (2, 10, 0)
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -30,7 +30,7 @@ class Ptrace(plugins.PluginInterface):
|
||||
architectures=architectures.LINUX_ARCHS,
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -372,7 +372,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
|
||||
bt_sock = sock.cast("bt_sock")
|
||||
|
||||
def bt_addr(addr):
|
||||
return ":".join(reversed(["%02x" % x for x in addr.b]))
|
||||
return ":".join(reversed([f"{x:02x}" for x in addr.b]))
|
||||
|
||||
src_addr = src_port = dst_addr = dst_port = None
|
||||
bt_protocol = bt_sock.get_protocol()
|
||||
@@ -438,7 +438,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
"""Lists all network connections for all processes."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (3, 0, 1)
|
||||
_version = (3, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -455,7 +455,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
name="lsof", plugin=lsof.Lsof, version=(2, 0, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0)
|
||||
|
||||
@@ -21,7 +21,7 @@ class VmaRegExScan(plugins.PluginInterface):
|
||||
"""Scans all virtual memory areas for tasks using RegEx."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
MAXSIZE_DEFAULT = 128
|
||||
|
||||
@@ -35,7 +35,7 @@ class VmaRegExScan(plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
|
||||
@@ -18,7 +18,7 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
|
||||
"""Scans all virtual memory areas for tasks using yara."""
|
||||
|
||||
_required_framework_version = (2, 4, 0)
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -31,7 +31,7 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
|
||||
optional=True,
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0)
|
||||
|
||||
@@ -60,7 +60,7 @@ class Check_sysctl(plugins.PluginInterface):
|
||||
return var_str
|
||||
|
||||
def _process_sysctl_list(self, kernel, sysctl_list, recursive=0):
|
||||
if type(sysctl_list) == volatility3.framework.objects.Pointer:
|
||||
if type(sysctl_list) is volatility3.framework.objects.Pointer:
|
||||
sysctl_list = sysctl_list.dereference().cast("sysctl_oid_list")
|
||||
|
||||
sysctl = sysctl_list.slh_first
|
||||
@@ -93,10 +93,9 @@ class Check_sysctl(plugins.PluginInterface):
|
||||
val = self._parse_global_variable_sysctls(kernel, name)
|
||||
elif ctltype == "CTLTYPE_NODE":
|
||||
if sysctl.oid_handler == 0:
|
||||
for info in self._process_sysctl_list(
|
||||
yield from self._process_sysctl_list(
|
||||
kernel, sysctl.oid_arg1, recursive=1
|
||||
):
|
||||
yield info
|
||||
)
|
||||
|
||||
val = "Node"
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ class Kauth_scopes(interfaces.plugins.PluginInterface):
|
||||
(
|
||||
identifier,
|
||||
format_hints.Hex(scope.ks_idata),
|
||||
len([l for l in scope.get_listeners()]),
|
||||
len([listener for listener in scope.get_listeners()]),
|
||||
format_hints.Hex(callback),
|
||||
module_name,
|
||||
symbol_name,
|
||||
|
||||
@@ -119,8 +119,7 @@ class Kevents(interfaces.plugins.PluginInterface):
|
||||
return None
|
||||
|
||||
for klist in klist_array:
|
||||
for kn in mac.MacUtilities.walk_slist(klist, "kn_link"):
|
||||
yield kn
|
||||
yield from mac.MacUtilities.walk_slist(klist, "kn_link")
|
||||
|
||||
@classmethod
|
||||
def _get_task_kevents(cls, kernel, task):
|
||||
|
||||
@@ -49,8 +49,7 @@ class Mount(plugins.PluginInterface):
|
||||
|
||||
list_head = kernel.object_from_symbol(symbol_name="mountlist")
|
||||
|
||||
for mount in mac.MacUtilities.walk_tailq(list_head, "mnt_list"):
|
||||
yield mount
|
||||
yield from mac.MacUtilities.walk_tailq(list_head, "mnt_list")
|
||||
|
||||
def _generator(self):
|
||||
for mount in self.list_mounts(self.context, self.config["kernel"]):
|
||||
|
||||
@@ -115,9 +115,7 @@ class Maps(interfaces.plugins.PluginInterface):
|
||||
proc_layer_name = task.add_process_layer()
|
||||
except exceptions.InvalidAddressException as excp:
|
||||
vollog.debug(
|
||||
"Process {}: invalid address {} in layer {}".format(
|
||||
pid, excp.invalid_address, excp.layer_name
|
||||
)
|
||||
f"Process {pid}: invalid address {excp.invalid_address} in layer {excp.layer_name}"
|
||||
)
|
||||
return None
|
||||
vm_size = vm_end - vm_start
|
||||
@@ -154,7 +152,9 @@ class Maps(interfaces.plugins.PluginInterface):
|
||||
address_list = self.config.get("address", None)
|
||||
if not address_list:
|
||||
# do not filter as no address_list was supplied
|
||||
vma_filter_func = lambda _: True
|
||||
def vma_filter_func(_):
|
||||
return True
|
||||
|
||||
else:
|
||||
# filter for any vm_start that matches the supplied address config
|
||||
def vma_filter_function(task: interfaces.objects.ObjectInterface) -> bool:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Callable, Dict, Iterable, List
|
||||
from typing import Callable, Dict, Iterable, List, Optional
|
||||
|
||||
from volatility3.framework import exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -82,8 +82,12 @@ class PsList(interfaces.plugins.PluginInterface):
|
||||
return list_tasks
|
||||
|
||||
@classmethod
|
||||
def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[int], bool]:
|
||||
filter_func = lambda _: False
|
||||
def create_pid_filter(
|
||||
cls, pid_list: Optional[List[int]] = None
|
||||
) -> Callable[[int], bool]:
|
||||
def filter_func(_):
|
||||
return False
|
||||
|
||||
# FIXME: mypy #4973 or #2608
|
||||
pid_list = pid_list or []
|
||||
filter_list = [x for x in pid_list if x is not None]
|
||||
|
||||
@@ -54,7 +54,9 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
self.automagics: Optional[List[interfaces.automagic.AutomagicInterface]] = None
|
||||
|
||||
@classmethod
|
||||
def get_usable_plugins(cls, selected_list: List[str] = None) -> List[Type]:
|
||||
def get_usable_plugins(
|
||||
cls, selected_list: Optional[List[str]] = None
|
||||
) -> List[Type]:
|
||||
# Initialize for the run
|
||||
plugin_list = list(framework.class_subclasses(TimeLinerInterface))
|
||||
|
||||
@@ -143,9 +145,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
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
|
||||
)
|
||||
f"Multiple timestamps for the same plugin/file combination found: {plugin_name} {item}"
|
||||
)
|
||||
times[timestamp_type] = timestamp
|
||||
self.timeline[(plugin_name, item)] = times
|
||||
@@ -206,8 +206,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
)
|
||||
vollog.log(logging.DEBUG, traceback.format_exc())
|
||||
|
||||
for data_item in sorted(data, key=self._sort_function):
|
||||
yield data_item
|
||||
yield from sorted(data, key=self._sort_function)
|
||||
|
||||
# Write out a body file if necessary
|
||||
if self.config.get("create-bodyfile", True):
|
||||
|
||||
@@ -84,9 +84,7 @@ class CmdLine(interfaces.plugins.PluginInterface):
|
||||
result_text = f"Required memory at {exp.invalid_address:#x} is not valid (process exited?)"
|
||||
|
||||
except exceptions.InvalidAddressException as exp:
|
||||
result_text = "Process {}: Required memory at {:#x} is not valid (incomplete layer {}?)".format(
|
||||
proc_id, exp.invalid_address, exp.layer_name
|
||||
)
|
||||
result_text = f"Process {proc_id}: Required memory at {exp.invalid_address:#x} is not valid (incomplete layer {exp.layer_name}?)"
|
||||
|
||||
yield (0, (proc.UniqueProcessId, process_name, result_text))
|
||||
|
||||
|
||||
@@ -95,9 +95,7 @@ class Consoles(interfaces.plugins.PluginInterface):
|
||||
|
||||
except exceptions.InvalidAddressException as excp:
|
||||
vollog.debug(
|
||||
"Process {}: invalid address {} in layer {}".format(
|
||||
proc_id, excp.invalid_address, excp.layer_name
|
||||
)
|
||||
f"Process {proc_id}: invalid address {excp.invalid_address} in layer {excp.layer_name}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -176,12 +174,7 @@ class Consoles(interfaces.plugins.PluginInterface):
|
||||
)
|
||||
|
||||
vollog.debug(
|
||||
"Determined OS Version: {}.{} {}.{}".format(
|
||||
kuser.NtMajorVersion,
|
||||
kuser.NtMinorVersion,
|
||||
vers.MajorVersion,
|
||||
vers.MinorVersion,
|
||||
)
|
||||
f"Determined OS Version: {kuser.NtMajorVersion}.{kuser.NtMinorVersion} {vers.MajorVersion}.{vers.MinorVersion}"
|
||||
)
|
||||
|
||||
if nt_major_version == 10 and arch == "x64":
|
||||
@@ -260,9 +253,7 @@ class Consoles(interfaces.plugins.PluginInterface):
|
||||
if ver:
|
||||
conhost_mod_version = ver[3]
|
||||
vollog.debug(
|
||||
"Determined conhost.exe's FileVersion: {}".format(
|
||||
conhost_mod_version
|
||||
)
|
||||
f"Determined conhost.exe's FileVersion: {conhost_mod_version}"
|
||||
)
|
||||
else:
|
||||
vollog.debug("Could not determine conhost.exe's FileVersion.")
|
||||
@@ -311,12 +302,7 @@ class Consoles(interfaces.plugins.PluginInterface):
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"This version of Windows is not supported: {}.{} {}.{}!".format(
|
||||
nt_major_version,
|
||||
nt_minor_version,
|
||||
vers.MajorVersion,
|
||||
vers_minor_version,
|
||||
)
|
||||
f"This version of Windows is not supported: {nt_major_version}.{nt_minor_version} {vers.MajorVersion}.{vers_minor_version}!"
|
||||
)
|
||||
|
||||
vollog.debug(f"Determined symbol filename: {filename}")
|
||||
|
||||
@@ -433,6 +433,8 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
|
||||
proc_layer = self.context.layers[proc_layer_name]
|
||||
|
||||
vads = self.get_vad_maps(proc)
|
||||
if not vads:
|
||||
continue
|
||||
|
||||
# for each valid process, look for malicious syscall invocations
|
||||
for address, vad_path in self._get_rule_hits(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user