multivol 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multivol/__init__.py +0 -0
- multivol/api.py +1001 -0
- multivol/multi_volatility2.py +269 -0
- multivol/multi_volatility3.py +266 -0
- multivol/multivol.py +254 -0
- multivol-0.1.0.dist-info/METADATA +45 -0
- multivol-0.1.0.dist-info/RECORD +11 -0
- multivol-0.1.0.dist-info/WHEEL +5 -0
- multivol-0.1.0.dist-info/entry_points.txt +2 -0
- multivol-0.1.0.dist-info/licenses/LICENSE +674 -0
- multivol-0.1.0.dist-info/top_level.txt +1 -0
multivol/multivol.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# multivol.py
|
|
2
|
+
# Entry point for MultiVolatility: orchestrates running Volatility2 and Volatility3 memory analysis in parallel using multiprocessing.
|
|
3
|
+
import multiprocessing, time, os, argparse, sys
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.theme import Theme
|
|
6
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
|
7
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from .multi_volatility2 import multi_volatility2
|
|
11
|
+
from .multi_volatility3 import multi_volatility3
|
|
12
|
+
except ImportError:
|
|
13
|
+
from multi_volatility2 import multi_volatility2
|
|
14
|
+
from multi_volatility3 import multi_volatility3
|
|
15
|
+
|
|
16
|
+
# Wrapper for Volatility 3 to use with imap
|
|
17
|
+
def vol3_wrapper(packed_args):
|
|
18
|
+
instance, args = packed_args
|
|
19
|
+
return instance.execute_command_volatility3(*args)
|
|
20
|
+
|
|
21
|
+
def runner(arguments):
|
|
22
|
+
# Ensure required directories exist for output, symbols, profiles, and plugins
|
|
23
|
+
os.makedirs(os.path.join(os.getcwd(), "volatility3_symbols"), exist_ok=True)
|
|
24
|
+
os.makedirs(os.path.join(os.getcwd(), "volatility2_profiles"), exist_ok=True)
|
|
25
|
+
os.makedirs(os.path.join(os.getcwd(), "volatility3_cache"), exist_ok=True)
|
|
26
|
+
os.makedirs(os.path.join(os.getcwd(), "volatility3_plugins"), exist_ok=True)
|
|
27
|
+
|
|
28
|
+
# Default to light mode if neither light nor full is specified
|
|
29
|
+
if not arguments.light and not arguments.full:
|
|
30
|
+
arguments.light = True
|
|
31
|
+
|
|
32
|
+
# Handle Volatility2 mode
|
|
33
|
+
if arguments.mode == "vol2":
|
|
34
|
+
volatility2_instance = multi_volatility2()
|
|
35
|
+
if hasattr(arguments, "output_dir") and arguments.output_dir:
|
|
36
|
+
output_dir = arguments.output_dir
|
|
37
|
+
else:
|
|
38
|
+
output_dir = f"volatility2_{os.path.basename(arguments.dump)}__output"
|
|
39
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
40
|
+
# Determine commands to run based on arguments
|
|
41
|
+
if arguments.commands:
|
|
42
|
+
commands = arguments.commands.split(",")
|
|
43
|
+
elif arguments.windows:
|
|
44
|
+
if arguments.light:
|
|
45
|
+
commands = volatility2_instance.getCommands("windows.light")
|
|
46
|
+
else:
|
|
47
|
+
commands = volatility2_instance.getCommands("windows.full")
|
|
48
|
+
elif arguments.linux:
|
|
49
|
+
if arguments.light:
|
|
50
|
+
commands = volatility2_instance.getCommands("linux.light")
|
|
51
|
+
else:
|
|
52
|
+
commands = volatility2_instance.getCommands("linux.full")
|
|
53
|
+
|
|
54
|
+
# Handle Volatility3 mode
|
|
55
|
+
elif arguments.mode == "vol3":
|
|
56
|
+
volatility3_instance = multi_volatility3()
|
|
57
|
+
if hasattr(arguments, "output_dir") and arguments.output_dir:
|
|
58
|
+
output_dir = arguments.output_dir
|
|
59
|
+
else:
|
|
60
|
+
output_dir = f"volatility3_{os.path.basename(arguments.dump)}__output"
|
|
61
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
62
|
+
# Determine commands to run based on arguments
|
|
63
|
+
if arguments.commands:
|
|
64
|
+
commands = arguments.commands.split(",")
|
|
65
|
+
elif arguments.windows:
|
|
66
|
+
if arguments.light:
|
|
67
|
+
commands = volatility3_instance.getCommands("windows.light")
|
|
68
|
+
else:
|
|
69
|
+
commands = volatility3_instance.getCommands("windows.full")
|
|
70
|
+
elif arguments.linux:
|
|
71
|
+
commands = volatility3_instance.getCommands("linux")
|
|
72
|
+
|
|
73
|
+
# Limit the number of parallel processes
|
|
74
|
+
# Default to len(commands) (unlimited) if processes arg is not set or None
|
|
75
|
+
max_procs = getattr(arguments, 'processes', None)
|
|
76
|
+
if max_procs is None:
|
|
77
|
+
# Default to CPU count to avoid system thrashing with too many Docker containers
|
|
78
|
+
try:
|
|
79
|
+
max_processes = os.cpu_count() or 4
|
|
80
|
+
except:
|
|
81
|
+
max_processes = 4
|
|
82
|
+
else:
|
|
83
|
+
max_processes = min(max_procs, len(commands))
|
|
84
|
+
start_time = time.time()
|
|
85
|
+
|
|
86
|
+
custom_theme = Theme({"info": "dim cyan", "warning": "magenta", "danger": "bold red"})
|
|
87
|
+
console = Console(theme=custom_theme)
|
|
88
|
+
console.print("\n[bold green][+] Launching all commands...[/bold green]\n")
|
|
89
|
+
|
|
90
|
+
# Use multiprocessing Manager for Lock
|
|
91
|
+
manager = multiprocessing.Manager()
|
|
92
|
+
lock = manager.Lock()
|
|
93
|
+
|
|
94
|
+
# Use multiprocessing to run commands in parallel
|
|
95
|
+
with multiprocessing.Pool(processes=max_processes) as pool:
|
|
96
|
+
if arguments.mode == "vol2":
|
|
97
|
+
pool.starmap(
|
|
98
|
+
volatility2_instance.execute_command_volatility2,
|
|
99
|
+
[(cmd,
|
|
100
|
+
os.path.basename(arguments.dump),
|
|
101
|
+
os.path.abspath(arguments.dump),
|
|
102
|
+
arguments.profiles_path,
|
|
103
|
+
arguments.image,
|
|
104
|
+
arguments.profile,
|
|
105
|
+
output_dir, # output_dir
|
|
106
|
+
arguments.format,
|
|
107
|
+
False, # quiet
|
|
108
|
+
lock, # lock
|
|
109
|
+
arguments.host_path
|
|
110
|
+
) for cmd in commands]
|
|
111
|
+
)
|
|
112
|
+
else:
|
|
113
|
+
# Enforce priority execution for Info module to ensure symbols are downloaded/cached
|
|
114
|
+
info_module = "windows.info.Info"
|
|
115
|
+
if info_module in commands:
|
|
116
|
+
commands.remove(info_module)
|
|
117
|
+
volatility3_instance.execute_command_volatility3(info_module,
|
|
118
|
+
os.path.basename(arguments.dump),
|
|
119
|
+
os.path.abspath(arguments.dump),
|
|
120
|
+
arguments.symbols_path,
|
|
121
|
+
arguments.image,
|
|
122
|
+
os.path.abspath(arguments.cache_path),
|
|
123
|
+
os.path.abspath(arguments.plugins_dir),
|
|
124
|
+
output_dir,
|
|
125
|
+
arguments.format,
|
|
126
|
+
False, # quiet
|
|
127
|
+
lock, # lock
|
|
128
|
+
arguments.host_path
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# Prepare arguments for imap
|
|
133
|
+
# We must pass the instance because wrapper is global and doesn't see local variable
|
|
134
|
+
tasks_args = [(volatility3_instance, (cmd,
|
|
135
|
+
os.path.basename(arguments.dump),
|
|
136
|
+
os.path.abspath(arguments.dump),
|
|
137
|
+
arguments.symbols_path,
|
|
138
|
+
arguments.image,
|
|
139
|
+
os.path.abspath(arguments.cache_path),
|
|
140
|
+
os.path.abspath(arguments.plugins_dir),
|
|
141
|
+
output_dir,
|
|
142
|
+
arguments.format,
|
|
143
|
+
False, # quiet=False so we see the output as it happens
|
|
144
|
+
lock, # lock
|
|
145
|
+
arguments.host_path
|
|
146
|
+
)) for cmd in commands]
|
|
147
|
+
|
|
148
|
+
# Progress counters
|
|
149
|
+
success_count = 0
|
|
150
|
+
failed_count = 0
|
|
151
|
+
successful_modules = []
|
|
152
|
+
failed_modules = []
|
|
153
|
+
|
|
154
|
+
# Use imap_unordered for real-time results collection
|
|
155
|
+
for result in pool.imap_unordered(vol3_wrapper, tasks_args):
|
|
156
|
+
command_name, is_success = result
|
|
157
|
+
|
|
158
|
+
if is_success:
|
|
159
|
+
success_count += 1
|
|
160
|
+
successful_modules.append(command_name)
|
|
161
|
+
else:
|
|
162
|
+
failed_count += 1
|
|
163
|
+
failed_modules.append(command_name)
|
|
164
|
+
if arguments.format == "json":
|
|
165
|
+
console.print(f"[red][!] Failed to validate JSON for {command_name}[/red]")
|
|
166
|
+
|
|
167
|
+
console.print(f"\n[bold green]Scan Complete![/bold green] Success: {success_count}, Failed: {failed_count}")
|
|
168
|
+
|
|
169
|
+
if successful_modules:
|
|
170
|
+
console.print("\n[bold green]Successful Modules:[/bold green]")
|
|
171
|
+
for mod in successful_modules:
|
|
172
|
+
console.print(f" - [green]{mod}[/green]")
|
|
173
|
+
|
|
174
|
+
if failed_modules:
|
|
175
|
+
console.print("\n[bold red]Failed Modules:[/bold red]")
|
|
176
|
+
for mod in failed_modules:
|
|
177
|
+
console.print(f" - [red]{mod}[/red]")
|
|
178
|
+
|
|
179
|
+
last_time = time.time()
|
|
180
|
+
console.print(f"\n[bold yellow]⏱️ Time : {last_time - start_time:.2f} seconds for {len(commands)} modules.[/bold yellow]")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main():
|
|
184
|
+
# Argument parsing for CLI usage
|
|
185
|
+
parser = argparse.ArgumentParser("MultiVolatility")
|
|
186
|
+
parser.add_argument("--api", action="store_true", help="Start API server")
|
|
187
|
+
parser.add_argument("--dev", action="store_true", help="Enable developer mode (hot reload)")
|
|
188
|
+
parser.add_argument("--host-path", type=str, required=False, default=None, help="Root path of the project on the Host machine (required for Docker-in-Docker)")
|
|
189
|
+
subparser = parser.add_subparsers(dest="mode", required=False)
|
|
190
|
+
|
|
191
|
+
# Volatility2 argument group
|
|
192
|
+
vol2_parser = subparser.add_parser("vol2", help="Use volatility2.")
|
|
193
|
+
vol2_parser.add_argument("--profiles-path", help="Path to the directory with the profiles.", default=os.path.join(os.getcwd(), "volatility2_profiles"))
|
|
194
|
+
vol2_parser.add_argument("--profile", help="Profile to use.", required=True)
|
|
195
|
+
vol2_parser.add_argument("--dump", help="Dump to parse.", required=True)
|
|
196
|
+
vol2_parser.add_argument("--image", help="Docker image to use.", required=True)
|
|
197
|
+
vol2_parser.add_argument("--commands", help="Commands to run : command1,command2,command3", required=False)
|
|
198
|
+
vol2_os_group = vol2_parser.add_mutually_exclusive_group(required=True)
|
|
199
|
+
vol2_os_group.add_argument("--linux", action="store_true", help="For a Linux memory dump")
|
|
200
|
+
vol2_os_group.add_argument("--windows", action="store_true", help="For a Windows memory dump")
|
|
201
|
+
vol2_parser.add_argument("--light", action="store_true", help="Use the main modules.")
|
|
202
|
+
vol2_parser.add_argument("--full", action="store_true", help="Use all modules.")
|
|
203
|
+
vol2_parser.add_argument("--format", help="Format of the outputs: json, text", required=False, default="text")
|
|
204
|
+
vol2_parser.add_argument("--processes", type=int, required=False, default=None, help="Max number of concurrent processes")
|
|
205
|
+
|
|
206
|
+
# Volatility3 argument group
|
|
207
|
+
vol3_parser = subparser.add_parser("vol3", help="Use volatility3.")
|
|
208
|
+
vol3_parser.add_argument("--dump", help="Dump to parse.", required=True)
|
|
209
|
+
vol3_parser.add_argument("--image", help="Docker image to use.", required=True)
|
|
210
|
+
vol3_parser.add_argument("--symbols-path", help="Path to the directory with the symbols.", required=False, default=os.path.join(os.getcwd(), "volatility3_symbols"))
|
|
211
|
+
vol3_parser.add_argument("--cache-path", help="Path to directory with the cache for volatility3.", required=False, default=os.path.join(os.getcwd(), "volatility3_cache"))
|
|
212
|
+
vol3_parser.add_argument("--plugins-dir", help="Path to directory with the plugins", required=False, default=os.path.join(os.getcwd(), "volatility3_plugins"))
|
|
213
|
+
vol3_parser.add_argument("--commands", help="Commands to run : command1,command2,command3", required=False)
|
|
214
|
+
vol3_os_group = vol3_parser.add_mutually_exclusive_group(required=True)
|
|
215
|
+
vol3_os_group.add_argument("--linux", action="store_true", help="It's a Linux memory dump")
|
|
216
|
+
vol3_os_group.add_argument("--windows", action="store_true", help="It's a Windows memory dump")
|
|
217
|
+
vol3_parser.add_argument("--light", action="store_true", help="Use the principal modules.")
|
|
218
|
+
vol3_parser.add_argument("--full", action="store_true", help="Use all modules.")
|
|
219
|
+
vol3_parser.add_argument("--format", help="Format of the outputs: json, text", required=False, default="text")
|
|
220
|
+
vol3_parser.add_argument("--processes", type=int, required=False, default=None, help="Max number of concurrent processes")
|
|
221
|
+
args = parser.parse_args()
|
|
222
|
+
|
|
223
|
+
if args.api:
|
|
224
|
+
try:
|
|
225
|
+
from .api import run_api
|
|
226
|
+
except ImportError:
|
|
227
|
+
from api import run_api
|
|
228
|
+
run_api(runner, debug_mode=args.dev)
|
|
229
|
+
sys.exit(0)
|
|
230
|
+
|
|
231
|
+
if args.mode is None:
|
|
232
|
+
parser.print_help()
|
|
233
|
+
sys.exit(1)
|
|
234
|
+
|
|
235
|
+
# Validate required OS type
|
|
236
|
+
if not args.linux and not args.windows:
|
|
237
|
+
print("[-] --linux or --windows required.")
|
|
238
|
+
sys.exit(1)
|
|
239
|
+
|
|
240
|
+
# Prevent unsupported combinations for Volatility3 Linux
|
|
241
|
+
if (args.mode == "vol3" and args.linux and args.light) or (args.mode == "vol3" and args.linux and args.full):
|
|
242
|
+
print("[-] --linux not available with --full or --light")
|
|
243
|
+
sys.exit(1)
|
|
244
|
+
|
|
245
|
+
# Validate output format
|
|
246
|
+
if (args.format != "json") and (args.format != "text"):
|
|
247
|
+
print("Format not supported !")
|
|
248
|
+
sys.exit(1)
|
|
249
|
+
|
|
250
|
+
# Start the runner with parsed arguments
|
|
251
|
+
runner(args)
|
|
252
|
+
|
|
253
|
+
if __name__ == "__main__":
|
|
254
|
+
main()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multivol
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MultiVolatility: Analyze memory dumps faster than ever with Volatility2 and Volatility3 in parallel using Docker
|
|
5
|
+
Home-page: https://github.com/BoBNewz/MultiVolatility
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.6
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pyyaml
|
|
13
|
+
Requires-Dist: requests
|
|
14
|
+
Requires-Dist: flask
|
|
15
|
+
Requires-Dist: flask-cors
|
|
16
|
+
Requires-Dist: docker
|
|
17
|
+
Requires-Dist: rich
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
Dynamic: requires-dist
|
|
24
|
+
Dynamic: requires-python
|
|
25
|
+
Dynamic: summary
|
|
26
|
+
|
|
27
|
+
# MultiVolatility
|
|
28
|
+
|
|
29
|
+
MultiVolatility uses multi-processing to run volatility2 and volatility3 docker containers.
|
|
30
|
+
The tool comes with the possibility to send JSON outputs to a web application.
|
|
31
|
+
|
|
32
|
+
## Build docker images
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
git clone https://github.com/BoBNewz/MultiVolatility.git
|
|
36
|
+
cd MultiVolatility
|
|
37
|
+
docker build Dockerfiles/volatility2/ -t volatility2
|
|
38
|
+
docker build Dockerfiles/volatility3/ -t volatility3
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Send outputs to the web application
|
|
42
|
+
|
|
43
|
+
Modify the URL and the API password in the config.yml.
|
|
44
|
+
|
|
45
|
+

|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
multivol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
multivol/api.py,sha256=N_7Sm8Ys_rjRI9MLm-ThTMcDXe6JJU1G11p6zljvQUs,37503
|
|
3
|
+
multivol/multi_volatility2.py,sha256=_Z2yxF05xLjzJSZBBisUEMT6WKy1YahbH4E-l41XvnI,9789
|
|
4
|
+
multivol/multi_volatility3.py,sha256=y7_vDG9iQcpPMYwyejq2V1Hhjkzml-2E8Xa0LW0LEXU,10733
|
|
5
|
+
multivol/multivol.py,sha256=7OP8SpmLL-t6Tuf7Gz4ecqQIRRPj-la0uXpfXHql8Jg,12824
|
|
6
|
+
multivol-0.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
7
|
+
multivol-0.1.0.dist-info/METADATA,sha256=JdTQKyTRlTprtU9XAIBlEdZ8N10VmQokDxTGsXhheIY,1383
|
|
8
|
+
multivol-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
+
multivol-0.1.0.dist-info/entry_points.txt,sha256=FM4lUHzrKUmV37U6IemQkRGXEJdgyB7-dwtw1jgwTQc,52
|
|
10
|
+
multivol-0.1.0.dist-info/top_level.txt,sha256=DcxSP883XnM_ad5TXyCIZkzDcYSoI1bPTie_AzHlN0A,9
|
|
11
|
+
multivol-0.1.0.dist-info/RECORD,,
|