Skip to content

Commit 13cb292

Browse files
authored
Merge pull request #619 from volatilityfoundation/release/v2.0.0
Release/v2.0.0
2 parents 8ecc7df + d469d9c commit 13cb292

File tree

228 files changed

+11269
-4514
lines changed

Some content is hidden

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

228 files changed

+11269
-4514
lines changed

API_CHANGES.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
API Changes
2+
===========
3+
4+
When an addition to the existing API is made, the minor version is bumped.
5+
When an API feature or function is removed or changed, the major version is bumped.
6+
7+
8+
1.2.0
9+
=====
10+
* Added support for module collections
11+
* Added context.modules
12+
* Added ModuleRequirement
13+
* Added get\_symbols\_by\_absolute\_location
14+
15+

README.md

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Volatility 3: The volatile memory extraction framework
22

3-
Volatility is the worlds most widely used framework for extracting digital
3+
Volatility is the world's most widely used framework for extracting digital
44
artifacts from volatile memory (RAM) samples. The extraction techniques are
55
performed completely independent of the system being investigated but offer
66
visibility into the runtime state of the system. The framework is intended
@@ -14,21 +14,34 @@ technical and performance challenges associated with the original
1414
code base that became apparent over the previous 10 years. Another benefit
1515
of the rewrite is that Volatility 3 could be released under a custom
1616
license that was more aligned with the goals of the Volatility community,
17-
the Volatility Software License (VSL). See the [LICENSE](LICENSE.txt) file for more details.
17+
the Volatility Software License (VSL). See the
18+
[LICENSE](https://www.volatilityfoundation.org/license/vsl-v1.0) file for
19+
more details.
1820

1921
## Requirements
2022

21-
- Python 3.5.3 or later. <https://www.python.org>
22-
- Pefile 2017.8.1 or later. <https://pypi.org/project/pefile/>
23+
Volatility 3 requires Python 3.6.0 or later. To install the most minimal set of dependencies (some plugins will not work) use a command such as:
2324

24-
## Optional Dependencies
25+
```shell
26+
pip3 install -r requirements-minimal.txt
27+
```
28+
29+
Alternately, the minimal packages will be installed automatically when Volatility 3 is installed using setup.py. However, as noted in the Quick Start section below, Volatility 3 does not *need* to be installed via setup.py prior to using it.
2530

26-
- yara-python 3.8.0 or later. <https://github.com/VirusTotal/yara-python>
27-
- capstone 3.0.0 or later. <https://www.capstone-engine.org/download.html>
31+
```shell
32+
python3 setup.py build
33+
python3 setup.py install
34+
```
35+
36+
To enable the full range of Volatility 3 functionality, use a command like the one below. For partial functionality, comment out any unnecessary packages in [requirements.txt](requirements.txt) prior to running the command.
37+
38+
```shell
39+
pip3 install -r requirements.txt
40+
```
2841

2942
## Downloading Volatility
3043

31-
The latest stable version of Volatility will always be the master branch of the GitHub repository. You can get the latest version of the code using the following command:
44+
The latest stable version of Volatility will always be the stable branch of the GitHub repository. You can get the latest version of the code using the following command:
3245

3346
```shell
3447
git clone https://github.com/volatilityfoundation/volatility3.git
@@ -45,7 +58,7 @@ git clone https://github.com/volatilityfoundation/volatility3.git
4558
2. See available options:
4659

4760
```shell
48-
python3 vol.py h
61+
python3 vol.py -h
4962
```
5063

5164
3. To get more information on a Windows memory sample and to make sure
@@ -55,10 +68,10 @@ Volatility supports that sample type, run
5568
Example:
5669

5770
```shell
58-
python3 vol.py f /home/user/samples/stuxnet.vmem windows.info
71+
python3 vol.py -f /home/user/samples/stuxnet.vmem windows.info
5972
```
6073

61-
4. Run some other plugins. The `-f` or `-single-location` is not strictly
74+
4. Run some other plugins. The `-f` or `--single-location` is not strictly
6275
required, but most plugins expect a single sample. Some also
6376
require/accept other options. Run `python3 vol.py <plugin> -h`
6477
for more information on a particular command.
@@ -91,7 +104,7 @@ The latest generated copy of the documentation can be found at: <https://volatil
91104

92105
## Licensing and Copyright
93106

94-
Copyright (C) 2007-2020 Volatility Foundation
107+
Copyright (C) 2007-2022 Volatility Foundation
95108

96109
All Rights Reserved
97110

development/banner_server.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import argparse
2+
import base64
3+
import json
4+
import logging
5+
import os
6+
import pathlib
7+
import urllib
8+
9+
from volatility3.cli import PrintedProgress
10+
from volatility3.framework import contexts, constants
11+
from volatility3.framework.automagic import linux, mac
12+
13+
vollog = logging.getLogger(__name__)
14+
15+
16+
class BannerCacheGenerator:
17+
18+
def __init__(self, path: str, url_prefix: str):
19+
self._path = path
20+
self._url_prefix = url_prefix
21+
22+
def convert_url(self, url):
23+
parsed = urllib.parse.urlparse(url)
24+
25+
relpath = os.path.relpath(parsed.path, os.path.abspath(self._path))
26+
27+
return urllib.parse.urljoin(self._url_prefix, relpath)
28+
29+
def run(self):
30+
context = contexts.Context()
31+
json_output = {'version': 1}
32+
33+
path = self._path
34+
filename = '*'
35+
36+
for banner_cache in [linux.LinuxBannerCache, mac.MacBannerCache]:
37+
sub_path = banner_cache.os
38+
potentials = []
39+
for extension in constants.ISF_EXTENSIONS:
40+
# Hopefully these will not be large lists, otherwise this might be slow
41+
try:
42+
for found in pathlib.Path(path).joinpath(sub_path).resolve().rglob(filename + extension):
43+
potentials.append(found.as_uri())
44+
except FileNotFoundError:
45+
# If there's no linux symbols, don't cry about it
46+
pass
47+
48+
new_banners = banner_cache.read_new_banners(context, 'BannerServer', potentials, banner_cache.symbol_name,
49+
banner_cache.os, progress_callback = PrintedProgress())
50+
result_banners = {}
51+
for new_banner in new_banners:
52+
# Only accept file schemes
53+
value = [self.convert_url(url) for url in new_banners[new_banner] if
54+
urllib.parse.urlparse(url).scheme == 'file']
55+
if value and new_banner:
56+
# Convert files into URLs
57+
result_banners[str(base64.b64encode(new_banner), 'latin-1')] = value
58+
59+
json_output[banner_cache.os] = result_banners
60+
61+
output_path = os.path.join(self._path, 'banners.json')
62+
with open(output_path, 'w') as fp:
63+
vollog.warning(f"Banners file written to {output_path}")
64+
json.dump(json_output, fp)
65+
66+
67+
if __name__ == '__main__':
68+
69+
parser = argparse.ArgumentParser()
70+
parser.add_argument('--path', default = os.path.dirname(__file__))
71+
parser.add_argument('--urlprefix', help = 'Web prefix that will eventually serve the ISF files',
72+
default = 'http://localhost/symbols')
73+
74+
args = parser.parse_args()
75+
76+
bcg = BannerCacheGenerator(args.path, args.urlprefix)
77+
bcg.run()

development/compare-vol.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def create_results(self, plugin: VolatilityPlugin, image: VolatilityImage, image
4646
self.create_prerequisites(plugin, image, image_hash)
4747

4848
# Volatility 2 Test
49-
print("[*] Testing {} {} with image {}".format(self.short_name, plugin.name, image.filepath))
49+
print(f"[*] Testing {self.short_name} {plugin.name} with image {image.filepath}")
5050
os.chdir(self.path)
5151
cmd = self.plugin_cmd(plugin, image)
5252
start_time = time.perf_counter()
@@ -56,15 +56,15 @@ def create_results(self, plugin: VolatilityPlugin, image: VolatilityImage, image
5656
completed = excp
5757
end_time = time.perf_counter()
5858
total_time = end_time - start_time
59-
print(" Tested {} {} with image {}: {}".format(self.short_name, plugin.name, image.filepath, total_time))
59+
print(f" Tested {self.short_name} {plugin.name} with image {image.filepath}: {total_time}")
6060
with open(
61-
os.path.join(self.output_directory, '{}_{}_{}_stdout'.format(self.short_name, plugin.name, image_hash)),
61+
os.path.join(self.output_directory, f'{self.short_name}_{plugin.name}_{image_hash}_stdout'),
6262
"wb") as f:
6363
f.write(completed.stdout)
6464
if completed.stderr:
6565
with open(
66-
os.path.join(self.output_directory, '{}_{}_{}_stderr'.format(self.short_name, plugin.name,
67-
image_hash)), "wb") as f:
66+
os.path.join(self.output_directory, f'{self.short_name}_{plugin.name}_{image_hash}_stderr'),
67+
"wb") as f:
6868
f.write(completed.stderr)
6969
return [total_time]
7070

@@ -91,15 +91,15 @@ def create_results(self, plugin: VolatilityPlugin, image: VolatilityImage, image
9191
def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash):
9292
# Volatility 2 image info
9393
if not image.vol2_profile:
94-
print("[*] Testing {} imageinfo with image {}".format(self.short_name, image.filepath))
94+
print(f"[*] Testing {self.short_name} imageinfo with image {image.filepath}")
9595
os.chdir(self.path)
9696
cmd = ["python2", "-u", "vol.py", "-f", image.filepath, "imageinfo"]
9797
start_time = time.perf_counter()
9898
vol2_completed = subprocess.run(cmd, cwd = self.path, capture_output = True)
9999
end_time = time.perf_counter()
100100
image.vol2_imageinfo_time = end_time - start_time
101-
print(" Tested volatility2 imageinfo with image {}: {}".format(image.filepath, end_time - start_time))
102-
with open(os.path.join(self.output_directory, 'vol2_imageinfo_{}_stdout'.format(image_hash)), "wb") as f:
101+
print(f" Tested volatility2 imageinfo with image {image.filepath}: {end_time - start_time}")
102+
with open(os.path.join(self.output_directory, f'vol2_imageinfo_{image_hash}_stdout'), "wb") as f:
103103
f.write(vol2_completed.stdout)
104104
image.vol2_profile = re.search(b"Suggested Profile\(s\) : ([^,]+)", vol2_completed.stdout)[1]
105105

development/mac-kdk/extract_kernel.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ ${DWARF2JSON}
4444
popd
4545
rm -fr tmp
4646

47-
${DWARF2JSON} mac --macho "${UNPACK_DIR}/${KERNEL_DIR}/kernel.dSYM" --macho-symbols "${UNPACK_DIRECTORY}/${KERNEL_DIR}/kernel" | xz -9 > ${JSON_DIR}/${KERNEL_DIR}.json.xz
48-
if [ $? == 0 ]; then
49-
${DWARF2JSON} mac --arch i386 --macho "${UNPACK_DIR}/${KERNEL_DIR}/kernel.dSYM" --macho-symbols "${UNPACK_DIRECTORY}/${KERNEL_DIR}/kernel" | xz -9 > ${JSON_DIR}/${KERNEL_DIR}.json.xz
47+
echo "Running ${DWARF2JSON} mac --macho "${UNPACK_DIR}/${KERNEL_DIR}/kernel.dSYM" --macho-symbols "${UNPACK_DIR}/${KERNEL_DIR}/kernel" | xz -9 > ${JSON_DIR}/${KERNEL_DIR}.json.xz"
48+
${DWARF2JSON} mac --macho "${UNPACK_DIR}/${KERNEL_DIR}/kernel.dSYM" --macho-symbols "${UNPACK_DIR}/${KERNEL_DIR}/kernel" | xz -9 > ${JSON_DIR}/${KERNEL_DIR}.json.xz
49+
if [ $? != 0 ]; then
50+
${DWARF2JSON} mac --arch i386 --macho "${UNPACK_DIR}/${KERNEL_DIR}/kernel.dSYM" --macho-symbols "${UNPACK_DIR}/${KERNEL_DIR}/kernel" | xz -9 > ${JSON_DIR}/${KERNEL_DIR}.json.xz
5051
fi

development/pdbparse-to-json.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@ def retreive_pdb(self, guid: str, file_name: str) -> Optional[str]:
2727
logger.info("Download PDB file...")
2828
file_name = ".".join(file_name.split(".")[:-1] + ['pdb'])
2929
for sym_url in ['http://msdl.microsoft.com/download/symbols']:
30-
url = sym_url + "/{}/{}/".format(file_name, guid)
30+
url = sym_url + f"/{file_name}/{guid}/"
3131

3232
result = None
3333
for suffix in [file_name[:-1] + '_', file_name]:
3434
try:
35-
logger.debug("Attempting to retrieve {}".format(url + suffix))
35+
logger.debug(f"Attempting to retrieve {url + suffix}")
3636
result, _ = request.urlretrieve(url + suffix)
3737
except request.HTTPError as excp:
38-
logger.debug("Failed with {}".format(excp))
38+
logger.debug(f"Failed with {excp}")
3939
if result:
40-
logger.debug("Successfully written to {}".format(result))
40+
logger.debug(f"Successfully written to {result}")
4141
break
4242
return result
4343

@@ -116,7 +116,7 @@ def __init__(self, filename: str):
116116
self._filename = filename
117117
logger.info("Parsing PDB...")
118118
self._pdb = pdbparse.parse(filename)
119-
self._seen_ctypes = set([]) # type: Set[str]
119+
self._seen_ctypes: Set[str] = set([])
120120

121121
def lookup_ctype(self, ctype: str) -> str:
122122
self._seen_ctypes.add(ctype)
@@ -169,7 +169,7 @@ def generate_metadata(self) -> Dict[str, Any]:
169169
def read_enums(self) -> Dict:
170170
"""Reads the Enumerations from the PDB file"""
171171
logger.info("Reading enums...")
172-
output = {} # type: Dict[str, Any]
172+
output: Dict[str, Any] = {}
173173
stream = self._pdb.STREAM_TPI
174174
for type_index in stream.types:
175175
user_type = stream.types[type_index]
@@ -231,7 +231,7 @@ def read_usertypes(self) -> Dict:
231231

232232
def _format_usertype(self, usertype, kind) -> Dict:
233233
"""Produces a single usertype"""
234-
fields = {} # type: Dict[str, Dict[str, Any]]
234+
fields: Dict[str, Dict[str, Any]] = {}
235235
[fields.update(self._format_field(s)) for s in usertype.fieldlist.substructs]
236236
return {usertype.name: {'fields': fields, 'kind': kind, 'size': usertype.size}}
237237

@@ -257,7 +257,7 @@ def _determine_size(self, field):
257257
if output is None:
258258
import pdb
259259
pdb.set_trace()
260-
raise ValueError("Unknown size for field: {}".format(field.name))
260+
raise ValueError(f"Unknown size for field: {field.name}")
261261
return output
262262

263263
def _format_kind(self, kind):
@@ -355,6 +355,6 @@ def read_basetypes(self) -> Dict:
355355
json.dump(convertor.read_pdb(), f, indent = 2, sort_keys = True)
356356

357357
if args.keep:
358-
print("Temporary PDB file: {}".format(filename))
358+
print(f"Temporary PDB file: {filename}")
359359
elif delfile:
360360
os.remove(filename)

development/schema_validate.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
for filename in args.filenames:
3636
try:
3737
if os.path.exists(filename):
38-
print("[?] Validating file: {}".format(filename))
38+
print(f"[?] Validating file: {filename}")
3939
with open(filename, 'r') as t:
4040
test = json.load(t)
4141

@@ -45,14 +45,14 @@
4545
result = schemas.validate(test, False)
4646

4747
if result:
48-
print("[+] Validation successful: {}".format(filename))
48+
print(f"[+] Validation successful: {filename}")
4949
else:
50-
print("[-] Validation failed: {}".format(filename))
50+
print(f"[-] Validation failed: {filename}")
5151
failures.append(filename)
5252
else:
53-
print("[x] File not found: {}".format(filename))
53+
print(f"[x] File not found: {filename}")
5454
except Exception as e:
5555
failures.append(filename)
56-
print("[x] Exception occurred: {} ({})".format(filename, repr(e)))
56+
print(f"[x] Exception occurred: {filename} ({repr(e)})")
5757

5858
print("Failures", failures)

development/stock-linux-json.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def download_lists(self, keep = False):
3030
def download_list(self, urls: List[str]) -> Dict[str, str]:
3131
processed_files = {}
3232
for url in urls:
33-
print(" - Downloading {}".format(url))
33+
print(f" - Downloading {url}")
3434
data = requests.get(url)
3535
with tempfile.NamedTemporaryFile() as archivedata:
3636
archivedata.write(data.content)
@@ -48,14 +48,14 @@ def process_rpm(self, archivedata) -> Optional[str]:
4848
extracted = None
4949
for member in rpm.getmembers():
5050
if 'vmlinux' in member.name or 'System.map' in member.name:
51-
print(" - Extracting {}".format(member.name))
51+
print(f" - Extracting {member.name}")
5252
extracted = rpm.extractfile(member)
5353
break
5454
if not member or not extracted:
5555
return None
5656
with tempfile.NamedTemporaryFile(delete = False,
5757
prefix = 'vmlinux' if 'vmlinux' in member.name else 'System.map') as output:
58-
print(" - Writing to {}".format(output.name))
58+
print(f" - Writing to {output.name}")
5959
output.write(extracted.read())
6060
return output.name
6161

@@ -65,14 +65,14 @@ def process_deb(self, archivedata) -> Optional[str]:
6565
extracted = None
6666
for member in deb.data.tgz().getmembers():
6767
if member.name.endswith('vmlinux') or 'System.map' in member.name:
68-
print(" - Extracting {}".format(member.name))
68+
print(f" - Extracting {member.name}")
6969
extracted = deb.data.get_file(member.name)
7070
break
7171
if not member or not extracted:
7272
return None
7373
with tempfile.NamedTemporaryFile(delete = False,
7474
prefix = 'vmlinux' if 'vmlinux' in member.name else 'System.map') as output:
75-
print(" - Writing to {}".format(output.name))
75+
print(f" - Writing to {output.name}")
7676
output.write(extracted.read())
7777
return output.name
7878

@@ -81,7 +81,7 @@ def process_files(self, named_files: Dict[str, str]):
8181
print("Processing Files...")
8282
for i in named_files:
8383
if named_files[i] is None:
84-
print("FAILURE: None encountered for {}".format(i))
84+
print(f"FAILURE: None encountered for {i}")
8585
return
8686
args = [DWARF2JSON, 'linux']
8787
output_filename = 'unknown-kernel.json'
@@ -91,10 +91,10 @@ def process_files(self, named_files: Dict[str, str]):
9191
prefix = '--elf'
9292
output_filename = './' + '-'.join((named_file.split('/')[-1]).split('-')[2:])[:-4] + '.json.xz'
9393
args += [prefix, named_files[named_file]]
94-
print(" - Running {}".format(args))
94+
print(f" - Running {args}")
9595
proc = subprocess.run(args, capture_output = True)
9696

97-
print(" - Writing to {}".format(output_filename))
97+
print(f" - Writing to {output_filename}")
9898
with lzma.open(output_filename, 'w') as f:
9999
f.write(proc.stdout)
100100

doc/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# These packages are required for building the documentation.
2+
sphinx>=1.8.2
3+
sphinx_autodoc_typehints>=1.4.0
4+
sphinx-rtd-theme>=0.4.3

0 commit comments

Comments
 (0)