Skip to content

Commit f2f412d

Browse files
authored
Merge branch 'volatilityfoundation:develop' into fix-issue-895
2 parents 5c80a66 + 57924c5 commit f2f412d

File tree

14 files changed

+277
-47
lines changed

14 files changed

+277
-47
lines changed

.github/workflows/install.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Install Volatility3 test
2+
on: [push, pull_request]
3+
jobs:
4+
5+
install_test:
6+
runs-on: ${{ matrix.host }}
7+
strategy:
8+
fail-fast: false
9+
matrix:
10+
host: [ ubuntu-latest, windows-latest ]
11+
python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ]
12+
steps:
13+
- uses: actions/checkout@v3
14+
15+
- name: Set up Python ${{ matrix.python-version }}
16+
uses: actions/setup-python@v4
17+
with:
18+
python-version: ${{ matrix.python-version }}
19+
20+
- name: Setup python-pip
21+
run: python -m pip install --upgrade pip
22+
23+
- name: Install dependencies
24+
run: |
25+
pip install -r requirements.txt
26+
27+
- name: Install volatility3
28+
run: pip install .
29+
30+
- name: Run volatility3
31+
run: vol --help

doc/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def setup(app):
2727

2828
source_dir = os.path.abspath(os.path.dirname(__file__))
2929
sphinx.ext.apidoc.main(
30-
argv=["-e", "-M", "-f", "-T", "-o", source_dir, volatility_directory]
30+
["-e", "-M", "-f", "-T", "-o", source_dir, volatility_directory]
3131
)
3232

3333
# Go through the volatility3.framework.plugins files and change them to volatility3.plugins

doc/source/using-as-a-library.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ also be included, which can be found in `volatility3.constants.PLUGINS_PATH`.
5454
volatility3.plugins.__path__ = <new_plugin_path> + constants.PLUGINS_PATH
5555
failures = framework.import_files(volatility3.plugins, True)
5656

57+
.. note::
58+
59+
Volatility uses the `volatility3.plugins` namespace for all plugins (including those in `volatility3.framework.plugins`).
60+
Please ensure you only use `volatility3.plugins` and only ever import plugins from this namespace.
61+
This ensures the ability of users to override core plugins without needing write access to the framework directory.
62+
5763
Once the plugins have been imported, we can interrogate which plugins are available. The
5864
:py:func:`~volatility3.framework.list_plugins` call will
5965
return a dictionary of plugin names and the plugin classes.

setup.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212

1313
def get_install_requires():
1414
requirements = []
15-
with open("requirements-minimal.txt", "r", encoding = "utf-8") as fh:
15+
with open("requirements-minimal.txt", "r", encoding="utf-8") as fh:
1616
for line in fh.readlines():
1717
stripped_line = line.strip()
1818
if stripped_line == "" or stripped_line.startswith("#"):
1919
continue
2020
requirements.append(stripped_line)
2121
return requirements
2222

23+
2324
setuptools.setup(
2425
name="volatility3",
2526
description="Memory forensics framework",
@@ -36,12 +37,12 @@ def get_install_requires():
3637
"Documentation": "https://volatility3.readthedocs.io/",
3738
"Source Code": "https://github.com/volatilityfoundation/volatility3",
3839
},
39-
python_requires=">=3.7.0",
40-
include_package_data=True,
41-
exclude_package_data={"": ["development", "development.*"], "development": ["*"]},
4240
packages=setuptools.find_namespace_packages(
43-
exclude=["development", "development.*"]
41+
include=["volatility3", "volatility3.*"]
4442
),
43+
package_dir={"volatility3": "volatility3"},
44+
python_requires=">=3.7.0",
45+
include_package_data=True,
4546
entry_points={
4647
"console_scripts": [
4748
"vol = volatility3.cli:main",

volatility3/framework/constants/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
# We use the SemVer 2.0.0 versioning scheme
4646
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
4747
VERSION_MINOR = 5 # Number of changes that only add to the interface
48-
VERSION_PATCH = 0 # Number of changes that do not change the interface
48+
VERSION_PATCH = 2 # Number of changes that do not change the interface
4949
VERSION_SUFFIX = ""
5050

5151
# TODO: At version 2.0.0, remove the symbol_shift feature

volatility3/framework/constants/linux/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,5 @@
279279
"bpf",
280280
"checkpoint_restore",
281281
)
282+
283+
ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1

volatility3/framework/layers/intel.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ def _page_is_valid(entry: int) -> bool:
111111
"""Returns whether a particular page is valid based on its entry."""
112112
return bool(entry & 1)
113113

114+
@staticmethod
115+
def _page_is_dirty(entry: int) -> bool:
116+
"""Returns whether a particular page is dirty based on its entry."""
117+
return bool(entry & (1 << 6))
118+
114119
def canonicalize(self, addr: int) -> int:
115120
"""Canonicalizes an address by performing an appropiate sign extension on the higher addresses"""
116121
if self._bits_per_register <= self._maxvirtaddr:
@@ -259,6 +264,10 @@ def is_valid(self, offset: int, length: int = 1) -> bool:
259264
except exceptions.InvalidAddressException:
260265
return False
261266

267+
def is_dirty(self, offset: int) -> bool:
268+
"""Returns whether the page at offset is marked dirty"""
269+
return self._page_is_dirty(self._translate_entry(offset)[0])
270+
262271
def mapping(
263272
self, offset: int, length: int, ignore_errors: bool = False
264273
) -> Iterable[Tuple[int, int, int, int, str]]:

volatility3/framework/layers/vmware.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import contextlib
55
import logging
66
import struct
7+
import os
78
from typing import Any, Dict, List, Optional
89

910
from volatility3.framework import constants, exceptions, interfaces
@@ -232,6 +233,11 @@ def stack(
232233
)
233234

234235
if not vmss_success and not vmsn_success:
236+
vmem_file_basename = os.path.basename(location)
237+
example_vmss_file_basename = os.path.basename(vmss)
238+
vollog.warning(
239+
f"No metadata file found alongside VMEM file. A VMSS or VMSN file may be required to correctly process a VMEM file. These should be placed in the same directory with the same file name, e.g. {vmem_file_basename} and {example_vmss_file_basename}.",
240+
)
235241
return None
236242
new_layer_name = context.layers.free_layer_name("VmwareLayer")
237243
context.config[

volatility3/framework/plugins/linux/elfs.py

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,26 @@
44
"""A module containing a collection of plugins that produce data typically
55
found in Linux's /proc file system."""
66

7-
from typing import List
7+
import logging
8+
from typing import List, Optional, Type
89

9-
from volatility3.framework import renderers, interfaces
10+
from volatility3.framework import constants, interfaces, renderers
1011
from volatility3.framework.configuration import requirements
1112
from volatility3.framework.interfaces import plugins
1213
from volatility3.framework.objects import utility
1314
from volatility3.framework.renderers import format_hints
15+
from volatility3.framework.symbols import intermed
16+
from volatility3.framework.symbols.linux.extensions import elf
1417
from volatility3.plugins.linux import pslist
1518

19+
vollog = logging.getLogger(__name__)
20+
1621

1722
class Elfs(plugins.PluginInterface):
1823
"""Lists all memory mapped ELF files for all processes."""
1924

2025
_required_framework_version = (2, 0, 0)
26+
_version = (2, 0, 0)
2127

2228
@classmethod
2329
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -36,9 +42,93 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
3642
element_type=int,
3743
optional=True,
3844
),
45+
requirements.BooleanRequirement(
46+
name="dump",
47+
description="Extract listed processes",
48+
default=False,
49+
optional=True,
50+
),
3951
]
4052

53+
@classmethod
54+
def elf_dump(
55+
cls,
56+
context: interfaces.context.ContextInterface,
57+
layer_name: str,
58+
elf_table_name: str,
59+
vma: interfaces.objects.ObjectInterface,
60+
task: interfaces.objects.ObjectInterface,
61+
open_method: Type[interfaces.plugins.FileHandlerInterface],
62+
) -> Optional[interfaces.plugins.FileHandlerInterface]:
63+
"""Extracts an ELF as a FileHandlerInterface
64+
Args:
65+
context: the context to operate upon
66+
layer_name: The name of the layer on which to operate
67+
elf_table_name: the name for the symbol table containing the symbols for ELF-files
68+
vma: virtual memory allocation of ELF
69+
task: the task object whose memory should be output
70+
open_method: class to provide context manager for opening the file
71+
Returns:
72+
An open FileHandlerInterface object containing the complete data for the task or None in the case of failure
73+
"""
74+
75+
proc_layer = context.layers[layer_name]
76+
file_handle = None
77+
78+
elf_object = context.object(
79+
elf_table_name + constants.BANG + "Elf",
80+
offset=vma.vm_start,
81+
layer_name=layer_name,
82+
)
83+
84+
if not elf_object.is_valid():
85+
return None
86+
87+
sections = {}
88+
# TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ?
89+
for phdr in elf_object.get_program_headers():
90+
if phdr.p_type != 1: # PT_LOAD = 1
91+
continue
92+
93+
start = phdr.p_vaddr
94+
size = phdr.p_memsz
95+
end = start + size
96+
97+
# Use complete memory pages for dumping
98+
# If start isn't a multiple of 4096, stick to the highest multiple < start
99+
# If end isn't a multiple of 4096, stick to the lowest multiple > end
100+
if start % 4096:
101+
start = start & ~0xFFF
102+
103+
if end % 4096:
104+
end = (end & ~0xFFF) + 4096
105+
106+
real_size = end - start
107+
108+
# Check if ELF has a legitimate size
109+
if real_size < 0 or real_size > constants.linux.ELF_MAX_EXTRACTION_SIZE:
110+
raise ValueError(f"The claimed size of the ELF is invalid: {real_size}")
111+
112+
sections[start] = real_size
113+
114+
elf_data = b""
115+
for section_start in sorted(sections.keys()):
116+
read_size = sections[section_start]
117+
118+
buf = proc_layer.read(vma.vm_start + section_start, read_size, pad=True)
119+
elf_data = elf_data + buf
120+
121+
file_handle = open_method(
122+
f"pid.{task.pid}.{utility.array_to_string(task.comm)}.{vma.vm_start:#x}.dmp"
123+
)
124+
file_handle.write(elf_data)
125+
126+
return file_handle
127+
41128
def _generator(self, tasks):
129+
elf_table_name = intermed.IntermediateSymbolTable.create(
130+
self.context, self.config_path, "linux", "elf", class_types=elf.class_types
131+
)
42132
for task in tasks:
43133
proc_layer_name = task.add_process_layer()
44134
if not proc_layer_name:
@@ -60,6 +150,21 @@ def _generator(self, tasks):
60150

61151
path = vma.get_name(self.context, task)
62152

153+
file_output = "Disabled"
154+
if self.config["dump"]:
155+
file_handle = self.elf_dump(
156+
self.context,
157+
proc_layer_name,
158+
elf_table_name,
159+
vma,
160+
task,
161+
self.open,
162+
)
163+
file_output = "Error outputting file"
164+
if file_handle:
165+
file_handle.close()
166+
file_output = str(file_handle.preferred_filename)
167+
63168
yield (
64169
0,
65170
(
@@ -68,6 +173,7 @@ def _generator(self, tasks):
68173
format_hints.Hex(vma.vm_start),
69174
format_hints.Hex(vma.vm_end),
70175
path,
176+
file_output,
71177
),
72178
)
73179

@@ -81,6 +187,7 @@ def run(self):
81187
("Start", format_hints.Hex),
82188
("End", format_hints.Hex),
83189
("File Path", str),
190+
("File Output", str),
84191
],
85192
self._generator(
86193
pslist.PsList.list_tasks(

volatility3/framework/plugins/linux/malfind.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
#
44

55
from typing import List
6-
6+
import logging
77
from volatility3.framework import constants, interfaces
88
from volatility3.framework import renderers
99
from volatility3.framework.configuration import requirements
1010
from volatility3.framework.objects import utility
1111
from volatility3.framework.renderers import format_hints
1212
from volatility3.plugins.linux import pslist
1313

14+
vollog = logging.getLogger(__name__)
15+
1416

1517
class Malfind(interfaces.plugins.PluginInterface):
1618
"""Lists process memory ranges that potentially contain injected code."""
@@ -47,7 +49,14 @@ def _list_injections(self, task):
4749
proc_layer = self.context.layers[proc_layer_name]
4850

4951
for vma in task.mm.get_vma_iter():
50-
if vma.is_suspicious() and vma.get_name(self.context, task) != "[vdso]":
52+
vma_name = vma.get_name(self.context, task)
53+
vollog.debug(
54+
f"Injections : processing PID {task.pid} : VMA {vma_name} : {hex(vma.vm_start)}-{hex(vma.vm_end)}"
55+
)
56+
if (
57+
vma.is_suspicious(proc_layer)
58+
and vma.get_name(self.context, task) != "[vdso]"
59+
):
5160
data = proc_layer.read(vma.vm_start, 64, pad=True)
5261
yield vma, data
5362

0 commit comments

Comments
 (0)