sandbox-cli 0.2.26__py3-none-any.whl

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 (36) hide show
  1. sandbox_cli/__main__.py +28 -0
  2. sandbox_cli/cli/__init__.py +46 -0
  3. sandbox_cli/cli/downloader.py +308 -0
  4. sandbox_cli/cli/images.py +45 -0
  5. sandbox_cli/cli/reporter.py +146 -0
  6. sandbox_cli/cli/rules/__init__.py +83 -0
  7. sandbox_cli/cli/scanner/__init__.py +681 -0
  8. sandbox_cli/cli/unpack.py +50 -0
  9. sandbox_cli/console.py +24 -0
  10. sandbox_cli/internal/__init__.py +0 -0
  11. sandbox_cli/internal/config.py +143 -0
  12. sandbox_cli/internal/helpers.py +32 -0
  13. sandbox_cli/models/__init__.py +0 -0
  14. sandbox_cli/models/detections.py +91 -0
  15. sandbox_cli/utils/__init__.py +0 -0
  16. sandbox_cli/utils/compiler/__init__.py +60 -0
  17. sandbox_cli/utils/compiler/abc.py +51 -0
  18. sandbox_cli/utils/compiler/docker.py +136 -0
  19. sandbox_cli/utils/compiler/ssh.py +203 -0
  20. sandbox_cli/utils/downloader/__init__.py +178 -0
  21. sandbox_cli/utils/extractors.py +66 -0
  22. sandbox_cli/utils/merge_dll_hooks.py +84 -0
  23. sandbox_cli/utils/scanner/__init__.py +310 -0
  24. sandbox_cli/utils/scanner/advanced.py +360 -0
  25. sandbox_cli/utils/scanner/rescan.py +258 -0
  26. sandbox_cli/utils/unpack/__init__.py +96 -0
  27. sandbox_cli/utils/unpack/plugins/__init__.py +0 -0
  28. sandbox_cli/utils/unpack/plugins/abc.py +18 -0
  29. sandbox_cli/utils/unpack/plugins/correlation.py +52 -0
  30. sandbox_cli/utils/unpack/plugins/sort_by_plugins.py +30 -0
  31. sandbox_cli-0.2.26.dist-info/METADATA +139 -0
  32. sandbox_cli-0.2.26.dist-info/RECORD +36 -0
  33. sandbox_cli-0.2.26.dist-info/WHEEL +4 -0
  34. sandbox_cli-0.2.26.dist-info/entry_points.txt +2 -0
  35. sandbox_cli-0.2.26.dist-info/licenses/LICENSE +21 -0
  36. sandbox_cli-0.2.26.dist-info/licenses/NOTICE +322 -0
@@ -0,0 +1,681 @@
1
+ import sys
2
+ from collections.abc import Sequence
3
+ from pathlib import Path
4
+ from typing import Annotated, Any
5
+
6
+ from cyclopts import App, Parameter, Token, validators
7
+ from ptsandbox.models import VNCMode
8
+
9
+ from sandbox_cli.console import console
10
+ from sandbox_cli.internal.config import VMImage, settings
11
+ from sandbox_cli.internal.helpers import validate_key
12
+ from sandbox_cli.utils.scanner import scan_internal
13
+ from sandbox_cli.utils.scanner.advanced import scan_internal_advanced
14
+ from sandbox_cli.utils.scanner.rescan import rescan_internal
15
+
16
+ DELIMETER = "\n"
17
+
18
+ scanner = App(
19
+ name="scanner",
20
+ help="Scan with the sandbox.",
21
+ help_format="markdown",
22
+ )
23
+
24
+
25
+ def image_converter(_: Any, tokens: Sequence[Token]) -> set[VMImage | str]:
26
+ images: set[VMImage | str] = set()
27
+ for token in tokens:
28
+ try:
29
+ images.add(VMImage(token.value))
30
+ except ValueError:
31
+ # maybe it is custom image?
32
+ images.add(token.value)
33
+
34
+ return images
35
+
36
+
37
+ @scanner.command(name="re-scan")
38
+ async def re_scan(
39
+ traces: Annotated[
40
+ list[Path],
41
+ Parameter(
42
+ help="Path to folder with **drakvuf-trace.log.zst and tcpdump.pcap** or **sandbox_logs.zip**",
43
+ ),
44
+ ],
45
+ /,
46
+ *,
47
+ rules_dir: Annotated[
48
+ Path | None,
49
+ Parameter(
50
+ name=["--rules", "-r"],
51
+ help="The path to the folder with the rules or the default rules from the sandbox",
52
+ ),
53
+ ] = None,
54
+ out_dir: Annotated[
55
+ Path,
56
+ Parameter(
57
+ name=["--out", "-o"],
58
+ help="The path where to save the results",
59
+ ),
60
+ ] = Path("./sandbox"),
61
+ key: Annotated[
62
+ str,
63
+ Parameter(
64
+ name=["--key", "-k"],
65
+ help=f"The key to access the sandbox **{'**,**'.join(x.name for x in settings.sandbox_keys)}**",
66
+ validator=validate_key,
67
+ group="Sandbox Options",
68
+ ),
69
+ ] = settings.sandbox_keys[0].name,
70
+ is_local: Annotated[
71
+ bool,
72
+ Parameter(
73
+ name=["--local", "-l"],
74
+ negative="",
75
+ help="The rules will be compiled locally using Docker (unix only)",
76
+ ),
77
+ ] = False,
78
+ unpack: Annotated[
79
+ bool,
80
+ Parameter(
81
+ name=["--unpack", "-U"],
82
+ help="Unpack downloaded files",
83
+ negative="",
84
+ ),
85
+ ] = False,
86
+ debug: Annotated[
87
+ bool,
88
+ Parameter(
89
+ name=["--debug", "-d"],
90
+ help="Download debug artifacts",
91
+ negative="",
92
+ group="Download options",
93
+ ),
94
+ ] = False,
95
+ ) -> None:
96
+ """
97
+ Send traces to re-scan.
98
+ """
99
+
100
+ out_dir.mkdir(exist_ok=True, parents=True)
101
+ out_dir = out_dir.expanduser().resolve()
102
+
103
+ # sanity check for traces
104
+ is_ok = True
105
+ for trace in traces:
106
+ trace = trace.expanduser().resolve()
107
+ if not trace.exists():
108
+ console.log(f"{str(trace)} doesn't exists", style="bold red")
109
+ is_ok = False
110
+
111
+ if not is_ok:
112
+ sys.exit(1)
113
+
114
+ await rescan_internal(
115
+ traces=traces,
116
+ rules_dir=rules_dir,
117
+ out_dir=out_dir,
118
+ key_name=key,
119
+ is_local=is_local,
120
+ unpack=unpack,
121
+ debug=debug,
122
+ )
123
+
124
+
125
+ @scanner.command(name="scan")
126
+ async def scan(
127
+ files: Annotated[
128
+ list[Path],
129
+ Parameter(
130
+ help="Path to the files or folders to scan",
131
+ ),
132
+ ],
133
+ /,
134
+ *,
135
+ rules_dir: Annotated[
136
+ Path | None,
137
+ Parameter(
138
+ name=["--rules", "-r"],
139
+ help="The path to the folder with the rules or the default rules from the sandbox",
140
+ ),
141
+ ] = None,
142
+ out_dir: Annotated[
143
+ Path,
144
+ Parameter(
145
+ name=["--out", "-o"],
146
+ help="The path where to save the results",
147
+ ),
148
+ ] = Path("./sandbox"),
149
+ images: Annotated[
150
+ set[VMImage] | None,
151
+ Parameter(
152
+ name=["--image", "-i"],
153
+ help=f"The name of the image to scan (*don't mix different platforms*) {DELIMETER}{DELIMETER.join(f'* {x}' for x in VMImage._value2member_map_.keys())}{DELIMETER * 2}",
154
+ negative="",
155
+ group="Sandbox Options",
156
+ show_choices=False,
157
+ converter=image_converter,
158
+ ),
159
+ ] = None,
160
+ key: Annotated[
161
+ str,
162
+ Parameter(
163
+ name=["--key", "-k"],
164
+ help=f"The key to access the sandbox **{'**,**'.join(x.name for x in settings.sandbox_keys)}**",
165
+ validator=validate_key,
166
+ group="Sandbox Options",
167
+ ),
168
+ ] = settings.sandbox_keys[0].name,
169
+ is_local: Annotated[
170
+ bool,
171
+ Parameter(
172
+ name=["--local", "-l"],
173
+ negative="",
174
+ help="The rules will be compiled locally using Docker (unix only)",
175
+ ),
176
+ ] = False,
177
+ unpack: Annotated[
178
+ bool,
179
+ Parameter(
180
+ name=["--unpack", "-U"],
181
+ help="Unpack downloaded files",
182
+ negative="",
183
+ ),
184
+ ] = False,
185
+ upload_timeout: Annotated[
186
+ int,
187
+ Parameter(
188
+ name=["--upload-timeout", "-T"],
189
+ help="Upload timeout in seconds (increase if upload big files)",
190
+ validator=validators.Number(gt=0),
191
+ ),
192
+ ] = 300,
193
+ fake_name: Annotated[
194
+ str | None,
195
+ Parameter(
196
+ name=["--name", "-n"],
197
+ help="Fake name for the sandbox (if specified more than one files will be applied to all files)",
198
+ group="Sandbox Options",
199
+ ),
200
+ ] = None,
201
+ analysis_duration: Annotated[
202
+ int,
203
+ Parameter(
204
+ name=["--timeout", "-t"],
205
+ help="Analysis duration in seconds",
206
+ validator=validators.Number(gt=0, lt=3600),
207
+ group="Sandbox Options",
208
+ ),
209
+ ] = settings.default_duration,
210
+ syscall_hooks: Annotated[
211
+ Path | None,
212
+ Parameter(
213
+ name=["--syscall-hooks", "-s"],
214
+ help="Path to files with syscall hooks (file with syscall names splitted by newline)",
215
+ group="Sandbox Options",
216
+ ),
217
+ ] = None,
218
+ dll_hooks_dir: Annotated[
219
+ Path | None,
220
+ Parameter(
221
+ name=["--dll-hooks-dir", "-dll"],
222
+ help="Path to directory with dll hooks",
223
+ group="Sandbox Options",
224
+ ),
225
+ ] = None,
226
+ custom_command: Annotated[
227
+ str | None,
228
+ Parameter(
229
+ name="--cmd",
230
+ help="Command line for file execution `rundll32.exe {file},#1`",
231
+ group="Sandbox Options",
232
+ ),
233
+ ] = None,
234
+ all: Annotated[
235
+ bool,
236
+ Parameter(
237
+ name=["--all", "-a"],
238
+ help="Download all artifacts",
239
+ negative="",
240
+ group="Download options",
241
+ ),
242
+ ] = False,
243
+ debug: Annotated[
244
+ bool,
245
+ Parameter(
246
+ name=["--debug", "-d"],
247
+ help="Download debug artifacts",
248
+ negative="",
249
+ group="Download options",
250
+ ),
251
+ ] = False,
252
+ artifacts: Annotated[
253
+ bool,
254
+ Parameter(
255
+ name=["--artifacts", "-A"],
256
+ help="Download artifacts",
257
+ negative="",
258
+ group="Download options",
259
+ ),
260
+ ] = False,
261
+ download_files: Annotated[
262
+ bool,
263
+ Parameter(
264
+ name=["--files", "-f"],
265
+ help="Download files",
266
+ negative="",
267
+ group="Download options",
268
+ ),
269
+ ] = False,
270
+ crashdumps: Annotated[
271
+ bool,
272
+ Parameter(
273
+ name=["--crashdumps", "-c"],
274
+ help="Download crashdumps (maybe be more 1GB)",
275
+ negative="",
276
+ group="Download options",
277
+ ),
278
+ ] = False,
279
+ procdumps: Annotated[
280
+ bool,
281
+ Parameter(
282
+ name=["--procdumps", "-p"],
283
+ help="Download procdumps",
284
+ negative="",
285
+ group="Download options",
286
+ ),
287
+ ] = False,
288
+ decompress: Annotated[
289
+ bool,
290
+ Parameter(
291
+ name=["--decompress", "-D"],
292
+ help="Decompress downloaded files",
293
+ negative="",
294
+ ),
295
+ ] = False,
296
+ ) -> None:
297
+ """
298
+ Send files to scan with the sandbox.
299
+
300
+ If you want to scan a folder, you can specify the path to the folder
301
+
302
+ Amount of simultaneous scans is limited by the sandbox settings (usually 8)
303
+ """
304
+
305
+ console.warning('Deprecated option, not particularly supported. Use "scan-new" instead')
306
+
307
+ # some path preparations
308
+ out_dir.mkdir(exist_ok=True, parents=True)
309
+ out_dir = out_dir.expanduser().resolve()
310
+
311
+ if images is None:
312
+ images = {settings.default_image}
313
+
314
+ # parse files options
315
+ is_ok = True
316
+ files_for_analysis: list[Path] = []
317
+ for file in files:
318
+ file = file.expanduser().resolve()
319
+
320
+ if not file.exists():
321
+ console.error(f"{str(file)} doesn't exists")
322
+ is_ok = False
323
+
324
+ if file.is_dir():
325
+ files_for_analysis.extend(file.glob("**/*"))
326
+ continue
327
+
328
+ files_for_analysis.append(file)
329
+
330
+ if not is_ok:
331
+ sys.exit(1)
332
+
333
+ if len(files_for_analysis) == 0:
334
+ console.error("Nothing to scan")
335
+ sys.exit(1)
336
+
337
+ await scan_internal(
338
+ files=files_for_analysis,
339
+ scan_images=images,
340
+ rules_dir=rules_dir,
341
+ out_dir=out_dir,
342
+ key_name=key,
343
+ is_local=is_local,
344
+ analysis_duration=analysis_duration,
345
+ syscall_hooks=syscall_hooks,
346
+ custom_command=custom_command,
347
+ dll_hooks_dir=dll_hooks_dir,
348
+ fake_name=fake_name,
349
+ unpack=unpack,
350
+ upload_timeout=upload_timeout,
351
+ all=all,
352
+ debug=debug,
353
+ artifacts=artifacts,
354
+ download_files=download_files,
355
+ crashdumps=crashdumps,
356
+ procdumps=procdumps,
357
+ decompress=decompress,
358
+ )
359
+
360
+
361
+ @scanner.command(name="scan-new")
362
+ async def scan_new(
363
+ files: Annotated[
364
+ list[Path],
365
+ Parameter(
366
+ help="Path to the files or folders to scan",
367
+ ),
368
+ ],
369
+ /,
370
+ *,
371
+ rules_dir: Annotated[
372
+ Path | None,
373
+ Parameter(
374
+ name=["--rules", "-r"],
375
+ help="The path to the folder with the rules or the default rules from the sandbox",
376
+ ),
377
+ ] = None,
378
+ out_dir: Annotated[
379
+ Path,
380
+ Parameter(
381
+ name=["--out", "-o"],
382
+ help="The path where to save the results",
383
+ ),
384
+ ] = Path("./sandbox"),
385
+ images: Annotated[
386
+ set[VMImage | str] | None,
387
+ Parameter(
388
+ name=["--image", "-i"],
389
+ help=f"The name of the image to scan (*don't mix different platforms*) {DELIMETER}{DELIMETER.join(f'* {x}' for x in VMImage._value2member_map_.keys())}{DELIMETER * 2}",
390
+ negative="",
391
+ group="Sandbox Options",
392
+ show_choices=False,
393
+ converter=image_converter,
394
+ ),
395
+ ] = None,
396
+ key: Annotated[
397
+ str,
398
+ Parameter(
399
+ name=["--key", "-k"],
400
+ help=f"The key to access the sandbox **{'**,**'.join(x.name for x in settings.sandbox_keys)}**",
401
+ validator=validate_key,
402
+ group="Sandbox Options",
403
+ ),
404
+ ] = settings.sandbox_keys[0].name,
405
+ is_local: Annotated[
406
+ bool,
407
+ Parameter(
408
+ name=["--local", "-l"],
409
+ negative="",
410
+ help="The rules will be compiled locally using Docker (unix only)",
411
+ ),
412
+ ] = False,
413
+ upload_timeout: Annotated[
414
+ int,
415
+ Parameter(
416
+ name=["--upload-timeout", "-T"],
417
+ help="Upload timeout in seconds (increase if upload big files)",
418
+ validator=validators.Number(gt=0),
419
+ ),
420
+ ] = 300,
421
+ fake_name: Annotated[
422
+ str | None,
423
+ Parameter(
424
+ name=["--name", "-n"],
425
+ help="Fake name for the sandbox (if specified more than one file will be applied to all files)",
426
+ group="Sandbox Options",
427
+ ),
428
+ ] = None,
429
+ analysis_duration: Annotated[
430
+ int,
431
+ Parameter(
432
+ name=["--timeout", "-t"],
433
+ help="Analysis duration in seconds",
434
+ validator=validators.Number(gt=0, lt=3600),
435
+ group="Sandbox Options",
436
+ ),
437
+ ] = settings.default_duration,
438
+ syscall_hooks: Annotated[
439
+ Path | None,
440
+ Parameter(
441
+ name=["--syscall-hooks", "-s"],
442
+ help="Path to files with syscall hooks (file with syscall names splitted by newline)",
443
+ group="Sandbox Options",
444
+ ),
445
+ ] = None,
446
+ dll_hooks_dir: Annotated[
447
+ Path | None,
448
+ Parameter(
449
+ name=["--dll-hooks-dir", "-dll"],
450
+ help="Path to directory with dll hooks",
451
+ group="Sandbox Options",
452
+ ),
453
+ ] = None,
454
+ custom_command: Annotated[
455
+ str | None,
456
+ Parameter(
457
+ name="--cmd",
458
+ help="Command line for file execution _rundll32.exe {file},#1_",
459
+ group="Sandbox Options",
460
+ ),
461
+ ] = None,
462
+ unpack: Annotated[
463
+ bool,
464
+ Parameter(
465
+ name=["--unpack", "-U"],
466
+ help="Unpack downloaded files",
467
+ negative="",
468
+ ),
469
+ ] = False,
470
+ priority: Annotated[
471
+ int,
472
+ Parameter(
473
+ name=["--priority", "-pr"],
474
+ help="Priority of the scan (1-4)",
475
+ validator=validators.Number(gte=1, lte=4),
476
+ group="Sandbox Options",
477
+ ),
478
+ ] = 3,
479
+ procdump_new_processes_on_finish: Annotated[
480
+ bool,
481
+ Parameter(
482
+ name=["--procdump-new-processes-on-finish", "-P"],
483
+ help="Collect dumps for all created and not finished processes",
484
+ group="Sandbox Options",
485
+ negative="",
486
+ ),
487
+ ] = False,
488
+ bootkitmon: Annotated[
489
+ bool,
490
+ Parameter(
491
+ name=["--bootkitmon", "-b"],
492
+ help="Enable bootkitmon",
493
+ group="Sandbox Options",
494
+ negative="",
495
+ ),
496
+ ] = False,
497
+ bootkitmon_duration: Annotated[
498
+ int,
499
+ Parameter(
500
+ name=["--bootkitmon-duration", "-bd"],
501
+ help="Bootkitmon duration in seconds",
502
+ validator=validators.Number(gt=0),
503
+ group="Sandbox Options",
504
+ negative="",
505
+ ),
506
+ ] = 60,
507
+ mitm_disabled: Annotated[
508
+ bool,
509
+ Parameter(
510
+ name=["--mitm-disabled", "-M"],
511
+ help="Disable MITM",
512
+ group="Sandbox Options",
513
+ negative="",
514
+ ),
515
+ ] = False,
516
+ disable_clicker: Annotated[
517
+ bool,
518
+ Parameter(
519
+ name=["--disable-clicker", "-dc"],
520
+ help="Disable clicker",
521
+ group="Sandbox Options",
522
+ negative="",
523
+ ),
524
+ ] = False,
525
+ skip_sample_run: Annotated[
526
+ bool,
527
+ Parameter(
528
+ name=["--skip-sample-run", "-S"],
529
+ help="Skip sample run",
530
+ group="Sandbox Options",
531
+ negative="",
532
+ ),
533
+ ] = False,
534
+ vnc_mode: Annotated[
535
+ VNCMode,
536
+ Parameter(
537
+ name=["--vnc-mode", "-V"],
538
+ help="VNC mode",
539
+ group="Sandbox Options",
540
+ ),
541
+ ] = VNCMode.DISABLED,
542
+ extra_files: Annotated[
543
+ list[Path] | None,
544
+ Parameter(
545
+ name=["--extra-files", "-e"],
546
+ help="Extra files to upload",
547
+ group="Sandbox Options",
548
+ negative="",
549
+ ),
550
+ ] = None,
551
+ all: Annotated[
552
+ bool,
553
+ Parameter(
554
+ name=["--all", "-a"],
555
+ help="Download all artifacts",
556
+ negative="",
557
+ group="Download options",
558
+ ),
559
+ ] = False,
560
+ debug: Annotated[
561
+ bool,
562
+ Parameter(
563
+ name=["--debug", "-d"],
564
+ help="Download debug artifacts",
565
+ negative="",
566
+ group="Download options",
567
+ ),
568
+ ] = False,
569
+ artifacts: Annotated[
570
+ bool,
571
+ Parameter(
572
+ name=["--artifacts", "-A"],
573
+ help="Download artifacts",
574
+ negative="",
575
+ group="Download options",
576
+ ),
577
+ ] = False,
578
+ download_files: Annotated[
579
+ bool,
580
+ Parameter(
581
+ name=["--files", "-f"],
582
+ help="Download files",
583
+ negative="",
584
+ group="Download options",
585
+ ),
586
+ ] = False,
587
+ crashdumps: Annotated[
588
+ bool,
589
+ Parameter(
590
+ name=["--crashdumps", "-c"],
591
+ help="Download crashdumps (maybe be more 1GB)",
592
+ negative="",
593
+ group="Download options",
594
+ ),
595
+ ] = False,
596
+ procdumps: Annotated[
597
+ bool,
598
+ Parameter(
599
+ name=["--procdumps", "-p"],
600
+ help="Download procdumps",
601
+ negative="",
602
+ group="Download options",
603
+ ),
604
+ ] = False,
605
+ decompress: Annotated[
606
+ bool,
607
+ Parameter(
608
+ name=["--decompress", "-D"],
609
+ help="Decompress downloaded files",
610
+ negative="",
611
+ ),
612
+ ] = False,
613
+ ) -> None:
614
+ """
615
+ Send files to scan with the sandbox (advanced scan).
616
+ """
617
+
618
+ # some path preparations
619
+ out_dir.mkdir(exist_ok=True, parents=True)
620
+ out_dir = out_dir.expanduser().resolve()
621
+
622
+ if images is None:
623
+ images = {settings.default_image}
624
+
625
+ if extra_files is None:
626
+ extra_files = []
627
+
628
+ # parse files options
629
+ is_ok = True
630
+ files_for_analysis: list[Path] = []
631
+ for file in files:
632
+ file = file.expanduser().resolve()
633
+
634
+ if not file.exists():
635
+ console.error(f"{str(file)} doesn't exists")
636
+ is_ok = False
637
+
638
+ if file.is_dir():
639
+ files_for_analysis.extend(file.glob("**/*"))
640
+ continue
641
+
642
+ files_for_analysis.append(file)
643
+
644
+ if not is_ok:
645
+ sys.exit(1)
646
+
647
+ if len(files_for_analysis) == 0:
648
+ console.error("Nothing to scan")
649
+ sys.exit(1)
650
+
651
+ await scan_internal_advanced(
652
+ files=files_for_analysis,
653
+ scan_images=images,
654
+ rules_dir=rules_dir,
655
+ out_dir=out_dir,
656
+ key_name=key,
657
+ is_local=is_local,
658
+ analysis_duration=analysis_duration,
659
+ syscall_hooks=syscall_hooks,
660
+ custom_command=custom_command,
661
+ dll_hooks_dir=dll_hooks_dir,
662
+ fake_name=fake_name,
663
+ unpack=unpack,
664
+ priority=priority,
665
+ procdump_new_processes_on_finish=procdump_new_processes_on_finish,
666
+ bootkitmon=bootkitmon,
667
+ bootkitmon_duration=bootkitmon_duration,
668
+ mitm_disabled=mitm_disabled,
669
+ disable_clicker=disable_clicker,
670
+ skip_sample_run=skip_sample_run,
671
+ vnc_mode=vnc_mode,
672
+ extra_files=extra_files,
673
+ upload_timeout=upload_timeout,
674
+ all=all,
675
+ debug=debug,
676
+ artifacts=artifacts,
677
+ download_files=download_files,
678
+ crashdumps=crashdumps,
679
+ procdumps=procdumps,
680
+ decompress=decompress,
681
+ )