multivol 0.1.3__py3-none-any.whl → 0.1.4__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.
@@ -25,21 +25,8 @@ class multi_volatility2:
25
25
  rel_path = os.path.relpath(path, os.getcwd())
26
26
  return os.path.join(host_path, rel_path)
27
27
  return path
28
-
29
- def generate_command_volatility2(self, command, dump, dump_dir, profiles_path, docker_image, profile, format):
30
- # Generates the Docker command to run a Volatility2 module
31
- return [
32
- "docker", "run", "--rm",
33
- "-v", f"{dump_dir}:/dumps/{dump}",
34
- "-v", f"{profiles_path}:/home/vol/profiles",
35
- "-t", docker_image, "--plugins=/home/vol/profiles",
36
- "-f", f"/dumps/{dump}",
37
- f"--profile={profile}",
38
- f"--output={format}",
39
- f"{command}"
40
- ]
41
28
 
42
- def execute_command_volatility2(self, command, dump, dump_dir, profiles_path, docker_image, profile, output_dir, format, quiet=False, lock=None, host_path=None):
29
+ def execute_command_volatility2(self, command, dump, dump_dir, profiles_path, docker_image, profile, output_dir, format, quiet=False, lock=None, host_path=None, show_commands=False):
43
30
  # Executes a Volatility2 command in Docker and handles output
44
31
  if not quiet:
45
32
  self.safe_print(f"[+] Starting {command}...", lock)
@@ -59,6 +46,8 @@ class multi_volatility2:
59
46
 
60
47
  # Construct the command string to run inside the container
61
48
  cmd_args = f"--plugins=/home/vol/profiles -f /dumps/{dump} --profile={profile} --output={format} {command}"
49
+ if show_commands:
50
+ print(f"[DEBUG] Volatility 2 Command: vol.py {cmd_args}", flush=True)
62
51
 
63
52
  if format == "json":
64
53
  self.output_file = os.path.join(output_dir, f"{command}_output.json")
@@ -76,8 +65,17 @@ class multi_volatility2:
76
65
  )
77
66
 
78
67
  with open(self.output_file, "wb") as file:
79
- for chunk in container.logs(stream=True):
80
- file.write(chunk)
68
+ try:
69
+ for chunk in container.logs(stream=True):
70
+ file.write(chunk)
71
+ except Exception as log_err:
72
+ # Handle Docker log rotation errors
73
+ self.safe_print(f"[!] Log streaming interrupted: {log_err}, fetching remaining logs...", lock)
74
+ try:
75
+ remaining_logs = container.logs(stream=False)
76
+ file.write(remaining_logs)
77
+ except:
78
+ pass
81
79
 
82
80
  container.wait()
83
81
  container.remove()
@@ -110,160 +108,16 @@ class multi_volatility2:
110
108
  rprint(message)
111
109
 
112
110
  def getCommands(self, opsys):
113
- # Returns a list of Volatility2 commands for the specified OS and mode
114
- if opsys == "windows.light":
115
- return ["cmdline",
116
- "cmdscan",
117
- "connscan",
118
- "consoles",
119
- "dlllist",
120
- "filescan",
121
- "hashdump",
122
- "malfind",
123
- "psscan",
124
- "pslist",
125
- "pstree",
126
- "psxview",
127
- ]
128
- elif opsys == "windows.full":
129
- return ["cmdline",
130
- "cmdscan",
131
- "connscan",
132
- "consoles",
133
- "dlllist",
134
- "filescan",
135
- "hashdump",
136
- "malfind",
137
- "mftparser",
138
- "psscan",
139
- "pslist",
140
- "pstree",
141
- "psxview",
142
- "amcache",
143
- "atoms",
144
- "autoruns",
145
- "bitlocker",
146
- "cachedump",
147
- "chromecookies",
148
- "chromedownloads",
149
- "chromehistory",
150
- "chromevisits",
151
- "clipboard",
152
- "connections",
153
- "devicetree",
154
- "directoryenumerator",
155
- "driverirp",
156
- "drivermodule",
157
- "driverscan",
158
- "envars",
159
- "evtlogs",
160
- "firefoxcookies",
161
- "firefoxdownloads",
162
- "firefoxhistory",
163
- "gahti",
164
- "getservicesids",
165
- "getsids",
166
- "handles",
167
- "hivelist",
168
- "hivescan",
169
- "hpakinfo",
170
- "lsadump",
171
- "mbrparser",
172
- "messagehooks",
173
- "modscan",
174
- "modules",
175
- "multiscan",
176
- "mutantscan",
177
- "networkpackets",
178
- "prefetchparser",
179
- "privs",
180
- "psinfo",
181
- "schtasks",
182
- "screenshot",
183
- "servicediff",
184
- "sessions",
185
- "shellbags",
186
- "shimcache",
187
- "sockets",
188
- "sockscan",
189
- "ssdt",
190
- "svcscan",
191
- "symlinkscan",
192
- "thrdscan",
193
- "threads",
194
- "unloadedmodules",
195
- "userassist",
196
- "userhandles",
197
- ]
198
- elif opsys == "linux.light":
199
- return ["linux_bash",
200
- "linux_ifconfig",
201
- "linux_malfind",
202
- "linux_netscan",
203
- "linux_enumerate_files",
204
- "linux_netstat",
205
- "linux_psaux",
206
- "linux_pslist",
207
- "linux_psscan",
208
- "linux_pstree",
209
- "linux_psxview"
210
- ]
211
- elif opsys == "linux.full":
212
- return ["linux_banner",
213
- "linux_bash",
214
- "linux_ifconfig",
215
- "linux_malfind",
216
- "linux_netscan",
217
- "linux_netstat",
218
- "linux_psaux",
219
- "linux_pslist",
220
- "linux_psscan",
221
- "linux_pstree",
222
- "linux_psxview",
223
- "linux_apihooks",
224
- "linux_arp",
225
- "linux_aslr_shift",
226
- "linux_bash_env",
227
- "linux_bash_hash",
228
- "linux_check_afinfo",
229
- "linux_check_creds",
230
- "linux_check_fop",
231
- "linux_check_idt",
232
- "linux_check_inline_kernel",
233
- "linux_check_modules",
234
- "linux_check_syscall",
235
- "linux_check_syscall_arm",
236
- "linux_check_tty",
237
- "linux_cpuinfo",
238
- "linux_dentry_cache",
239
- "linux_dmesg",
240
- "linux_dynamic_env",
241
- "linux_elfs",
242
- "linux_getcwd",
243
- "linux_hidden_modules",
244
- "linux_info_regs",
245
- "linux_iomem",
246
- "linux_kernel_opened_files",
247
- "linux_keyboard_notifiers",
248
- "linux_ldrmodules",
249
- "linux_library_list",
250
- "linux_list_raw",
251
- "linux_lsmod",
252
- "linux_lsof",
253
- "linux_memmap",
254
- "linux_mount",
255
- "linux_mount_cache",
256
- "linux_netfilter",
257
- "linux_pidhashtable",
258
- "linux_pkt_queues",
259
- "linux_plthook",
260
- "linux_proc_maps",
261
- "linux_proc_maps_rb",
262
- "linux_process_hollow",
263
- "linux_psenv",
264
- "linux_pslist_cache",
265
- "linux_route_cache",
266
- "linux_sk_buff_cache",
267
- "linux_threads",
268
- "linux_truecrypt_passphrase"
269
- ]
111
+
112
+ base_dir = os.path.dirname(os.path.abspath(__file__))
113
+
114
+ yaml_path = os.path.join(base_dir, "plugins_list", f"vol2_{opsys}.yaml")
115
+ if not os.path.exists(yaml_path):
116
+ raise FileNotFoundError(f"File not found : {yaml_path}")
117
+
118
+ with open(yaml_path, "r", encoding="utf-8") as f:
119
+ data = yaml.safe_load(f)
120
+
121
+ modules_list = data["modules"]
122
+
123
+ return modules_list
@@ -3,10 +3,12 @@
3
3
  import time
4
4
  import os
5
5
  import json
6
+ import re
7
+ import uuid
6
8
  from rich import print as rprint
9
+ import yaml
7
10
  import docker
8
11
 
9
-
10
12
  class multi_volatility3:
11
13
  def __init__(self):
12
14
  # Constructor for multi_volatility3 class
@@ -26,7 +28,7 @@ class multi_volatility3:
26
28
  return os.path.join(host_path, rel_path)
27
29
  return path
28
30
 
29
- def execute_command_volatility3(self, command, dump, dump_dir, symbols_path, docker_image, cache_dir, plugin_dir, output_dir, format, quiet=False, lock=None, host_path=None, fetch_symbols=False):
31
+ def execute_command_volatility3(self, command, dump, dump_dir, symbols_path, docker_image, cache_dir, plugin_dir, output_dir, format, quiet=False, lock=None, host_path=None, fetch_symbols=False, show_commands=False, custom_symbol=None, scan_id=None):
30
32
  # Executes a Volatility3 command in Docker and handles output
31
33
  if not quiet:
32
34
  self.safe_print(f"[+] Starting {command}...", lock)
@@ -42,6 +44,10 @@ class multi_volatility3:
42
44
  host_dump_path = self.resolve_path(os.path.abspath(dump_dir), host_path)
43
45
  host_dump_dir = os.path.dirname(host_dump_path)
44
46
 
47
+ # Debug logging for path resolution
48
+ print(f"[DEBUG] dump={dump}, dump_dir={dump_dir}", flush=True)
49
+ print(f"[DEBUG] host_dump_path={host_dump_path}, host_dump_dir={host_dump_dir}", flush=True)
50
+
45
51
  volumes = {
46
52
  host_dump_dir: {'bind': '/dump_dir', 'mode': 'ro'},
47
53
  host_symbols_path: {'bind': '/symbols', 'mode': 'rw'},
@@ -60,8 +66,13 @@ class multi_volatility3:
60
66
  # NOTE: -f expects the file path. Volume maps dump_dir to /dump_dir.
61
67
  # So dump file is at /dump_dir/basename(dump)
62
68
  dump_filename = os.path.basename(dump)
69
+ print(f"[DEBUG] dump_filename={dump_filename}, full path in container=/dump_dir/{dump_filename}", flush=True)
63
70
  base_args = f"vol -q -f /dump_dir/{dump_filename} -s /symbols -p /plugins"
64
71
 
72
+ if custom_symbol:
73
+ if show_commands:
74
+ print(f"[DEBUG] Custom Symbol Selected: {custom_symbol}", flush=True)
75
+
65
76
  if fetch_symbols:
66
77
  base_args = f"{base_args} --remote-isf-url https://github.com/Abyss-W4tcher/volatility3-symbols/raw/master/banners/banners.json"
67
78
 
@@ -71,10 +82,26 @@ class multi_volatility3:
71
82
  else:
72
83
  self.output_file = os.path.join(output_dir, f"{command}_output.txt")
73
84
  cmd_args = f"{base_args} {command}"
85
+
86
+ # Debug: verify output path
87
+ print(f"[DEBUG] output_dir={output_dir}, output_file={self.output_file}", flush=True)
88
+ print(f"[DEBUG] output_dir exists: {os.path.exists(output_dir)}", flush=True)
89
+ if show_commands:
90
+ print(f"[DEBUG] Volatility 3 Command: {cmd_args}", flush=True)
91
+ print(f"[DEBUG] Docker Volumes: {json.dumps(volumes, indent=2)}", flush=True)
74
92
 
75
93
  try:
94
+ # Sanitize command name for Docker container name
95
+ # Use scan_id for predictable naming so API can track container status
96
+ sanitized_name = re.sub(r'[^a-zA-Z0-9_.-]', '', command)
97
+ if scan_id:
98
+ container_name = f"vol3_{scan_id[:8]}_{sanitized_name}"
99
+ else:
100
+ container_name = f"vol3_{sanitized_name}_{str(uuid.uuid4())[:8]}"
101
+
76
102
  container = client.containers.run(
77
103
  image=docker_image,
104
+ name=container_name, # Name the container
78
105
  command=cmd_args,
79
106
  volumes=volumes,
80
107
  tty=True,
@@ -83,14 +110,27 @@ class multi_volatility3:
83
110
  )
84
111
 
85
112
  with open(self.output_file, "wb") as file:
86
- for chunk in container.logs(stream=True):
87
- file.write(chunk)
113
+ try:
114
+ for chunk in container.logs(stream=True):
115
+ file.write(chunk)
116
+ except Exception as log_err:
117
+ # Handle Docker log rotation errors (common with long-running modules)
118
+ self.safe_print(f"[!] Log streaming interrupted: {log_err}, fetching remaining logs...", lock)
119
+ try:
120
+ # Fallback: fetch all logs at once instead of streaming
121
+ remaining_logs = container.logs(stream=False)
122
+ file.write(remaining_logs)
123
+ except:
124
+ pass # Best effort - container may have finished
88
125
 
89
- container.wait()
90
- container.remove()
126
+ wait_result = container.wait()
127
+ exit_code = wait_result.get('StatusCode', 0)
128
+ # Don't remove container - API will check status and clean up
129
+ # container.remove()
91
130
 
92
131
  except Exception as e:
93
132
  self.safe_print(f"[!] Error running {command}: {e}", lock)
133
+ return (command, False)
94
134
 
95
135
  time.sleep(0.5)
96
136
  if format == "json":
@@ -121,32 +161,40 @@ class multi_volatility3:
121
161
 
122
162
  # Validation
123
163
  success = False
124
- try:
125
- with open(self.output_file, "r") as f:
126
- content = f.read()
127
-
128
- # Check for known error string regardless of format
129
- if "Volatility experienced" in content:
130
- success = False
131
- elif format == "json":
132
- # Simple validation check matching API logic
133
- start_index = content.find('[')
134
- if start_index == -1:
135
- start_index = content.find('{')
164
+
165
+ # Check Exit Code first
166
+ if exit_code != 0:
167
+ success = False
168
+ else:
169
+ try:
170
+ with open(self.output_file, "r") as f:
171
+ content = f.read()
136
172
 
137
- if start_index != -1:
138
- json.loads(content[start_index:])
139
- success = True
173
+ # Check for known error strings
174
+ # "Volatility experienced": General runtime error
175
+ # "vol.py: error:": Command line argument error
176
+ # "usage: vol": Often printed on argument error
177
+ if "Volatility experienced" in content or "vol.py: error:" in content or "vol: error:" in content:
178
+ success = False
179
+ elif format == "json":
180
+ # Simple validation check matching API logic
181
+ start_index = content.find('[')
182
+ if start_index == -1:
183
+ start_index = content.find('{')
184
+
185
+ if start_index != -1:
186
+ json.loads(content[start_index:])
187
+ success = True
188
+ else:
189
+ # Fallback check
190
+ lines = content.splitlines()
191
+ if len(lines) > 1:
192
+ json.loads('\n'.join(lines[1:]))
193
+ success = True
140
194
  else:
141
- # Fallback check
142
- lines = content.splitlines()
143
- if len(lines) > 1:
144
- json.loads('\n'.join(lines[1:]))
145
- success = True
146
- else:
147
- success = True # Assume text output is successful if no crash and no error string
148
- except:
149
- success = False
195
+ success = True # Assume text output is successful if no crash and no error string
196
+ except:
197
+ success = False
150
198
 
151
199
  return (command, success)
152
200
 
@@ -158,77 +206,16 @@ class multi_volatility3:
158
206
  rprint(message)
159
207
 
160
208
  def getCommands(self, opsys):
161
- # Returns a list of Volatility3 commands for the specified OS and mode
162
- if opsys == "windows.full":
163
- return ["windows.cmdline.CmdLine",
164
- "windows.cachedump.Cachedump",
165
- "windows.dlllist.DllList",
166
- "windows.driverirp.DriverIrp",
167
- "windows.drivermodule.DriverModule",
168
- "windows.driverscan.DriverScan",
169
- "windows.envars.Envars",
170
- "windows.filescan.FileScan",
171
- "windows.getservicesids.GetServiceSIDs",
172
- "windows.getsids.GetSIDs",
173
- "windows.handles.Handles",
174
- "windows.hashdump.Hashdump",
175
- "windows.lsadump.Lsadump",
176
- "windows.info.Info",
177
- "windows.malfind.Malfind",
178
- "windows.mftscan.MFTScan",
179
- "windows.modules.Modules",
180
- "windows.netscan.NetScan",
181
- "windows.netstat.NetStat",
182
- "windows.privileges.Privs",
183
- "windows.pslist.PsList",
184
- "windows.psscan.PsScan",
185
- "windows.pstree.PsTree",
186
- "windows.registry.hivelist.HiveList",
187
- "windows.registry.certificates.Certificates",
188
- "windows.registry.hivescan.HiveScan",
189
- "windows.registry.userassist.UserAssist",
190
- "windows.sessions.Sessions"
191
- ]
192
- elif opsys == "windows.light":
193
- return ["windows.cmdline.CmdLine",
194
- "windows.info.Info",
195
- "windows.filescan.FileScan",
196
- "windows.netscan.NetScan",
197
- "windows.netstat.NetStat",
198
- "windows.pslist.PsList",
199
- "windows.psscan.PsScan",
200
- "windows.pstree.PsTree",
201
- "windows.dlllist.DllList",
202
- "windows.hashdump.Hashdump"
203
- ]
204
- elif opsys == "linux":
205
- return ["linux.bash.Bash",
206
- "linux.capabilities.Capabilities",
207
- "linux.check_syscall.Check_syscall",
208
- "linux.elfs.Elfs",
209
- "linux.envars.Envars",
210
- "linux.library_list.LibraryList",
211
- "linux.lsmod.Lsmod",
212
- "linux.lsof.Lsof",
213
- "linux.malfind.Malfind",
214
- "linux.mountinfo.MountInfo",
215
- "linux.psaux.PsAux",
216
- "linux.pslist.PsList",
217
- "linux.psscan.PsScan",
218
- "linux.pstree.PsTree",
219
- "linux.sockstat.Sockstat",
220
- "linux.boottime.Boottime",
221
- "linux.check_creds.Check_creds",
222
- "linux.hidden_modules.Hidden_modules",
223
- "linux.ip.Addr",
224
- "linux.ip.Link",
225
- "linux.keyboard_notifiers.Keyboard_notifiers",
226
- "linux.modxview.Modxview",
227
- "linux.netfilter.Netfilter",
228
- "linux.pagecache.Files",
229
- "linux.pidhashtable.PIDHashTable",
230
- "linux.tracing.ftrace.CheckFtrace",
231
- "linux.tracing.perf_events.PerfEvents",
232
- "linux.tracing.tracepoints.CheckTracepoints",
233
- "linux.tty_check.tty_check"
234
- ]
209
+
210
+ base_dir = os.path.dirname(os.path.abspath(__file__))
211
+
212
+ yaml_path = os.path.join(base_dir, "plugins_list", f"vol3_{opsys}.yaml")
213
+ if not os.path.exists(yaml_path):
214
+ raise FileNotFoundError(f"File not found : {yaml_path}")
215
+
216
+ with open(yaml_path, "r", encoding="utf-8") as f:
217
+ data = yaml.safe_load(f)
218
+
219
+ modules_list = data["modules"]
220
+
221
+ return modules_list