sandbox-cli 0.2.29__tar.gz → 0.2.31__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.29 → sandbox_cli-0.2.31}/PKG-INFO +6 -6
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/pyproject.toml +6 -6
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/cli/__init__.py +2 -1
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/cli/reporter.py +55 -2
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/cli/scanner/__init__.py +31 -4
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/internal/config.py +7 -0
- sandbox_cli-0.2.31/sandbox_cli/internal/helpers.py +88 -0
- sandbox_cli-0.2.31/sandbox_cli/models/sandbox_arguments.py +22 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/downloader/__init__.py +8 -2
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/scanner/__init__.py +21 -44
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/scanner/advanced.py +22 -6
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/scanner/rescan.py +18 -2
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/unpack/plugins/sort_by_plugins.py +1 -1
- sandbox_cli-0.2.29/sandbox_cli/internal/helpers.py +0 -32
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/.gitignore +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/LICENSE +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/NOTICE +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/README.md +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/__main__.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/cli/downloader.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/cli/images.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/cli/rules/__init__.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/cli/unpack.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/console.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/internal/__init__.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/models/__init__.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/models/detections.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/__init__.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/compiler/__init__.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/compiler/abc.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/compiler/docker.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/compiler/ssh.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/extractors.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/merge_dll_hooks.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/unpack/__init__.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/unpack/plugins/__init__.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/unpack/plugins/abc.py +0 -0
- {sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/unpack/plugins/correlation.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.31
|
|
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
|
|
@@ -12,13 +12,13 @@ License-File: LICENSE
|
|
|
12
12
|
License-File: NOTICE
|
|
13
13
|
Requires-Python: >=3.11
|
|
14
14
|
Requires-Dist: aiofiles>=24.1.0
|
|
15
|
-
Requires-Dist: asyncssh>=2.
|
|
16
|
-
Requires-Dist: cryptography>=44.0.
|
|
17
|
-
Requires-Dist: cyclopts>=3.
|
|
15
|
+
Requires-Dist: asyncssh>=2.20.0
|
|
16
|
+
Requires-Dist: cryptography>=44.0.2
|
|
17
|
+
Requires-Dist: cyclopts>=3.16.0
|
|
18
18
|
Requires-Dist: docker>=7.1.0
|
|
19
|
-
Requires-Dist: ptsandbox>=5.0.
|
|
19
|
+
Requires-Dist: ptsandbox>=5.0.4
|
|
20
20
|
Requires-Dist: pyzipper>=0.3.6
|
|
21
|
-
Requires-Dist: rich>=
|
|
21
|
+
Requires-Dist: rich>=14.0.0
|
|
22
22
|
Requires-Dist: zstandard>=0.23.0
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sandbox-cli"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.31"
|
|
4
4
|
description = "Command line tool for interaction with sandboxes"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -9,13 +9,13 @@ license-files = ["LICENSE", "NOTICE"]
|
|
|
9
9
|
authors = [{ name = "Alexey Kolesnikov" }]
|
|
10
10
|
dependencies = [
|
|
11
11
|
"aiofiles>=24.1.0",
|
|
12
|
-
"asyncssh>=2.
|
|
13
|
-
"cryptography>=44.0.
|
|
14
|
-
"cyclopts>=3.
|
|
12
|
+
"asyncssh>=2.20.0",
|
|
13
|
+
"cryptography>=44.0.2",
|
|
14
|
+
"cyclopts>=3.16.0",
|
|
15
15
|
"docker>=7.1.0",
|
|
16
|
-
"ptsandbox>=5.0.
|
|
16
|
+
"ptsandbox>=5.0.4",
|
|
17
17
|
"pyzipper>=0.3.6",
|
|
18
|
-
"rich>=
|
|
18
|
+
"rich>=14.0.0",
|
|
19
19
|
"zstandard>=0.23.0",
|
|
20
20
|
]
|
|
21
21
|
|
|
@@ -4,7 +4,7 @@ Register all commands here
|
|
|
4
4
|
|
|
5
5
|
from cyclopts import App
|
|
6
6
|
|
|
7
|
-
from sandbox_cli.cli.reporter import generate_report
|
|
7
|
+
from sandbox_cli.cli.reporter import generate_report, open_browser
|
|
8
8
|
from sandbox_cli.cli.unpack import unpack_logs
|
|
9
9
|
from sandbox_cli.console import console
|
|
10
10
|
from sandbox_cli.internal.config import configpath, settings
|
|
@@ -32,6 +32,7 @@ app = App(
|
|
|
32
32
|
|
|
33
33
|
app.command(name=["conv", "unpack"])(unpack_logs)
|
|
34
34
|
app.command(name="report")(generate_report)
|
|
35
|
+
app.command(name="browser")(open_browser)
|
|
35
36
|
|
|
36
37
|
if len(settings.sandbox_keys) > 0:
|
|
37
38
|
from sandbox_cli.cli.downloader import download_command, download_email
|
|
@@ -9,6 +9,8 @@ from ptsandbox.models import SandboxBaseTaskResponse
|
|
|
9
9
|
from rich.table import Table
|
|
10
10
|
|
|
11
11
|
from sandbox_cli.console import console
|
|
12
|
+
from sandbox_cli.internal.helpers import format_link, get_key_by_name, open_link
|
|
13
|
+
from sandbox_cli.models.sandbox_arguments import SandboxArguments
|
|
12
14
|
from sandbox_cli.utils.extractors import (
|
|
13
15
|
extract_memory,
|
|
14
16
|
extract_network_from_trace,
|
|
@@ -72,6 +74,10 @@ def generate_report(
|
|
|
72
74
|
if not report_file.exists():
|
|
73
75
|
continue
|
|
74
76
|
|
|
77
|
+
scan_config_file = root / "scan_config.json"
|
|
78
|
+
if not scan_config_file.exists():
|
|
79
|
+
continue
|
|
80
|
+
|
|
75
81
|
if latest and (
|
|
76
82
|
datetime.datetime.now() - datetime.datetime.fromtimestamp(report_file.stat().st_mtime)
|
|
77
83
|
) > datetime.timedelta(hours=2):
|
|
@@ -81,6 +87,10 @@ def generate_report(
|
|
|
81
87
|
report_data = fd.read()
|
|
82
88
|
scan_data = SandboxBaseTaskResponse.model_validate_json(report_data)
|
|
83
89
|
|
|
90
|
+
with open(scan_config_file, encoding="utf-8") as fd:
|
|
91
|
+
scan_config_data = fd.read()
|
|
92
|
+
scan_config = SandboxArguments.model_validate_json(scan_config_data)
|
|
93
|
+
|
|
84
94
|
if (report := scan_data.get_long_report()) is None:
|
|
85
95
|
console.warning(f"A report without behavioral analysis: {root}")
|
|
86
96
|
continue
|
|
@@ -101,6 +111,9 @@ def generate_report(
|
|
|
101
111
|
if image is None:
|
|
102
112
|
image = root.name
|
|
103
113
|
|
|
114
|
+
key = get_key_by_name(scan_config.sandbox_key_name)
|
|
115
|
+
link = format_link(scan_data, key=key)
|
|
116
|
+
|
|
104
117
|
data.append(
|
|
105
118
|
{
|
|
106
119
|
"sample": report.artifacts[0].file_info.file_path, # type: ignore
|
|
@@ -109,6 +122,7 @@ def generate_report(
|
|
|
109
122
|
"static": delimeter.join(extract_static(report)),
|
|
110
123
|
"memory": delimeter.join(extract_memory(report)),
|
|
111
124
|
"network": delimeter.join(extract_network_from_trace(corr_trace)),
|
|
125
|
+
"sandbox": link,
|
|
112
126
|
}
|
|
113
127
|
)
|
|
114
128
|
|
|
@@ -116,8 +130,8 @@ def generate_report(
|
|
|
116
130
|
case "md":
|
|
117
131
|
md_report_head = textwrap.dedent(
|
|
118
132
|
"""
|
|
119
|
-
| Sample | Image | Verdict | Static | Memory | Network |
|
|
120
|
-
| --- | --- | --- | --- | --- | --- |"""
|
|
133
|
+
| Sample | Image | Verdict | Static | Memory | Network | Sandbox |
|
|
134
|
+
| --- | --- | --- | --- | --- | --- | --- |"""
|
|
121
135
|
).strip()
|
|
122
136
|
|
|
123
137
|
md_report_base = "|{sample}|{image}|{verdict}|{static}|{memory}|{network}|"
|
|
@@ -132,6 +146,8 @@ def generate_report(
|
|
|
132
146
|
table.add_column("Static", overflow="fold", style="bold")
|
|
133
147
|
table.add_column("Memory", overflow="fold")
|
|
134
148
|
table.add_column("Network", overflow="fold")
|
|
149
|
+
table.add_column("Sandbox", overflow="fold")
|
|
150
|
+
|
|
135
151
|
for d in data:
|
|
136
152
|
table.add_row(
|
|
137
153
|
d["sample"],
|
|
@@ -140,7 +156,44 @@ def generate_report(
|
|
|
140
156
|
d["static"],
|
|
141
157
|
d["memory"],
|
|
142
158
|
d["network"],
|
|
159
|
+
d["sandbox"],
|
|
143
160
|
)
|
|
144
161
|
console.print(table)
|
|
145
162
|
case _:
|
|
146
163
|
pass
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def open_browser(
|
|
167
|
+
path: Annotated[
|
|
168
|
+
Path,
|
|
169
|
+
Parameter(
|
|
170
|
+
help="Folder with sandbox report (report.json and scan_config.json)",
|
|
171
|
+
),
|
|
172
|
+
] = Path(),
|
|
173
|
+
) -> None:
|
|
174
|
+
"""
|
|
175
|
+
Open sandbox link in the default browser.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
report_file = path / "report.json"
|
|
179
|
+
if not report_file.exists():
|
|
180
|
+
console.error(f"Can't find report.json: {path}")
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
scan_config_file = path / "scan_config.json"
|
|
184
|
+
if not scan_config_file.exists():
|
|
185
|
+
console.error(f"Can't find scan_config.json: {path}")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
with open(report_file, encoding="utf-8") as fd:
|
|
189
|
+
report_data = fd.read()
|
|
190
|
+
report = SandboxBaseTaskResponse.model_validate_json(report_data)
|
|
191
|
+
|
|
192
|
+
with open(scan_config_file, encoding="utf-8") as fd:
|
|
193
|
+
scan_config_data = fd.read()
|
|
194
|
+
scan_config = SandboxArguments.model_validate_json(scan_config_data)
|
|
195
|
+
|
|
196
|
+
key = get_key_by_name(scan_config.sandbox_key_name)
|
|
197
|
+
link = format_link(report, key=key)
|
|
198
|
+
|
|
199
|
+
open_link(link)
|
|
@@ -110,6 +110,14 @@ async def re_scan(
|
|
|
110
110
|
group="Download options",
|
|
111
111
|
),
|
|
112
112
|
] = False,
|
|
113
|
+
open_browser: Annotated[
|
|
114
|
+
bool,
|
|
115
|
+
Parameter(
|
|
116
|
+
name=["--open-browser", "-ob"],
|
|
117
|
+
help="Open analysis link in the default browser",
|
|
118
|
+
negative="",
|
|
119
|
+
),
|
|
120
|
+
] = False,
|
|
113
121
|
) -> None:
|
|
114
122
|
"""
|
|
115
123
|
Send traces to re-scan.
|
|
@@ -137,6 +145,7 @@ async def re_scan(
|
|
|
137
145
|
is_local=is_local,
|
|
138
146
|
unpack=unpack,
|
|
139
147
|
debug=debug,
|
|
148
|
+
open_browser=open_browser,
|
|
140
149
|
)
|
|
141
150
|
|
|
142
151
|
|
|
@@ -312,6 +321,14 @@ async def scan(
|
|
|
312
321
|
negative="",
|
|
313
322
|
),
|
|
314
323
|
] = False,
|
|
324
|
+
open_browser: Annotated[
|
|
325
|
+
bool,
|
|
326
|
+
Parameter(
|
|
327
|
+
name=["--open-browser", "-ob"],
|
|
328
|
+
help="Open analysis link in the default browser",
|
|
329
|
+
negative="",
|
|
330
|
+
),
|
|
331
|
+
] = False,
|
|
315
332
|
) -> None:
|
|
316
333
|
"""
|
|
317
334
|
Send files to scan with the sandbox.
|
|
@@ -374,6 +391,7 @@ async def scan(
|
|
|
374
391
|
crashdumps=crashdumps,
|
|
375
392
|
procdumps=procdumps,
|
|
376
393
|
decompress=decompress,
|
|
394
|
+
open_browser=open_browser,
|
|
377
395
|
)
|
|
378
396
|
|
|
379
397
|
|
|
@@ -496,11 +514,11 @@ async def scan_new(
|
|
|
496
514
|
group="Sandbox Options",
|
|
497
515
|
),
|
|
498
516
|
] = 3,
|
|
499
|
-
|
|
517
|
+
no_procdumps_on_finish: Annotated[
|
|
500
518
|
bool,
|
|
501
519
|
Parameter(
|
|
502
|
-
name=["--
|
|
503
|
-
help="
|
|
520
|
+
name=["--no-procdumps-on-finish", "-P"],
|
|
521
|
+
help="Disable dumps for all created and not finished processes",
|
|
504
522
|
group="Sandbox Options",
|
|
505
523
|
negative="",
|
|
506
524
|
),
|
|
@@ -630,6 +648,14 @@ async def scan_new(
|
|
|
630
648
|
negative="",
|
|
631
649
|
),
|
|
632
650
|
] = False,
|
|
651
|
+
open_browser: Annotated[
|
|
652
|
+
bool,
|
|
653
|
+
Parameter(
|
|
654
|
+
name=["--open-browser", "-ob"],
|
|
655
|
+
help="Open analysis link in the default browser",
|
|
656
|
+
negative="",
|
|
657
|
+
),
|
|
658
|
+
] = False,
|
|
633
659
|
) -> None:
|
|
634
660
|
"""
|
|
635
661
|
Send files to scan with the sandbox (advanced scan).
|
|
@@ -682,7 +708,7 @@ async def scan_new(
|
|
|
682
708
|
fake_name=fake_name,
|
|
683
709
|
unpack=unpack,
|
|
684
710
|
priority=priority,
|
|
685
|
-
|
|
711
|
+
no_procdumps_on_finish=no_procdumps_on_finish,
|
|
686
712
|
bootkitmon=bootkitmon,
|
|
687
713
|
bootkitmon_duration=bootkitmon_duration,
|
|
688
714
|
mitm_disabled=mitm_disabled,
|
|
@@ -698,4 +724,5 @@ async def scan_new(
|
|
|
698
724
|
crashdumps=crashdumps,
|
|
699
725
|
procdumps=procdumps,
|
|
700
726
|
decompress=decompress,
|
|
727
|
+
open_browser=open_browser,
|
|
701
728
|
)
|
|
@@ -84,6 +84,10 @@ class Settings(BaseModel):
|
|
|
84
84
|
description=self.description,
|
|
85
85
|
)
|
|
86
86
|
|
|
87
|
+
class Browser(BaseModel):
|
|
88
|
+
path: Path
|
|
89
|
+
args: list[str]
|
|
90
|
+
|
|
87
91
|
# default settings (not changable)
|
|
88
92
|
linux_images: set[VMImage] = {
|
|
89
93
|
VMImage.ALTWORKSTATION_X64,
|
|
@@ -119,10 +123,13 @@ class Settings(BaseModel):
|
|
|
119
123
|
docker: Docker = Docker()
|
|
120
124
|
sandbox: list[Sandbox] = []
|
|
121
125
|
rules_path: Path | None = Field(default=None, alias="rules-path")
|
|
126
|
+
browser: Browser | None = Field(default=None)
|
|
122
127
|
|
|
123
128
|
def model_post_init(self, __context: Any) -> None:
|
|
124
129
|
self.sandbox_keys = [x.sandbox_key for x in self.sandbox]
|
|
125
130
|
self.rules_path = Path(self.rules_path) if self.rules_path else None
|
|
131
|
+
if self.browser:
|
|
132
|
+
self.browser.args.append("%s")
|
|
126
133
|
|
|
127
134
|
|
|
128
135
|
def load_config(path: Path) -> Settings:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import webbrowser
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, overload
|
|
5
|
+
|
|
6
|
+
from ptsandbox import Sandbox, SandboxKey
|
|
7
|
+
from ptsandbox.models import SandboxBaseTaskResponse
|
|
8
|
+
|
|
9
|
+
from sandbox_cli.console import console
|
|
10
|
+
from sandbox_cli.internal.config import settings
|
|
11
|
+
from sandbox_cli.models.sandbox_arguments import SandboxArguments
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_key_by_name(key_name: str) -> SandboxKey:
|
|
15
|
+
for sandbox_key in settings.sandbox_keys:
|
|
16
|
+
if sandbox_key.name == key_name:
|
|
17
|
+
return sandbox_key
|
|
18
|
+
raise KeyError()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_sandbox_key_by_host(task_host: str) -> SandboxKey:
|
|
22
|
+
for sandbox_key in settings.sandbox_keys:
|
|
23
|
+
if sandbox_key.host == task_host:
|
|
24
|
+
return sandbox_key
|
|
25
|
+
|
|
26
|
+
raise KeyError()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validate_key(_: Any, value: Any) -> None:
|
|
30
|
+
try:
|
|
31
|
+
get_key_by_name(value)
|
|
32
|
+
except KeyError:
|
|
33
|
+
console.error(
|
|
34
|
+
f'Key "{value}" doesn\'t exists in config. Available keys: "{'","'.join(x.name for x in settings.sandbox_keys)}"'
|
|
35
|
+
)
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def format_link(
|
|
41
|
+
report: SandboxBaseTaskResponse,
|
|
42
|
+
*,
|
|
43
|
+
sandbox: Sandbox,
|
|
44
|
+
key: SandboxKey | None = None,
|
|
45
|
+
) -> str: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@overload
|
|
49
|
+
def format_link(
|
|
50
|
+
report: SandboxBaseTaskResponse,
|
|
51
|
+
*,
|
|
52
|
+
sandbox: Sandbox | None = None,
|
|
53
|
+
key: SandboxKey,
|
|
54
|
+
) -> str: ...
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def format_link(
|
|
58
|
+
report: SandboxBaseTaskResponse,
|
|
59
|
+
*,
|
|
60
|
+
sandbox: Sandbox | None = None,
|
|
61
|
+
key: SandboxKey | None = None,
|
|
62
|
+
) -> str:
|
|
63
|
+
key = key or (sandbox.api.key if sandbox else None)
|
|
64
|
+
|
|
65
|
+
if not key:
|
|
66
|
+
console.error("Key not provided")
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
|
|
69
|
+
if not (short_report := report.get_short_report()):
|
|
70
|
+
return "Unknown"
|
|
71
|
+
|
|
72
|
+
return f"https://{key.host}/tasks/{short_report.scan_id}"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def save_scan_arguments(out_dir: Path, scan_args: SandboxArguments) -> None:
|
|
76
|
+
scan_config_path = out_dir / "scan_config.json"
|
|
77
|
+
scan_config_path.write_text(scan_args.model_dump_json(exclude="debug_options", indent=4), encoding="utf-8")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def open_link(link: str) -> None:
|
|
81
|
+
if settings.browser != None:
|
|
82
|
+
webbrowser.register("new_default_browser", None, webbrowser.GenericBrowser([settings.browser.path, *settings.browser.args]), preferred=True)
|
|
83
|
+
if not webbrowser.open(link):
|
|
84
|
+
console.error("Can't open link in the specified browser. Please check browser path and args.")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
if not webbrowser.open_new_tab(link):
|
|
88
|
+
console.error("Can't open link in the default browser. Try adding path and args for your browser to the config file.")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from ptsandbox.models.api import SandboxOptions, SandboxOptionsAdvanced
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ScanType(str, Enum):
|
|
8
|
+
SCAN = "scan"
|
|
9
|
+
RE_SCAN = "re-scan"
|
|
10
|
+
SCAN_NEW = "scan-new"
|
|
11
|
+
|
|
12
|
+
def __str__(self) -> str:
|
|
13
|
+
return str(self.value)
|
|
14
|
+
|
|
15
|
+
def __repr__(self) -> str:
|
|
16
|
+
return str(self.value)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SandboxArguments(BaseModel):
|
|
20
|
+
type: ScanType
|
|
21
|
+
sandbox_key_name: str
|
|
22
|
+
sandbox_options: SandboxOptions | SandboxOptionsAdvanced
|
|
@@ -40,6 +40,14 @@ async def _save_artifact(
|
|
|
40
40
|
|
|
41
41
|
# sanitize path
|
|
42
42
|
path = out_dir / Path(str(path).replace(" ", "_"))
|
|
43
|
+
n = 1
|
|
44
|
+
while path.exists():
|
|
45
|
+
if not path.with_name(path.name + f"_{n}").exists():
|
|
46
|
+
path = path.with_name(path.name + f"_{n}")
|
|
47
|
+
break
|
|
48
|
+
n += 1
|
|
49
|
+
path.parent.mkdir(exist_ok=True, parents=True)
|
|
50
|
+
path.touch()
|
|
43
51
|
task_id: TaskID = None # type: ignore
|
|
44
52
|
|
|
45
53
|
async with semaphore:
|
|
@@ -53,8 +61,6 @@ async def _save_artifact(
|
|
|
53
61
|
)
|
|
54
62
|
else:
|
|
55
63
|
task_id = progress.add_task(rf"\[[green1]{scan_id}[/]] {escape(path.name)}")
|
|
56
|
-
|
|
57
|
-
path.parent.mkdir(exist_ok=True, parents=True)
|
|
58
64
|
try:
|
|
59
65
|
downloaded_data = await sandbox.get_file(uri)
|
|
60
66
|
except SandboxFileNotFoundException:
|
|
@@ -2,62 +2,27 @@ import asyncio
|
|
|
2
2
|
import sys
|
|
3
3
|
from collections.abc import Coroutine
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
import aiofiles
|
|
8
8
|
from ptsandbox import Sandbox
|
|
9
|
-
from ptsandbox.models import
|
|
10
|
-
SandboxBaseScanTaskRequest,
|
|
11
|
-
SandboxBaseTaskResponse,
|
|
12
|
-
SandboxKey,
|
|
13
|
-
SandboxOptions,
|
|
14
|
-
)
|
|
9
|
+
from ptsandbox.models import SandboxBaseScanTaskRequest, SandboxKey, SandboxOptions
|
|
15
10
|
|
|
16
11
|
from sandbox_cli.console import console
|
|
17
12
|
from sandbox_cli.internal.config import VMImage, settings
|
|
18
|
-
from sandbox_cli.internal.helpers import
|
|
13
|
+
from sandbox_cli.internal.helpers import (
|
|
14
|
+
format_link,
|
|
15
|
+
get_key_by_name,
|
|
16
|
+
open_link,
|
|
17
|
+
save_scan_arguments,
|
|
18
|
+
)
|
|
19
|
+
from sandbox_cli.models.sandbox_arguments import SandboxArguments, ScanType
|
|
19
20
|
from sandbox_cli.utils.compiler import compile_rules_internal
|
|
20
21
|
from sandbox_cli.utils.downloader import download
|
|
21
22
|
from sandbox_cli.utils.merge_dll_hooks import merge_dll_hooks
|
|
22
23
|
from sandbox_cli.utils.unpack import Unpack
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
@overload
|
|
26
|
-
def format_link(
|
|
27
|
-
report: SandboxBaseTaskResponse,
|
|
28
|
-
*,
|
|
29
|
-
sandbox: Sandbox,
|
|
30
|
-
key: SandboxKey | None = None,
|
|
31
|
-
) -> str: ...
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@overload
|
|
35
|
-
def format_link(
|
|
36
|
-
report: SandboxBaseTaskResponse,
|
|
37
|
-
*,
|
|
38
|
-
sandbox: Sandbox | None = None,
|
|
39
|
-
key: SandboxKey,
|
|
40
|
-
) -> str: ...
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def format_link(
|
|
44
|
-
report: SandboxBaseTaskResponse,
|
|
45
|
-
*,
|
|
46
|
-
sandbox: Sandbox | None = None,
|
|
47
|
-
key: SandboxKey | None = None,
|
|
48
|
-
) -> str:
|
|
49
|
-
key = key or (sandbox.api.key if sandbox else None)
|
|
50
|
-
|
|
51
|
-
if not key:
|
|
52
|
-
console.error("Key not provided")
|
|
53
|
-
sys.exit(1)
|
|
54
|
-
|
|
55
|
-
if not (short_report := report.get_short_report()):
|
|
56
|
-
return "Unknown"
|
|
57
|
-
|
|
58
|
-
return f"https://{key.host}/tasks/{short_report.scan_id}"
|
|
59
|
-
|
|
60
|
-
|
|
61
26
|
async def _get_compiled_rules(rules_dir: Path | None, is_local: bool) -> bytes | None:
|
|
62
27
|
if not rules_dir:
|
|
63
28
|
return None
|
|
@@ -188,6 +153,7 @@ async def scan_internal(
|
|
|
188
153
|
crashdumps: bool,
|
|
189
154
|
procdumps: bool,
|
|
190
155
|
decompress: bool,
|
|
156
|
+
open_browser: bool,
|
|
191
157
|
) -> None:
|
|
192
158
|
key = get_key_by_name(key_name)
|
|
193
159
|
sandbox_sem = asyncio.Semaphore(value=key.max_workers)
|
|
@@ -222,6 +188,10 @@ async def scan_internal(
|
|
|
222
188
|
console.info(
|
|
223
189
|
rf"{idx} [magenta]\[{sandbox_options.sandbox.image_id}][/magenta] Waiting [yellow]{file_path.name}[/]: {format_link(scan_result, key=key)}"
|
|
224
190
|
)
|
|
191
|
+
|
|
192
|
+
if open_browser:
|
|
193
|
+
open_link(format_link(scan_result, key=key))
|
|
194
|
+
|
|
225
195
|
awaited_report = await sandbox.wait_for_report(scan_result, wait_time)
|
|
226
196
|
if not awaited_report:
|
|
227
197
|
console.error(f"{idx} Scan [yellow]{file_path.name}[/] failed: {scan_result=}")
|
|
@@ -263,6 +233,13 @@ async def scan_internal(
|
|
|
263
233
|
out_dir: Path,
|
|
264
234
|
idx: str,
|
|
265
235
|
) -> None:
|
|
236
|
+
sandbox_arguments = SandboxArguments(
|
|
237
|
+
type=ScanType.SCAN,
|
|
238
|
+
sandbox_key_name=key.name,
|
|
239
|
+
sandbox_options=sandbox_options.sandbox,
|
|
240
|
+
)
|
|
241
|
+
save_scan_arguments(out_dir, sandbox_arguments)
|
|
242
|
+
|
|
266
243
|
# try:
|
|
267
244
|
await process_file(sandbox_options, file_path, out_dir, idx)
|
|
268
245
|
# except Exception as ex:
|
|
@@ -14,11 +14,16 @@ from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, TimeElaps
|
|
|
14
14
|
|
|
15
15
|
from sandbox_cli.console import console
|
|
16
16
|
from sandbox_cli.internal.config import VMImage, settings
|
|
17
|
-
from sandbox_cli.internal.helpers import
|
|
17
|
+
from sandbox_cli.internal.helpers import (
|
|
18
|
+
format_link,
|
|
19
|
+
get_key_by_name,
|
|
20
|
+
open_link,
|
|
21
|
+
save_scan_arguments,
|
|
22
|
+
)
|
|
23
|
+
from sandbox_cli.models.sandbox_arguments import SandboxArguments, ScanType
|
|
18
24
|
from sandbox_cli.utils.compiler import compile_rules_internal
|
|
19
25
|
from sandbox_cli.utils.downloader import download
|
|
20
26
|
from sandbox_cli.utils.merge_dll_hooks import merge_dll_hooks
|
|
21
|
-
from sandbox_cli.utils.scanner import format_link
|
|
22
27
|
from sandbox_cli.utils.unpack import Unpack
|
|
23
28
|
|
|
24
29
|
DELIMETER = "\n"
|
|
@@ -69,7 +74,7 @@ async def _prepare_sandbox_new_scan(
|
|
|
69
74
|
syscall_hooks: Path | None,
|
|
70
75
|
dll_hooks_dir: Path | None,
|
|
71
76
|
custom_command: str | None,
|
|
72
|
-
|
|
77
|
+
no_procdumps_on_finish: bool,
|
|
73
78
|
bootkitmon: bool,
|
|
74
79
|
bootkitmon_duration: int,
|
|
75
80
|
mitm_disabled: bool,
|
|
@@ -156,7 +161,7 @@ async def _prepare_sandbox_new_scan(
|
|
|
156
161
|
sandbox_options.custom_command = custom_command
|
|
157
162
|
|
|
158
163
|
# add extra options
|
|
159
|
-
sandbox_options.procdump_new_processes_on_finish =
|
|
164
|
+
sandbox_options.procdump_new_processes_on_finish = not no_procdumps_on_finish
|
|
160
165
|
sandbox_options.bootkitmon = bootkitmon
|
|
161
166
|
sandbox_options.analysis_duration_bootkitmon = bootkitmon_duration
|
|
162
167
|
sandbox_options.mitm_enabled = not mitm_disabled
|
|
@@ -184,7 +189,7 @@ async def scan_internal_advanced(
|
|
|
184
189
|
fake_name: str | None,
|
|
185
190
|
unpack: bool,
|
|
186
191
|
priority: int,
|
|
187
|
-
|
|
192
|
+
no_procdumps_on_finish: bool,
|
|
188
193
|
bootkitmon: bool,
|
|
189
194
|
bootkitmon_duration: int,
|
|
190
195
|
mitm_disabled: bool,
|
|
@@ -200,6 +205,7 @@ async def scan_internal_advanced(
|
|
|
200
205
|
crashdumps: bool,
|
|
201
206
|
procdumps: bool,
|
|
202
207
|
decompress: bool,
|
|
208
|
+
open_browser: bool,
|
|
203
209
|
) -> None:
|
|
204
210
|
key = get_key_by_name(key_name)
|
|
205
211
|
sandbox_sem = asyncio.Semaphore(value=key.max_workers)
|
|
@@ -259,6 +265,9 @@ async def scan_internal_advanced(
|
|
|
259
265
|
formatted_link = f"[medium_purple]{format_link(scan_result, key=key)}[/]"
|
|
260
266
|
final_output = f"{image_string} • [yellow]{file_path.name}[/] • {formatted_link}"
|
|
261
267
|
|
|
268
|
+
if open_browser:
|
|
269
|
+
open_link(format_link(scan_result, key=key))
|
|
270
|
+
|
|
262
271
|
progress.update(
|
|
263
272
|
task_id=task_id,
|
|
264
273
|
description=f"Waiting [yellow]{file_path.name}[/]",
|
|
@@ -313,6 +322,13 @@ async def scan_internal_advanced(
|
|
|
313
322
|
out_dir: Path,
|
|
314
323
|
idx: str,
|
|
315
324
|
) -> None:
|
|
325
|
+
sandbox_arguments = SandboxArguments(
|
|
326
|
+
type=ScanType.SCAN_NEW,
|
|
327
|
+
sandbox_key_name=key.name,
|
|
328
|
+
sandbox_options=sandbox_options,
|
|
329
|
+
)
|
|
330
|
+
save_scan_arguments(out_dir, sandbox_arguments)
|
|
331
|
+
|
|
316
332
|
# try:
|
|
317
333
|
await process_file(sandbox_options, file_path, out_dir, idx)
|
|
318
334
|
# except Exception as ex:
|
|
@@ -332,7 +348,7 @@ async def scan_internal_advanced(
|
|
|
332
348
|
syscall_hooks,
|
|
333
349
|
dll_hooks_dir,
|
|
334
350
|
custom_command,
|
|
335
|
-
|
|
351
|
+
no_procdumps_on_finish,
|
|
336
352
|
bootkitmon,
|
|
337
353
|
bootkitmon_duration,
|
|
338
354
|
mitm_disabled,
|
|
@@ -18,10 +18,15 @@ from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, TimeElaps
|
|
|
18
18
|
|
|
19
19
|
from sandbox_cli.console import console
|
|
20
20
|
from sandbox_cli.internal.config import settings
|
|
21
|
-
from sandbox_cli.internal.helpers import
|
|
21
|
+
from sandbox_cli.internal.helpers import (
|
|
22
|
+
format_link,
|
|
23
|
+
get_key_by_name,
|
|
24
|
+
open_link,
|
|
25
|
+
save_scan_arguments,
|
|
26
|
+
)
|
|
27
|
+
from sandbox_cli.models.sandbox_arguments import SandboxArguments, ScanType
|
|
22
28
|
from sandbox_cli.utils.compiler import compile_rules_internal
|
|
23
29
|
from sandbox_cli.utils.downloader import download
|
|
24
|
-
from sandbox_cli.utils.scanner import format_link
|
|
25
30
|
from sandbox_cli.utils.unpack import Unpack
|
|
26
31
|
|
|
27
32
|
|
|
@@ -92,6 +97,7 @@ async def rescan_internal(
|
|
|
92
97
|
is_local: bool,
|
|
93
98
|
unpack: bool,
|
|
94
99
|
debug: bool,
|
|
100
|
+
open_browser: bool,
|
|
95
101
|
) -> None:
|
|
96
102
|
key = get_key_by_name(key_name)
|
|
97
103
|
sandbox_sem = asyncio.Semaphore(value=key.max_workers)
|
|
@@ -148,6 +154,9 @@ async def rescan_internal(
|
|
|
148
154
|
formatted_link = f"[medium_purple]{format_link(rescan_result, key=key)}[/]"
|
|
149
155
|
final_output = f"[yellow]{trace.name}[/] • {formatted_link}"
|
|
150
156
|
|
|
157
|
+
if open_browser:
|
|
158
|
+
open_link(format_link(rescan_result, key=key))
|
|
159
|
+
|
|
151
160
|
progress.update(
|
|
152
161
|
task_id=task_id,
|
|
153
162
|
description=f"Waiting for full report for [yellow]{trace.name}[/]",
|
|
@@ -237,10 +246,16 @@ async def rescan_internal(
|
|
|
237
246
|
tasks: list[Coroutine[Any, Any, None]] = []
|
|
238
247
|
with progress:
|
|
239
248
|
sandbox, sandbox_options = await _prepare_rescan_options(progress, rules_dir, key, is_local)
|
|
249
|
+
sandbox_arguments = SandboxArguments(
|
|
250
|
+
type=ScanType.RE_SCAN,
|
|
251
|
+
sandbox_key_name=key.name,
|
|
252
|
+
sandbox_options=sandbox_options.sandbox,
|
|
253
|
+
)
|
|
240
254
|
|
|
241
255
|
if len(traces) == 1:
|
|
242
256
|
local_out_dir = out_dir / "rescan"
|
|
243
257
|
local_out_dir.mkdir(parents=True, exist_ok=True)
|
|
258
|
+
save_scan_arguments(local_out_dir, sandbox_arguments)
|
|
244
259
|
tasks.append(wrapper(traces[0], local_out_dir, "1/1"))
|
|
245
260
|
else:
|
|
246
261
|
for i, trace in enumerate(traces):
|
|
@@ -251,6 +266,7 @@ async def rescan_internal(
|
|
|
251
266
|
local_out_dir = out_dir / trace.stem
|
|
252
267
|
|
|
253
268
|
local_out_dir.mkdir(parents=True, exist_ok=True)
|
|
269
|
+
save_scan_arguments(local_out_dir, sandbox_arguments)
|
|
254
270
|
idx = f"{i + 1}/{len(traces)}"
|
|
255
271
|
tasks.append(wrapper(trace, local_out_dir, idx))
|
|
256
272
|
|
{sandbox_cli-0.2.29 → sandbox_cli-0.2.31}/sandbox_cli/utils/unpack/plugins/sort_by_plugins.py
RENAMED
|
@@ -15,7 +15,7 @@ class SortByPlugins(BasePlugin):
|
|
|
15
15
|
console.warning(f"{file} not exist")
|
|
16
16
|
return
|
|
17
17
|
|
|
18
|
-
with open(file, errors="ignore") as fd:
|
|
18
|
+
with open(file, errors="ignore", encoding="utf-8") as fd:
|
|
19
19
|
raw_trace = fd.readlines()
|
|
20
20
|
|
|
21
21
|
plugins: defaultdict[str, list[str]] = defaultdict(list)
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from ptsandbox import SandboxKey
|
|
5
|
-
|
|
6
|
-
from sandbox_cli.console import console
|
|
7
|
-
from sandbox_cli.internal.config import settings
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def get_key_by_name(key_name: str) -> SandboxKey:
|
|
11
|
-
for sandbox_key in settings.sandbox_keys:
|
|
12
|
-
if sandbox_key.name == key_name:
|
|
13
|
-
return sandbox_key
|
|
14
|
-
raise KeyError()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_sandbox_key_by_host(task_host: str) -> SandboxKey:
|
|
18
|
-
for sandbox_key in settings.sandbox_keys:
|
|
19
|
-
if sandbox_key.host == task_host:
|
|
20
|
-
return sandbox_key
|
|
21
|
-
|
|
22
|
-
raise KeyError()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def validate_key(_: Any, value: Any) -> None:
|
|
26
|
-
try:
|
|
27
|
-
get_key_by_name(value)
|
|
28
|
-
except KeyError:
|
|
29
|
-
console.error(
|
|
30
|
-
f'Key "{value}" doesn\'t exists in config. Available keys: "{'","'.join(x.name for x in settings.sandbox_keys)}"'
|
|
31
|
-
)
|
|
32
|
-
sys.exit(1)
|
|
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
|