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/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
+ ![MultiVolatility](https://github.com/user-attachments/assets/f77c636d-b647-4218-9617-20268616689c)
@@ -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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ multivol = multivol.multivol:main