sandbox-cli 0.2.47__tar.gz → 0.2.49__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.
Files changed (37) hide show
  1. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/PKG-INFO +8 -8
  2. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/pyproject.toml +8 -8
  3. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/cli/downloader.py +27 -2
  4. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/cli/scanner/__init__.py +89 -40
  5. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/downloader/__init__.py +18 -0
  6. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/scanner/__init__.py +4 -0
  7. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/scanner/advanced.py +11 -0
  8. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/.gitignore +0 -0
  9. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/LICENSE +0 -0
  10. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/NOTICE +0 -0
  11. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/README.md +0 -0
  12. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/__main__.py +0 -0
  13. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/cli/__init__.py +0 -0
  14. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/cli/images.py +0 -0
  15. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/cli/reporter.py +0 -0
  16. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/cli/rules/__init__.py +0 -0
  17. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/cli/unpack.py +0 -0
  18. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/console.py +0 -0
  19. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/internal/__init__.py +0 -0
  20. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/internal/config.py +0 -0
  21. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/internal/helpers.py +0 -0
  22. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/models/__init__.py +0 -0
  23. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/models/detections.py +0 -0
  24. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/models/sandbox_arguments.py +0 -0
  25. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/__init__.py +0 -0
  26. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/compiler/__init__.py +0 -0
  27. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/compiler/abc.py +0 -0
  28. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/compiler/docker.py +0 -0
  29. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/compiler/ssh.py +0 -0
  30. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/extractors.py +0 -0
  31. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/merge_dll_hooks.py +0 -0
  32. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/scanner/rescan.py +0 -0
  33. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/unpack/__init__.py +0 -0
  34. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/unpack/plugins/__init__.py +0 -0
  35. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/unpack/plugins/abc.py +0 -0
  36. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/sandbox_cli/utils/unpack/plugins/correlation.py +0 -0
  37. {sandbox_cli-0.2.47 → sandbox_cli-0.2.49}/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.47
3
+ Version: 0.2.49
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
@@ -11,16 +11,16 @@ License-Expression: MIT
11
11
  License-File: LICENSE
12
12
  License-File: NOTICE
13
13
  Requires-Python: >=3.11
14
- Requires-Dist: aiofiles>=24.1.0
15
- Requires-Dist: asyncssh>=2.21.1
14
+ Requires-Dist: aiofiles>=25.1.0
15
+ Requires-Dist: asyncssh>=2.22.0
16
16
  Requires-Dist: colorama>=0.4.6
17
- Requires-Dist: cryptography>=46.0.2
18
- Requires-Dist: cyclopts>=4.2.5
17
+ Requires-Dist: cryptography>=46.0.5
18
+ Requires-Dist: cyclopts>=4.10.2
19
19
  Requires-Dist: docker>=7.1.0
20
- Requires-Dist: ptsandbox>=5.0.8
20
+ Requires-Dist: ptsandbox>=5.0.11
21
21
  Requires-Dist: pyzipper>=0.3.6
22
- Requires-Dist: rich>=14.1.0
23
- Requires-Dist: zstandard>=0.23.0
22
+ Requires-Dist: rich>=14.3.3
23
+ Requires-Dist: zstandard>=0.25.0
24
24
  Description-Content-Type: text/markdown
25
25
 
26
26
  ![Image](https://raw.githubusercontent.com/Security-Experts-Community/sandbox-cli/refs/heads/main/docs/assets/logo_with_text.svg)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sandbox-cli"
3
- version = "0.2.47"
3
+ version = "0.2.49"
4
4
  description = "Command line tool for interaction with sandboxes"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -8,16 +8,16 @@ license = "MIT"
8
8
  license-files = ["LICENSE", "NOTICE"]
9
9
  authors = [{ name = "Alexey Kolesnikov" }]
10
10
  dependencies = [
11
- "aiofiles>=24.1.0",
12
- "asyncssh>=2.21.1",
11
+ "aiofiles>=25.1.0",
12
+ "asyncssh>=2.22.0",
13
13
  "colorama>=0.4.6",
14
- "cryptography>=46.0.2",
15
- "cyclopts>=4.2.5",
14
+ "cryptography>=46.0.5",
15
+ "cyclopts>=4.10.2",
16
16
  "docker>=7.1.0",
17
- "ptsandbox>=5.0.8",
17
+ "ptsandbox>=5.0.11",
18
18
  "pyzipper>=0.3.6",
19
- "rich>=14.1.0",
20
- "zstandard>=0.23.0",
19
+ "rich>=14.3.3",
20
+ "zstandard>=0.25.0",
21
21
  ]
22
22
 
23
23
  [project.urls]
@@ -161,13 +161,31 @@ async def download_command(
161
161
  group="Download options",
162
162
  ),
163
163
  ] = False,
164
+ amsi: Annotated[
165
+ bool,
166
+ Parameter(
167
+ name=["--amsi", "-am"],
168
+ help="Download amsi-dumps",
169
+ negative="",
170
+ group="Download options",
171
+ ),
172
+ ] = False,
173
+ dex: Annotated[
174
+ bool,
175
+ Parameter(
176
+ name=["--dex", "-dx"],
177
+ help="Download dex-dumps",
178
+ negative="",
179
+ group="Download options",
180
+ ),
181
+ ] = False,
164
182
  query: Annotated[
165
183
  str | None,
166
184
  Parameter(
167
185
  name=["--query", "-q"],
168
186
  help="Query for searching tasks (leave empty for last tasks)",
169
187
  group="Search",
170
- )
188
+ ),
171
189
  ] = None,
172
190
  count: Annotated[
173
191
  int,
@@ -175,7 +193,7 @@ async def download_command(
175
193
  name=["--count", "-c"],
176
194
  help="How many tasks find and download",
177
195
  group="Search",
178
- )
196
+ ),
179
197
  ] = 20,
180
198
  ) -> None:
181
199
  """
@@ -206,6 +224,8 @@ async def download_command(
206
224
  logs: bool = False,
207
225
  procdumps: bool = False,
208
226
  video: bool = False,
227
+ amsi: bool = False,
228
+ dex: bool = False,
209
229
  progress: Progress | None = None,
210
230
  ) -> None:
211
231
  await download(
@@ -219,6 +239,8 @@ async def download_command(
219
239
  crashdumps=crashdumps,
220
240
  procdumps=procdumps,
221
241
  video=video,
242
+ amsi=amsi,
243
+ dex=dex,
222
244
  logs=logs,
223
245
  decompress=decompress,
224
246
  progress=progress,
@@ -266,6 +288,8 @@ async def download_command(
266
288
  crashdumps=crashdumps,
267
289
  procdumps=procdumps,
268
290
  video=video,
291
+ amsi=amsi,
292
+ dex=dex,
269
293
  logs=logs,
270
294
  decompress=decompress,
271
295
  progress=progress,
@@ -304,6 +328,7 @@ async def download_command(
304
328
  # clear last line
305
329
  sys.stdout.write("\033[F\033[K")
306
330
 
331
+
307
332
  def download_email(
308
333
  emails: Annotated[
309
334
  list[Path],
@@ -1,4 +1,5 @@
1
1
  import sys
2
+ from collections import deque
2
3
  from collections.abc import Sequence
3
4
  from pathlib import Path
4
5
  from typing import Annotated, Any
@@ -51,6 +52,40 @@ def rules_path_resolver(_: Any, tokens: Sequence[Token]) -> Path | None:
51
52
  return path
52
53
 
53
54
 
55
+ def files_path_resolver(files: list[Path]) -> list[Path] | None:
56
+ files_queue = deque(files)
57
+ files_for_analysis: set[Path] = set()
58
+
59
+ is_ok = True
60
+ while files_queue:
61
+ file = files_queue.popleft()
62
+
63
+ # process wildcards
64
+ if sys.platform == "win32" and "*" in str(file):
65
+ for f in Path.cwd().glob(str(file)):
66
+ if f.is_file():
67
+ files_queue.append(f)
68
+ continue
69
+
70
+ file = file.expanduser().resolve()
71
+
72
+ if not file.exists():
73
+ console.error(f"{str(file)} doesn't exist")
74
+ is_ok = False
75
+
76
+ if file.is_dir():
77
+ files_for_analysis.update(file.glob("**/*"))
78
+ continue
79
+
80
+ files_for_analysis.add(file)
81
+
82
+ if len(files_for_analysis) == 0:
83
+ console.error("Nothing to scan")
84
+ is_ok = False
85
+
86
+ return list(files_for_analysis) if is_ok else None
87
+
88
+
54
89
  @scanner.command(name="re-scan")
55
90
  async def re_scan(
56
91
  traces: Annotated[
@@ -322,6 +357,24 @@ async def scan(
322
357
  group="Download options",
323
358
  ),
324
359
  ] = False,
360
+ amsi: Annotated[
361
+ bool,
362
+ Parameter(
363
+ name=["--amsi", "-am"],
364
+ help="Download amsi-dumps",
365
+ negative="",
366
+ group="Download options",
367
+ ),
368
+ ] = False,
369
+ dex: Annotated[
370
+ bool,
371
+ Parameter(
372
+ name=["--dex", "-dx"],
373
+ help="Download dex-dumps",
374
+ negative="",
375
+ group="Download options",
376
+ ),
377
+ ] = False,
325
378
  decompress: Annotated[
326
379
  bool,
327
380
  Parameter(
@@ -356,27 +409,9 @@ async def scan(
356
409
  if images is None:
357
410
  images = {settings.default_image}
358
411
 
359
- # parse files options
360
- is_ok = True
361
- files_for_analysis: list[Path] = []
362
- for file in files:
363
- file = file.expanduser().resolve()
364
-
365
- if not file.exists():
366
- console.error(f"{str(file)} doesn't exists")
367
- is_ok = False
368
-
369
- if file.is_dir():
370
- files_for_analysis.extend(file.glob("**/*"))
371
- continue
412
+ files_for_analysis = files_path_resolver(files)
372
413
 
373
- files_for_analysis.append(file)
374
-
375
- if not is_ok:
376
- sys.exit(1)
377
-
378
- if len(files_for_analysis) == 0:
379
- console.error("Nothing to scan")
414
+ if not files_for_analysis:
380
415
  sys.exit(1)
381
416
 
382
417
  await scan_internal(
@@ -401,6 +436,8 @@ async def scan(
401
436
  procdumps=procdumps,
402
437
  decompress=decompress,
403
438
  open_browser=open_browser,
439
+ amsi=amsi,
440
+ dex=dex,
404
441
  )
405
442
 
406
443
 
@@ -637,6 +674,15 @@ async def scan_new(
637
674
  negative="",
638
675
  ),
639
676
  ] = None,
677
+ file_type_as_ext: Annotated[
678
+ bool | None,
679
+ Parameter(
680
+ name=["--file-type-as-ext", "-fe"],
681
+ help="Use a file type as an extension for a file",
682
+ group="Sandbox Options",
683
+ negative="",
684
+ ),
685
+ ] = None,
640
686
  all: Annotated[
641
687
  bool,
642
688
  Parameter(
@@ -691,6 +737,24 @@ async def scan_new(
691
737
  group="Download options",
692
738
  ),
693
739
  ] = False,
740
+ amsi: Annotated[
741
+ bool,
742
+ Parameter(
743
+ name=["--amsi", "-am"],
744
+ help="Download amsi-dumps",
745
+ negative="",
746
+ group="Download options",
747
+ ),
748
+ ] = False,
749
+ dex: Annotated[
750
+ bool,
751
+ Parameter(
752
+ name=["--dex", "-dx"],
753
+ help="Download dex-dumps",
754
+ negative="",
755
+ group="Download options",
756
+ ),
757
+ ] = False,
694
758
  decompress: Annotated[
695
759
  bool,
696
760
  Parameter(
@@ -730,27 +794,9 @@ async def scan_new(
730
794
  if extra_files is None:
731
795
  extra_files = []
732
796
 
733
- # parse files options
734
- is_ok = True
735
- files_for_analysis: list[Path] = []
736
- for file in files:
737
- file = file.expanduser().resolve()
738
-
739
- if not file.exists():
740
- console.error(f"{str(file)} doesn't exists")
741
- is_ok = False
742
-
743
- if file.is_dir():
744
- files_for_analysis.extend(file.glob("**/*"))
745
- continue
797
+ files_for_analysis = files_path_resolver(files)
746
798
 
747
- files_for_analysis.append(file)
748
-
749
- if not is_ok:
750
- sys.exit(1)
751
-
752
- if len(files_for_analysis) == 0:
753
- console.error("Nothing to scan")
799
+ if not files_for_analysis:
754
800
  sys.exit(1)
755
801
 
756
802
  await scan_internal_advanced(
@@ -790,4 +836,7 @@ async def scan_new(
790
836
  open_browser=open_browser,
791
837
  preserve_filename=preserve_filename,
792
838
  outbound_connections=outbound_connections,
839
+ amsi=amsi,
840
+ dex=dex,
841
+ file_type_as_ext=file_type_as_ext,
793
842
  )
@@ -25,8 +25,10 @@ semaphore = asyncio.Semaphore(value=16)
25
25
  # At first glance, it's counter-intuitive, but ArtifactType can contain not only predefined fields in it, but also newly added.
26
26
  # This usually happens when a new feature is released in the sandbox.
27
27
  _KNOWN_ARTIFACT_TYPES = {
28
+ ArtifactType.AMSI,
28
29
  ArtifactType.ARCHIVE,
29
30
  ArtifactType.COMPRESSED,
31
+ ArtifactType.DEX_DUMP,
30
32
  ArtifactType.EMAIL,
31
33
  ArtifactType.FILE,
32
34
  ArtifactType.PROCESS_DUMP,
@@ -122,6 +124,8 @@ async def download(
122
124
  logs: bool = False,
123
125
  procdumps: bool = False,
124
126
  video: bool = False,
127
+ amsi: bool = False,
128
+ dex: bool = False,
125
129
  progress: Progress | None = None,
126
130
  idx: str | None = None,
127
131
  image: str | None = None,
@@ -213,6 +217,20 @@ async def download(
213
217
  decompress=decompress,
214
218
  )
215
219
 
220
+ if artifact.type == ArtifactType.AMSI and (amsi or all):
221
+ add_task(
222
+ output / "amsi",
223
+ f"{artifact.file_info.sha256}.bin",
224
+ artifact.file_info.file_uri,
225
+ )
226
+
227
+ if artifact.type == ArtifactType.DEX_DUMP and (dex or all):
228
+ add_task(
229
+ output / "dex",
230
+ f"{artifact.file_info.sha256}.bin",
231
+ artifact.file_info.file_uri,
232
+ )
233
+
216
234
  if all and artifact.type not in _KNOWN_ARTIFACT_TYPES:
217
235
  add_task(
218
236
  output / "other",
@@ -153,6 +153,8 @@ async def scan_internal(
153
153
  crashdumps: bool,
154
154
  procdumps: bool,
155
155
  decompress: bool,
156
+ amsi: bool,
157
+ dex: bool,
156
158
  open_browser: bool,
157
159
  ) -> None:
158
160
  key = get_key_by_name(key_name)
@@ -216,6 +218,8 @@ async def scan_internal(
216
218
  files=download_files,
217
219
  crashdumps=crashdumps,
218
220
  procdumps=procdumps,
221
+ amsi=amsi,
222
+ dex=dex,
219
223
  video=True,
220
224
  logs=True,
221
225
  decompress=decompress,
@@ -105,6 +105,7 @@ async def _prepare_sandbox_new_scan(
105
105
  skip_sample_run: bool,
106
106
  vnc_mode: VNCMode,
107
107
  outbound_connections: list[str] | None,
108
+ file_type_as_ext: bool | None,
108
109
  ) -> tuple[Sandbox, SandboxOptionsAdvanced, set[VMImage | str]]:
109
110
  sandbox = Sandbox(key=sandbox_key)
110
111
 
@@ -200,6 +201,10 @@ async def _prepare_sandbox_new_scan(
200
201
  progress.console.print(f"{console.INFO} Commandline: {custom_command}")
201
202
  sandbox_options.custom_command = custom_command
202
203
 
204
+ if file_type_as_ext is not None:
205
+ progress.console.print(f"{console.INFO} Using magic types from the sandbox")
206
+ sandbox_options.debug_options["file_type_as_ext"] = file_type_as_ext
207
+
203
208
  # add extra options
204
209
  sandbox_options.debug_options["allowed_outbound_connections"] = outbound_connections or []
205
210
  sandbox_options.procdump_new_processes_on_finish = not no_procdumps_on_finish
@@ -250,9 +255,12 @@ async def scan_internal_advanced(
250
255
  crashdumps: bool,
251
256
  procdumps: bool,
252
257
  decompress: bool,
258
+ amsi: bool,
259
+ dex: bool,
253
260
  open_browser: bool,
254
261
  preserve_filename: bool,
255
262
  outbound_connections: list[str] | None,
263
+ file_type_as_ext: bool | None,
256
264
  ) -> None:
257
265
  key = get_key_by_name(key_name)
258
266
  sandbox_sem = asyncio.Semaphore(value=key.max_workers)
@@ -365,6 +373,8 @@ async def scan_internal_advanced(
365
373
  procdumps=procdumps,
366
374
  progress=progress,
367
375
  video=True, # by default download video
376
+ amsi=amsi,
377
+ dex=dex,
368
378
  idx=idx,
369
379
  image=formatted_image,
370
380
  link=formatted_link,
@@ -424,6 +434,7 @@ async def scan_internal_advanced(
424
434
  skip_sample_run=skip_sample_run,
425
435
  vnc_mode=vnc_mode,
426
436
  outbound_connections=outbound_connections,
437
+ file_type_as_ext=file_type_as_ext,
427
438
  )
428
439
  max_image_length = max(len(x) for x in images)
429
440
  for i, image_id in enumerate(images):
File without changes
File without changes
File without changes
File without changes