sandbox-cli 0.2.32__tar.gz → 0.2.34__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.32 → sandbox_cli-0.2.34}/PKG-INFO +6 -6
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/pyproject.toml +6 -6
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/cli/downloader.py +73 -33
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/cli/images.py +2 -2
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/cli/scanner/__init__.py +15 -6
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/internal/helpers.py +5 -4
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/downloader/__init__.py +63 -52
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/scanner/rescan.py +3 -8
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/.gitignore +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/LICENSE +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/NOTICE +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/README.md +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/__main__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/cli/__init__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/cli/reporter.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/cli/rules/__init__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/cli/unpack.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/console.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/internal/__init__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/internal/config.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/models/__init__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/models/detections.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/models/sandbox_arguments.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/__init__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/compiler/__init__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/compiler/abc.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/compiler/docker.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/compiler/ssh.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/extractors.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/merge_dll_hooks.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/scanner/__init__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/scanner/advanced.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/unpack/__init__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/unpack/plugins/__init__.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/unpack/plugins/abc.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/unpack/plugins/correlation.py +0 -0
- {sandbox_cli-0.2.32 → sandbox_cli-0.2.34}/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.34
|
|
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>=
|
|
17
|
-
Requires-Dist: cyclopts>=3.
|
|
15
|
+
Requires-Dist: asyncssh>=2.21.0
|
|
16
|
+
Requires-Dist: cryptography>=45.0.4
|
|
17
|
+
Requires-Dist: cyclopts>=3.22.5
|
|
18
18
|
Requires-Dist: docker>=7.1.0
|
|
19
|
-
Requires-Dist: ptsandbox>=5.0.
|
|
19
|
+
Requires-Dist: ptsandbox>=5.0.6
|
|
20
20
|
Requires-Dist: pyzipper>=0.3.6
|
|
21
|
-
Requires-Dist: rich>=14.
|
|
21
|
+
Requires-Dist: rich>=14.1.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.34"
|
|
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>=
|
|
14
|
-
"cyclopts>=3.
|
|
12
|
+
"asyncssh>=2.21.0",
|
|
13
|
+
"cryptography>=45.0.4",
|
|
14
|
+
"cyclopts>=3.22.5",
|
|
15
15
|
"docker>=7.1.0",
|
|
16
|
-
"ptsandbox>=5.0.
|
|
16
|
+
"ptsandbox>=5.0.6",
|
|
17
17
|
"pyzipper>=0.3.6",
|
|
18
|
-
"rich>=14.
|
|
18
|
+
"rich>=14.1.0",
|
|
19
19
|
"zstandard>=0.23.0",
|
|
20
20
|
]
|
|
21
21
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import os
|
|
2
3
|
from collections.abc import Coroutine
|
|
3
4
|
from http import HTTPStatus
|
|
4
5
|
from pathlib import Path
|
|
@@ -9,10 +10,11 @@ from uuid import UUID
|
|
|
9
10
|
import aiofiles
|
|
10
11
|
import aiohttp
|
|
11
12
|
import aiohttp.client_exceptions
|
|
13
|
+
import orjson
|
|
12
14
|
from cyclopts import Parameter
|
|
13
15
|
from ptsandbox import Sandbox
|
|
14
16
|
from ptsandbox.models import Artifact, SandboxBaseTaskResponse
|
|
15
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
|
17
|
+
from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, TimeElapsedColumn
|
|
16
18
|
|
|
17
19
|
from sandbox_cli.console import console
|
|
18
20
|
from sandbox_cli.internal.config import settings
|
|
@@ -51,20 +53,19 @@ async def download_command(
|
|
|
51
53
|
Parameter(
|
|
52
54
|
help="Links to tasks or task ids",
|
|
53
55
|
negative="",
|
|
54
|
-
required=True,
|
|
55
56
|
),
|
|
56
|
-
],
|
|
57
|
+
] = [],
|
|
57
58
|
/,
|
|
58
59
|
*,
|
|
59
60
|
key: Annotated[
|
|
60
61
|
str,
|
|
61
62
|
Parameter(
|
|
62
63
|
name=["--key", "-k"],
|
|
63
|
-
help=f"The key to access the sandbox **{'**,**'.join(x.name for x in settings.sandbox_keys)}**",
|
|
64
|
+
help=f"The key to access the sandbox **{'**,**'.join(x.name.get_secret_value() for x in settings.sandbox_keys)}**",
|
|
64
65
|
validator=validate_key,
|
|
65
66
|
group="Sandbox",
|
|
66
67
|
),
|
|
67
|
-
] = settings.sandbox_keys[0].name,
|
|
68
|
+
] = settings.sandbox_keys[0].name.get_secret_value(),
|
|
68
69
|
out_dir: Annotated[
|
|
69
70
|
Path,
|
|
70
71
|
Parameter(
|
|
@@ -127,7 +128,7 @@ async def download_command(
|
|
|
127
128
|
crashdumps: Annotated[
|
|
128
129
|
bool,
|
|
129
130
|
Parameter(
|
|
130
|
-
name=["--crashdumps", "-
|
|
131
|
+
name=["--crashdumps", "-C"],
|
|
131
132
|
help="Download crashdumps (maybe be more 1GB)",
|
|
132
133
|
negative="",
|
|
133
134
|
group="Download options",
|
|
@@ -160,11 +161,35 @@ async def download_command(
|
|
|
160
161
|
group="Download options",
|
|
161
162
|
),
|
|
162
163
|
] = False,
|
|
164
|
+
query: Annotated[
|
|
165
|
+
str | None,
|
|
166
|
+
Parameter(
|
|
167
|
+
name=["--query", "-q"],
|
|
168
|
+
help="Query for searching tasks (leave empty for last tasks)",
|
|
169
|
+
group="Search",
|
|
170
|
+
)
|
|
171
|
+
] = None,
|
|
172
|
+
count: Annotated[
|
|
173
|
+
int,
|
|
174
|
+
Parameter(
|
|
175
|
+
name=["--count", "-c"],
|
|
176
|
+
help="How many tasks find and download",
|
|
177
|
+
group="Search",
|
|
178
|
+
)
|
|
179
|
+
] = 20,
|
|
163
180
|
) -> None:
|
|
164
181
|
"""
|
|
165
182
|
Download any artifact from the sandbox.
|
|
166
183
|
"""
|
|
167
184
|
|
|
185
|
+
progress = Progress(
|
|
186
|
+
SpinnerColumn(),
|
|
187
|
+
TextColumn("{task.description}"),
|
|
188
|
+
TimeElapsedColumn(),
|
|
189
|
+
console=console,
|
|
190
|
+
transient=True,
|
|
191
|
+
)
|
|
192
|
+
|
|
168
193
|
async def worker(
|
|
169
194
|
report: SandboxBaseTaskResponse.LongReport,
|
|
170
195
|
sandbox: Sandbox,
|
|
@@ -197,39 +222,30 @@ async def download_command(
|
|
|
197
222
|
)
|
|
198
223
|
|
|
199
224
|
if unpack and out_dir.exists():
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
transient=True,
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
tasks: list[Coroutine[Any, Any, Artifact.EngineResult | None]] = []
|
|
225
|
+
# multiple images can be downloaded from the task
|
|
226
|
+
if not (out_dir / "events-correlated.log.gz").exists():
|
|
227
|
+
subfolders = [f.path for f in os.scandir(out_dir) if f.is_dir()]
|
|
228
|
+
for folder in subfolders:
|
|
229
|
+
Unpack(Path(folder)).run()
|
|
230
|
+
else:
|
|
231
|
+
Unpack(out_dir).run()
|
|
211
232
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if not sandbox or not task_id:
|
|
215
|
-
continue
|
|
233
|
+
async def create_task(sandbox: Sandbox, task_id: str) -> None:
|
|
234
|
+
progress_task_id = progress.add_task(description=rf"\[[green1]{task_id}[/]] fetching info", start=True)
|
|
216
235
|
|
|
217
236
|
try:
|
|
218
237
|
result = await sandbox.get_report(task_id=task_id)
|
|
219
238
|
except aiohttp.client_exceptions.ClientResponseError as e:
|
|
220
239
|
if e.status == HTTPStatus.NOT_FOUND:
|
|
221
|
-
console.warning(
|
|
222
|
-
|
|
223
|
-
"Read more about it in: https://security-experts-community.github.io/py-ptsandbox/usage/public-api/download-files/"
|
|
224
|
-
)
|
|
225
|
-
continue
|
|
240
|
+
console.warning(f"Not found information for {task_id}")
|
|
241
|
+
return
|
|
226
242
|
|
|
227
243
|
if (report := result.get_long_report()) is None:
|
|
228
|
-
console.warning(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
244
|
+
console.warning(f"Not found information for {task_id}")
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
progress.stop_task(task_id=progress_task_id)
|
|
248
|
+
progress.update(task_id=progress_task_id, visible=False)
|
|
233
249
|
|
|
234
250
|
tasks.append(
|
|
235
251
|
worker(
|
|
@@ -249,6 +265,30 @@ async def download_command(
|
|
|
249
265
|
)
|
|
250
266
|
)
|
|
251
267
|
|
|
268
|
+
tasks: list[Coroutine[Any, Any, Artifact.EngineResult | None]] = []
|
|
269
|
+
|
|
270
|
+
if query is not None:
|
|
271
|
+
sandbox = Sandbox(get_key_by_name(key_name=key))
|
|
272
|
+
|
|
273
|
+
limit = 40 if count > 40 else count
|
|
274
|
+
viewed, next_cursor = 0, ""
|
|
275
|
+
|
|
276
|
+
while viewed <= count:
|
|
277
|
+
response = await sandbox.get_tasks(query=query, limit=limit, next_cursor=next_cursor)
|
|
278
|
+
|
|
279
|
+
with progress:
|
|
280
|
+
await asyncio.gather(*(create_task(sandbox, task.id) for task in response.tasks))
|
|
281
|
+
|
|
282
|
+
viewed += limit
|
|
283
|
+
next_cursor = response.next_cursor
|
|
284
|
+
|
|
285
|
+
for task in tasks_id:
|
|
286
|
+
sandbox, task_id = get_key_and_task(key, task)
|
|
287
|
+
if not sandbox or not task_id:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
await create_task(sandbox, task_id)
|
|
291
|
+
|
|
252
292
|
with progress:
|
|
253
293
|
await asyncio.gather(*tasks)
|
|
254
294
|
|
|
@@ -273,11 +313,11 @@ def download_email(
|
|
|
273
313
|
str,
|
|
274
314
|
Parameter(
|
|
275
315
|
name=["--key", "-k"],
|
|
276
|
-
help=f"The key to access the sandbox **{'**,**'.join(x.name for x in settings.sandbox_keys)}**",
|
|
316
|
+
help=f"The key to access the sandbox **{'**,**'.join(x.name.get_secret_value() for x in settings.sandbox_keys)}**",
|
|
277
317
|
validator=validate_key,
|
|
278
318
|
group="Sandbox",
|
|
279
319
|
),
|
|
280
|
-
] = settings.sandbox_keys[0].name,
|
|
320
|
+
] = settings.sandbox_keys[0].name.get_secret_value(),
|
|
281
321
|
) -> None:
|
|
282
322
|
"""
|
|
283
323
|
Upload an email and get its headers.
|
|
@@ -15,11 +15,11 @@ async def get_images(
|
|
|
15
15
|
str,
|
|
16
16
|
Parameter(
|
|
17
17
|
name=["--key", "-k"],
|
|
18
|
-
help=f"The key to access the sandbox **{'**,**'.join(x.name for x in settings.sandbox_keys)}**",
|
|
18
|
+
help=f"The key to access the sandbox **{'**,**'.join(x.name.get_secret_value() for x in settings.sandbox_keys)}**",
|
|
19
19
|
validator=validate_key,
|
|
20
20
|
group="Sandbox",
|
|
21
21
|
),
|
|
22
|
-
] = settings.sandbox_keys[0].name,
|
|
22
|
+
] = settings.sandbox_keys[0].name.get_secret_value(),
|
|
23
23
|
) -> None:
|
|
24
24
|
"""
|
|
25
25
|
Get available images in the sandbox.
|
|
@@ -80,11 +80,11 @@ async def re_scan(
|
|
|
80
80
|
str,
|
|
81
81
|
Parameter(
|
|
82
82
|
name=["--key", "-k"],
|
|
83
|
-
help=f"The key to access the sandbox **{'**,**'.join(x.name for x in settings.sandbox_keys)}**",
|
|
83
|
+
help=f"The key to access the sandbox **{'**,**'.join(x.name.get_secret_value() for x in settings.sandbox_keys)}**",
|
|
84
84
|
validator=validate_key,
|
|
85
85
|
group="Sandbox Options",
|
|
86
86
|
),
|
|
87
|
-
] = settings.sandbox_keys[0].name,
|
|
87
|
+
] = settings.sandbox_keys[0].name.get_secret_value(),
|
|
88
88
|
is_local: Annotated[
|
|
89
89
|
bool,
|
|
90
90
|
Parameter(
|
|
@@ -118,6 +118,14 @@ async def re_scan(
|
|
|
118
118
|
negative="",
|
|
119
119
|
),
|
|
120
120
|
] = False,
|
|
121
|
+
timeout: Annotated[
|
|
122
|
+
int,
|
|
123
|
+
Parameter(
|
|
124
|
+
name=["--timeout", "-t"],
|
|
125
|
+
help="Response waiting time (increase this value if large traces are scanned)",
|
|
126
|
+
validator=validators.Number(gt=0, lt=3600),
|
|
127
|
+
),
|
|
128
|
+
] = 300,
|
|
121
129
|
) -> None:
|
|
122
130
|
"""
|
|
123
131
|
Send traces to re-scan.
|
|
@@ -146,6 +154,7 @@ async def re_scan(
|
|
|
146
154
|
unpack=unpack,
|
|
147
155
|
debug=debug,
|
|
148
156
|
open_browser=open_browser,
|
|
157
|
+
timeout=timeout,
|
|
149
158
|
)
|
|
150
159
|
|
|
151
160
|
|
|
@@ -189,11 +198,11 @@ async def scan(
|
|
|
189
198
|
str,
|
|
190
199
|
Parameter(
|
|
191
200
|
name=["--key", "-k"],
|
|
192
|
-
help=f"The key to access the sandbox **{'**,**'.join(x.name for x in settings.sandbox_keys)}**",
|
|
201
|
+
help=f"The key to access the sandbox **{'**,**'.join(x.name.get_secret_value() for x in settings.sandbox_keys)}**",
|
|
193
202
|
validator=validate_key,
|
|
194
203
|
group="Sandbox Options",
|
|
195
204
|
),
|
|
196
|
-
] = settings.sandbox_keys[0].name,
|
|
205
|
+
] = settings.sandbox_keys[0].name.get_secret_value(),
|
|
197
206
|
is_local: Annotated[
|
|
198
207
|
bool,
|
|
199
208
|
Parameter(
|
|
@@ -435,11 +444,11 @@ async def scan_new(
|
|
|
435
444
|
str,
|
|
436
445
|
Parameter(
|
|
437
446
|
name=["--key", "-k"],
|
|
438
|
-
help=f"The key to access the sandbox **{'**,**'.join(x.name for x in settings.sandbox_keys)}**",
|
|
447
|
+
help=f"The key to access the sandbox **{'**,**'.join(x.name.get_secret_value() for x in settings.sandbox_keys)}**",
|
|
439
448
|
validator=validate_key,
|
|
440
449
|
group="Sandbox Options",
|
|
441
450
|
),
|
|
442
|
-
] = settings.sandbox_keys[0].name,
|
|
451
|
+
] = settings.sandbox_keys[0].name.get_secret_value(),
|
|
443
452
|
is_local: Annotated[
|
|
444
453
|
bool,
|
|
445
454
|
Parameter(
|
|
@@ -13,7 +13,7 @@ from sandbox_cli.models.sandbox_arguments import SandboxArguments
|
|
|
13
13
|
|
|
14
14
|
def get_key_by_name(key_name: str) -> SandboxKey:
|
|
15
15
|
for sandbox_key in settings.sandbox_keys:
|
|
16
|
-
if sandbox_key.name == key_name:
|
|
16
|
+
if sandbox_key.name.get_secret_value() == key_name:
|
|
17
17
|
return sandbox_key
|
|
18
18
|
raise KeyError()
|
|
19
19
|
|
|
@@ -30,8 +30,9 @@ def validate_key(_: Any, value: Any) -> None:
|
|
|
30
30
|
try:
|
|
31
31
|
get_key_by_name(value)
|
|
32
32
|
except KeyError:
|
|
33
|
+
available = "', '".join(x.name.get_secret_value() for x in settings.sandbox_keys)
|
|
33
34
|
console.error(
|
|
34
|
-
f'Key "{value}" doesn\'t
|
|
35
|
+
f'Key "{value}" doesn\'t exist in config. Available keys: "{available}"'
|
|
35
36
|
)
|
|
36
37
|
sys.exit(1)
|
|
37
38
|
|
|
@@ -78,11 +79,11 @@ def save_scan_arguments(out_dir: Path, scan_args: SandboxArguments) -> None:
|
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
def open_link(link: str) -> None:
|
|
81
|
-
if settings.browser
|
|
82
|
+
if settings.browser is not None:
|
|
82
83
|
webbrowser.register("new_default_browser", None, webbrowser.GenericBrowser([settings.browser.path, *settings.browser.args]), preferred=True)
|
|
83
84
|
if not webbrowser.open(link):
|
|
84
85
|
console.error("Can't open link in the specified browser. Please check browser path and args.")
|
|
85
86
|
return
|
|
86
|
-
|
|
87
|
+
|
|
87
88
|
if not webbrowser.open_new_tab(link):
|
|
88
89
|
console.error("Can't open link in the default browser. Try adding path and args for your browser to the config file.")
|
|
@@ -18,8 +18,9 @@ from rich.markup import escape
|
|
|
18
18
|
from rich.progress import Progress, TaskID
|
|
19
19
|
|
|
20
20
|
from sandbox_cli.console import console
|
|
21
|
+
from sandbox_cli.internal.config import settings
|
|
21
22
|
|
|
22
|
-
semaphore = asyncio.Semaphore(value=
|
|
23
|
+
semaphore = asyncio.Semaphore(value=16)
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
async def _save_artifact(
|
|
@@ -28,6 +29,7 @@ async def _save_artifact(
|
|
|
28
29
|
out_dir: Path,
|
|
29
30
|
path: Path,
|
|
30
31
|
file_uri: str,
|
|
32
|
+
overwrite: bool = False,
|
|
31
33
|
decompress: bool = False,
|
|
32
34
|
progress: Progress | None = None,
|
|
33
35
|
idx: str | None = None,
|
|
@@ -40,12 +42,14 @@ async def _save_artifact(
|
|
|
40
42
|
|
|
41
43
|
# sanitize path
|
|
42
44
|
path = out_dir / Path(str(path).replace(" ", "_"))
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
if not overwrite:
|
|
46
|
+
n = 1
|
|
47
|
+
while path.exists():
|
|
48
|
+
if not path.with_name(path.name + f"_{n}").exists():
|
|
49
|
+
path = path.with_name(path.name + f"_{n}")
|
|
50
|
+
break
|
|
51
|
+
n += 1
|
|
52
|
+
|
|
49
53
|
path.parent.mkdir(exist_ok=True, parents=True)
|
|
50
54
|
path.touch()
|
|
51
55
|
task_id: TaskID = None # type: ignore
|
|
@@ -107,7 +111,7 @@ async def download(
|
|
|
107
111
|
) -> None:
|
|
108
112
|
tasks: list[Coroutine[Any, Any, None]] = []
|
|
109
113
|
|
|
110
|
-
def add_task(out_dir: Path, path: Path, file_uri: str, decompress: bool = False) -> None:
|
|
114
|
+
def add_task(out_dir: Path, path: Path, file_uri: str, decompress: bool = False, overwrite: bool = False) -> None:
|
|
111
115
|
tasks.append(
|
|
112
116
|
_save_artifact(
|
|
113
117
|
scan_id=report.scan_id,
|
|
@@ -120,66 +124,73 @@ async def download(
|
|
|
120
124
|
image=image,
|
|
121
125
|
link=link,
|
|
122
126
|
decompress=decompress,
|
|
127
|
+
overwrite=overwrite,
|
|
123
128
|
)
|
|
124
129
|
)
|
|
125
130
|
|
|
126
131
|
for artifact in report.artifacts:
|
|
127
|
-
sandbox_result
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
for sandbox_result in artifact.get_sandbox_results():
|
|
133
|
+
if sandbox_result is None:
|
|
134
|
+
continue
|
|
130
135
|
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
if sandbox_result.details is None:
|
|
137
|
+
continue
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
if sandbox_result.details.sandbox is None:
|
|
140
|
+
continue
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
LogType.EVENT_NORMALIZED,
|
|
142
|
-
LogType.EVENT_RAW,
|
|
143
|
-
LogType.NETWORK,
|
|
144
|
-
}:
|
|
145
|
-
add_task(out_dir, log.file_name, log.file_uri)
|
|
142
|
+
if sandbox_result.details.sandbox.image and (image_id := sandbox_result.details.sandbox.image.image_id):
|
|
143
|
+
output = out_dir / image_id
|
|
144
|
+
else:
|
|
145
|
+
output = out_dir
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
for log in sandbox_result.details.sandbox.logs:
|
|
148
|
+
# download default logs by default
|
|
149
|
+
if (all or logs) and log.type in {
|
|
150
|
+
LogType.EVENT_CORRELATED,
|
|
151
|
+
LogType.EVENT_NORMALIZED,
|
|
152
|
+
LogType.EVENT_RAW,
|
|
153
|
+
LogType.NETWORK,
|
|
154
|
+
}:
|
|
155
|
+
add_task(output, log.file_name, log.file_uri, overwrite=True)
|
|
149
156
|
|
|
150
|
-
|
|
151
|
-
|
|
157
|
+
if (all or video) and log.type == LogType.SCREENSHOT:
|
|
158
|
+
add_task(output, log.file_name, log.file_uri, overwrite=True)
|
|
152
159
|
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
if (all or crashdumps) and log.file_name in {"crashdump.bin", "crashdump.metadata"}:
|
|
161
|
+
add_task(output / "crashdumps", log.file_name, log.file_uri)
|
|
155
162
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
continue
|
|
163
|
+
if (all or debug) and log.type in {LogType.DEBUG, LogType.GRAPH}:
|
|
164
|
+
add_task(output / "debug", log.file_name, log.file_uri)
|
|
159
165
|
|
|
160
|
-
if
|
|
161
|
-
|
|
166
|
+
if artifacts or files or procdumps or all:
|
|
167
|
+
if not sandbox_result.details:
|
|
168
|
+
continue
|
|
162
169
|
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
if not sandbox_result.details.sandbox:
|
|
171
|
+
continue
|
|
165
172
|
|
|
166
|
-
|
|
167
|
-
if not artifact.file_info:
|
|
173
|
+
if not sandbox_result.details.sandbox.artifacts:
|
|
168
174
|
continue
|
|
169
175
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
for artifact in sandbox_result.details.sandbox.artifacts:
|
|
177
|
+
if not artifact.file_info:
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
if artifact.type == ArtifactType.FILE and (files or artifacts or all):
|
|
181
|
+
add_task(
|
|
182
|
+
output / "artifacts",
|
|
183
|
+
artifact.file_info.file_path.removeprefix("/"),
|
|
184
|
+
artifact.file_info.file_uri,
|
|
185
|
+
)
|
|
186
|
+
if artifact.type == ArtifactType.PROCESS_DUMP and (procdumps or artifacts or all):
|
|
187
|
+
add_task(
|
|
188
|
+
output / "process_dump",
|
|
189
|
+
artifact.file_info.details.process_dump.process_name.removeprefix("/"), # type: ignore
|
|
190
|
+
artifact.file_info.file_uri,
|
|
191
|
+
decompress=decompress,
|
|
192
|
+
)
|
|
193
|
+
|
|
183
194
|
if not tasks:
|
|
184
195
|
console.info(f"Nothing to download from {report.scan_id}")
|
|
185
196
|
|
|
@@ -98,6 +98,7 @@ async def rescan_internal(
|
|
|
98
98
|
unpack: bool,
|
|
99
99
|
debug: bool,
|
|
100
100
|
open_browser: bool,
|
|
101
|
+
timeout: int,
|
|
101
102
|
) -> None:
|
|
102
103
|
key = get_key_by_name(key_name)
|
|
103
104
|
sandbox_sem = asyncio.Semaphore(value=key.max_workers)
|
|
@@ -128,19 +129,13 @@ async def rescan_internal(
|
|
|
128
129
|
async with sandbox_sem:
|
|
129
130
|
task_id = progress.add_task(description="Creating task", idx=idx, url="...")
|
|
130
131
|
|
|
131
|
-
wait_time = (
|
|
132
|
-
round(sandbox_options.sandbox.analysis_duration * 1.5)
|
|
133
|
-
if sandbox_options.sandbox.analysis_duration > 70
|
|
134
|
-
else 70
|
|
135
|
-
)
|
|
136
|
-
|
|
137
132
|
try:
|
|
138
133
|
rescan_result = await sandbox.create_rescan(
|
|
139
134
|
drakvuf_trace,
|
|
140
135
|
tcpdump_pcap,
|
|
141
136
|
options=sandbox_options,
|
|
142
137
|
rules=None,
|
|
143
|
-
read_timeout=
|
|
138
|
+
read_timeout=timeout,
|
|
144
139
|
)
|
|
145
140
|
except SandboxUploadException as e:
|
|
146
141
|
console.error(f"[yellow]{trace}[/] • an error occurred when uploading a file to the server • {e}")
|
|
@@ -162,7 +157,7 @@ async def rescan_internal(
|
|
|
162
157
|
description=f"Waiting for full report for [yellow]{trace.name}[/]",
|
|
163
158
|
url=formatted_link,
|
|
164
159
|
)
|
|
165
|
-
if not (awaited_report := await sandbox.wait_for_report(rescan_result,
|
|
160
|
+
if not (awaited_report := await sandbox.wait_for_report(rescan_result, timeout)):
|
|
166
161
|
console.error(f"Rescan failed for [yellow]{trace.name}[/] • {formatted_link} • {rescan_result}")
|
|
167
162
|
progress.remove_task(task_id)
|
|
168
163
|
return
|
|
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.32 → sandbox_cli-0.2.34}/sandbox_cli/utils/unpack/plugins/sort_by_plugins.py
RENAMED
|
File without changes
|