sandbox-cli 0.2.47__tar.gz → 0.2.48__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.48}/PKG-INFO +8 -8
  2. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/pyproject.toml +8 -8
  3. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/cli/downloader.py +27 -2
  4. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/cli/scanner/__init__.py +79 -40
  5. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/downloader/__init__.py +18 -0
  6. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/scanner/__init__.py +4 -0
  7. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/scanner/advanced.py +4 -0
  8. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/.gitignore +0 -0
  9. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/LICENSE +0 -0
  10. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/NOTICE +0 -0
  11. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/README.md +0 -0
  12. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/__main__.py +0 -0
  13. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/cli/__init__.py +0 -0
  14. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/cli/images.py +0 -0
  15. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/cli/reporter.py +0 -0
  16. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/cli/rules/__init__.py +0 -0
  17. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/cli/unpack.py +0 -0
  18. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/console.py +0 -0
  19. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/internal/__init__.py +0 -0
  20. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/internal/config.py +0 -0
  21. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/internal/helpers.py +0 -0
  22. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/models/__init__.py +0 -0
  23. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/models/detections.py +0 -0
  24. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/models/sandbox_arguments.py +0 -0
  25. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/__init__.py +0 -0
  26. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/compiler/__init__.py +0 -0
  27. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/compiler/abc.py +0 -0
  28. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/compiler/docker.py +0 -0
  29. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/compiler/ssh.py +0 -0
  30. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/extractors.py +0 -0
  31. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/merge_dll_hooks.py +0 -0
  32. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/scanner/rescan.py +0 -0
  33. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/unpack/__init__.py +0 -0
  34. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/unpack/plugins/__init__.py +0 -0
  35. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/unpack/plugins/abc.py +0 -0
  36. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/sandbox_cli/utils/unpack/plugins/correlation.py +0 -0
  37. {sandbox_cli-0.2.47 → sandbox_cli-0.2.48}/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.48
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.10
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.48"
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.10",
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
 
@@ -691,6 +728,24 @@ async def scan_new(
691
728
  group="Download options",
692
729
  ),
693
730
  ] = False,
731
+ amsi: Annotated[
732
+ bool,
733
+ Parameter(
734
+ name=["--amsi", "-am"],
735
+ help="Download amsi-dumps",
736
+ negative="",
737
+ group="Download options",
738
+ ),
739
+ ] = False,
740
+ dex: Annotated[
741
+ bool,
742
+ Parameter(
743
+ name=["--dex", "-dx"],
744
+ help="Download dex-dumps",
745
+ negative="",
746
+ group="Download options",
747
+ ),
748
+ ] = False,
694
749
  decompress: Annotated[
695
750
  bool,
696
751
  Parameter(
@@ -730,27 +785,9 @@ async def scan_new(
730
785
  if extra_files is None:
731
786
  extra_files = []
732
787
 
733
- # parse files options
734
- is_ok = True
735
- files_for_analysis: list[Path] = []
736
- for file in files:
737
- file = file.expanduser().resolve()
788
+ files_for_analysis = files_path_resolver(files)
738
789
 
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
746
-
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")
790
+ if not files_for_analysis:
754
791
  sys.exit(1)
755
792
 
756
793
  await scan_internal_advanced(
@@ -790,4 +827,6 @@ async def scan_new(
790
827
  open_browser=open_browser,
791
828
  preserve_filename=preserve_filename,
792
829
  outbound_connections=outbound_connections,
830
+ amsi=amsi,
831
+ dex=dex,
793
832
  )
@@ -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,
@@ -250,6 +250,8 @@ async def scan_internal_advanced(
250
250
  crashdumps: bool,
251
251
  procdumps: bool,
252
252
  decompress: bool,
253
+ amsi: bool,
254
+ dex: bool,
253
255
  open_browser: bool,
254
256
  preserve_filename: bool,
255
257
  outbound_connections: list[str] | None,
@@ -365,6 +367,8 @@ async def scan_internal_advanced(
365
367
  procdumps=procdumps,
366
368
  progress=progress,
367
369
  video=True, # by default download video
370
+ amsi=amsi,
371
+ dex=dex,
368
372
  idx=idx,
369
373
  image=formatted_image,
370
374
  link=formatted_link,
File without changes
File without changes
File without changes
File without changes