sandbox-cli 0.2.38__tar.gz → 0.2.39__tar.gz
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.
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/PKG-INFO +1 -1
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/pyproject.toml +1 -1
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/cli/reporter.py +9 -1
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/downloader/__init__.py +14 -2
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/extractors.py +9 -11
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/scanner/advanced.py +44 -24
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/.gitignore +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/LICENSE +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/NOTICE +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/README.md +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/__main__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/cli/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/cli/downloader.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/cli/images.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/cli/rules/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/cli/scanner/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/cli/unpack.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/console.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/internal/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/internal/config.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/internal/helpers.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/models/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/models/detections.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/models/sandbox_arguments.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/compiler/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/compiler/abc.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/compiler/docker.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/compiler/ssh.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/merge_dll_hooks.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/scanner/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/scanner/rescan.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/unpack/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/unpack/plugins/__init__.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/unpack/plugins/abc.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/unpack/plugins/correlation.py +0 -0
- {sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/unpack/plugins/sort_by_plugins.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sandbox-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.39
|
|
4
4
|
Summary: Command line tool for interaction with sandboxes
|
|
5
5
|
Project-URL: Homepage, https://github.com/Security-Experts-Community/sandbox-cli
|
|
6
6
|
Project-URL: Documentation, https://security-experts-community.github.io/sandbox-cli
|
|
@@ -53,6 +53,14 @@ def generate_report(
|
|
|
53
53
|
negative="",
|
|
54
54
|
),
|
|
55
55
|
] = False,
|
|
56
|
+
suspicious: Annotated[
|
|
57
|
+
bool,
|
|
58
|
+
Parameter(
|
|
59
|
+
name=["--supicious", "-s"],
|
|
60
|
+
help="Include suspicious detects",
|
|
61
|
+
negative="",
|
|
62
|
+
),
|
|
63
|
+
] = False,
|
|
56
64
|
) -> None:
|
|
57
65
|
"""
|
|
58
66
|
Generate short report from sandbox scans.
|
|
@@ -118,7 +126,7 @@ def generate_report(
|
|
|
118
126
|
{
|
|
119
127
|
"sample": report.artifacts[0].file_info.file_path, # type: ignore
|
|
120
128
|
"image": image,
|
|
121
|
-
"verdict": delimeter.join(extract_verdict_from_trace(corr_trace)),
|
|
129
|
+
"verdict": delimeter.join(extract_verdict_from_trace(corr_trace, suspicious)),
|
|
122
130
|
"static": delimeter.join(extract_static(report)),
|
|
123
131
|
"memory": delimeter.join(extract_memory(report)),
|
|
124
132
|
"network": delimeter.join(extract_network_from_trace(corr_trace)),
|
|
@@ -6,6 +6,7 @@ from typing import Any
|
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
import aiofiles
|
|
9
|
+
import aiohttp
|
|
9
10
|
import zstandard
|
|
10
11
|
from ptsandbox import Sandbox
|
|
11
12
|
from ptsandbox.models import (
|
|
@@ -65,13 +66,20 @@ async def _save_artifact(
|
|
|
65
66
|
else:
|
|
66
67
|
task_id = progress.add_task(rf"\[[green1]{scan_id}[/]] {escape(path.name)}")
|
|
67
68
|
try:
|
|
68
|
-
|
|
69
|
+
# a 5-minute timeout for downloading large files
|
|
70
|
+
downloaded_data = await sandbox.get_file(uri, read_timeout=300)
|
|
69
71
|
except SandboxFileNotFoundException:
|
|
70
72
|
console.warning(f"File {path.name} not found in storage: {uri=} {scan_id=}")
|
|
71
73
|
if progress:
|
|
72
74
|
progress.stop_task(task_id)
|
|
73
75
|
progress.update(task_id=task_id, visible=False)
|
|
74
76
|
return
|
|
77
|
+
except aiohttp.SocketTimeoutError as e:
|
|
78
|
+
if progress:
|
|
79
|
+
progress.stop_task(task_id)
|
|
80
|
+
progress.update(task_id=task_id, visible=False)
|
|
81
|
+
|
|
82
|
+
raise e
|
|
75
83
|
|
|
76
84
|
if decompress:
|
|
77
85
|
try:
|
|
@@ -138,7 +146,11 @@ async def download(
|
|
|
138
146
|
if sandbox_result.details.sandbox is None:
|
|
139
147
|
continue
|
|
140
148
|
|
|
141
|
-
if
|
|
149
|
+
if (
|
|
150
|
+
sandbox_result.details.sandbox.image
|
|
151
|
+
and (image_id := sandbox_result.details.sandbox.image.image_id)
|
|
152
|
+
and out_dir.parts[-1] != image_id
|
|
153
|
+
):
|
|
142
154
|
output = out_dir / image_id
|
|
143
155
|
else:
|
|
144
156
|
output = out_dir
|
|
@@ -4,18 +4,16 @@ from ptsandbox.models import ArtifactType, EngineSubsystem, SandboxBaseTaskRespo
|
|
|
4
4
|
from sandbox_cli.models.detections import Detections
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def extract_verdict_from_trace(trace: bytes) ->
|
|
8
|
-
non_generics: set[str] = set()
|
|
9
|
-
generics: set[str] = set()
|
|
10
|
-
|
|
7
|
+
def extract_verdict_from_trace(trace: bytes, suspicious: bool = False) -> list[str]:
|
|
11
8
|
d = Detections(trace)
|
|
12
|
-
for detect in d.malware
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
malware_detects: list[str] = list({detect.name for detect in d.malware})
|
|
10
|
+
suspicious_detects: set[str] = set()
|
|
11
|
+
if suspicious:
|
|
12
|
+
for detect in d.suspicious:
|
|
13
|
+
suspicious_detects.add(detect.name)
|
|
14
|
+
|
|
15
|
+
# malware detects always on top
|
|
16
|
+
return malware_detects + sorted(suspicious_detects)
|
|
19
17
|
|
|
20
18
|
|
|
21
19
|
def extract_network_from_trace(trace: bytes) -> set[str]:
|
|
@@ -15,7 +15,14 @@ from ptsandbox.models import (
|
|
|
15
15
|
VNCMode,
|
|
16
16
|
)
|
|
17
17
|
from rich.markup import escape
|
|
18
|
-
from rich.progress import
|
|
18
|
+
from rich.progress import (
|
|
19
|
+
Progress,
|
|
20
|
+
SpinnerColumn,
|
|
21
|
+
Task,
|
|
22
|
+
TaskID,
|
|
23
|
+
TextColumn,
|
|
24
|
+
TimeElapsedColumn,
|
|
25
|
+
)
|
|
19
26
|
|
|
20
27
|
from sandbox_cli.console import console
|
|
21
28
|
from sandbox_cli.internal.config import VMImage, settings
|
|
@@ -69,6 +76,13 @@ async def _get_compiled_rules(progress: Progress, rules_dir: Path | None, is_loc
|
|
|
69
76
|
return result
|
|
70
77
|
|
|
71
78
|
|
|
79
|
+
def get_elapsed_time(task: Task) -> str:
|
|
80
|
+
hours = int(task.elapsed) // 3600
|
|
81
|
+
minutes = (int(task.elapsed) % 3600) // 60
|
|
82
|
+
seconds = int(task.elapsed) % 60
|
|
83
|
+
return f"[yellow]{hours}:{minutes:02d}:{seconds:02d}[/]"
|
|
84
|
+
|
|
85
|
+
|
|
72
86
|
async def _prepare_sandbox_new_scan(
|
|
73
87
|
progress: Progress,
|
|
74
88
|
scan_images: set[VMImage | str],
|
|
@@ -246,6 +260,7 @@ async def scan_internal_advanced(
|
|
|
246
260
|
|
|
247
261
|
async with sandbox_sem:
|
|
248
262
|
task_id = progress.add_task(description="Creating task", idx=idx, image=formatted_image, url="...")
|
|
263
|
+
task = progress.tasks[task_id]
|
|
249
264
|
|
|
250
265
|
wait_time = sandbox_options.analysis_duration * 4 + (300 if sandbox_options.analysis_duration < 80 else 120)
|
|
251
266
|
|
|
@@ -266,7 +281,7 @@ async def scan_internal_advanced(
|
|
|
266
281
|
progress.remove_task(task_id)
|
|
267
282
|
return
|
|
268
283
|
except aiohttp.client_exceptions.ClientResponseError as e:
|
|
269
|
-
console.error(f"{image_string} • [yellow]{file_path.name}[/] • {e}")
|
|
284
|
+
console.error(f"{image_string} • [yellow]{file_path.name}[/] • {e} • {get_elapsed_time(task)}")
|
|
270
285
|
progress.remove_task(task_id)
|
|
271
286
|
return
|
|
272
287
|
|
|
@@ -283,11 +298,11 @@ async def scan_internal_advanced(
|
|
|
283
298
|
)
|
|
284
299
|
try:
|
|
285
300
|
if not (awaited_report := await sandbox.wait_for_report(scan_result, wait_time)):
|
|
286
|
-
console.error(f"{final_output} • scan failed")
|
|
301
|
+
console.error(f"{final_output} • scan failed • {get_elapsed_time(task)}")
|
|
287
302
|
progress.remove_task(task_id)
|
|
288
303
|
return
|
|
289
304
|
except SandboxWaitTimeoutException:
|
|
290
|
-
console.error(f"{final_output} • got timeout while waiting")
|
|
305
|
+
console.error(f"{final_output} • got timeout while waiting • {get_elapsed_time(task)}")
|
|
291
306
|
progress.remove_task(task_id)
|
|
292
307
|
return
|
|
293
308
|
|
|
@@ -297,32 +312,37 @@ async def scan_internal_advanced(
|
|
|
297
312
|
(out_dir / settings.report_name).write_text(scan_result.model_dump_json(indent=4), encoding="utf-8")
|
|
298
313
|
|
|
299
314
|
if not (long_report := scan_result.get_long_report()):
|
|
300
|
-
console.error(f"{final_output} • full report not available")
|
|
315
|
+
console.error(f"{final_output} • full report not available • {get_elapsed_time(task)}")
|
|
301
316
|
progress.remove_task(task_id)
|
|
302
317
|
return
|
|
303
318
|
|
|
304
319
|
progress.update(task_id=task_id, description="Downloading results...")
|
|
305
320
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
321
|
+
try:
|
|
322
|
+
await download(
|
|
323
|
+
long_report,
|
|
324
|
+
sandbox,
|
|
325
|
+
out_dir,
|
|
326
|
+
all=all,
|
|
327
|
+
artifacts=artifacts,
|
|
328
|
+
crashdumps=crashdumps,
|
|
329
|
+
debug=debug,
|
|
330
|
+
decompress=decompress,
|
|
331
|
+
files=download_files,
|
|
332
|
+
logs=True, # by default download logs
|
|
333
|
+
procdumps=procdumps,
|
|
334
|
+
progress=progress,
|
|
335
|
+
video=True, # by default download video
|
|
336
|
+
idx=idx,
|
|
337
|
+
image=formatted_image,
|
|
338
|
+
link=formatted_link,
|
|
339
|
+
)
|
|
340
|
+
except aiohttp.SocketTimeoutError:
|
|
341
|
+
console.error(f"{final_output} • got timeout while downloading results • {get_elapsed_time(task)}")
|
|
342
|
+
progress.remove_task(task_id)
|
|
343
|
+
return
|
|
324
344
|
|
|
325
|
-
console.done(final_output)
|
|
345
|
+
console.done(f"{final_output} • {get_elapsed_time(task)}")
|
|
326
346
|
|
|
327
347
|
progress.remove_task(task_id)
|
|
328
348
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sandbox_cli-0.2.38 → sandbox_cli-0.2.39}/sandbox_cli/utils/unpack/plugins/sort_by_plugins.py
RENAMED
|
File without changes
|