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,258 @@
1
+ import asyncio
2
+ import sys
3
+ from collections.abc import Coroutine
4
+ from io import BytesIO
5
+ from pathlib import Path
6
+ from typing import Any
7
+ from zipfile import BadZipFile, ZipFile
8
+
9
+ import aiohttp
10
+ import aiohttp.client_exceptions
11
+ from ptsandbox import Sandbox
12
+ from ptsandbox.models import (
13
+ SandboxBaseScanTaskRequest,
14
+ SandboxKey,
15
+ SandboxUploadException,
16
+ )
17
+ from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, TimeElapsedColumn
18
+
19
+ from sandbox_cli.console import console
20
+ from sandbox_cli.internal.config import settings
21
+ from sandbox_cli.internal.helpers import get_key_by_name
22
+ from sandbox_cli.utils.compiler import compile_rules_internal
23
+ from sandbox_cli.utils.downloader import download
24
+ from sandbox_cli.utils.scanner import format_link
25
+ from sandbox_cli.utils.unpack import Unpack
26
+
27
+
28
+ async def _get_compiled_rules(progress: Progress, rules_dir: Path | None, is_local: bool) -> bytes | None:
29
+ if not rules_dir:
30
+ progress.disable = False
31
+ progress.start()
32
+ return None
33
+
34
+ inner_progress = Progress(
35
+ TextColumn(console.INFO),
36
+ SpinnerColumn(),
37
+ TextColumn(text_format="{task.description}"),
38
+ "•",
39
+ TimeElapsedColumn(),
40
+ console=console,
41
+ )
42
+ task_id: TaskID
43
+
44
+ text = (
45
+ "Compiling rules locally"
46
+ if is_local
47
+ else f"Compiling rules on the remote • [medium_purple]{settings.sandbox[0].host}[/]"
48
+ )
49
+ task_id = inner_progress.add_task(text)
50
+
51
+ with inner_progress:
52
+ result = await compile_rules_internal(rules_dir=rules_dir, is_local=is_local)
53
+ inner_progress.stop_task(task_id=task_id)
54
+
55
+ inner_progress.stop()
56
+
57
+ progress.disable = False
58
+ progress.start()
59
+
60
+ return result
61
+
62
+
63
+ async def _prepare_rescan_options(
64
+ progress: Progress,
65
+ rules_dir: Path | None,
66
+ sandbox_key: SandboxKey,
67
+ is_local: bool,
68
+ ) -> tuple[Sandbox, SandboxBaseScanTaskRequest.Options]:
69
+ sandbox = Sandbox(sandbox_key)
70
+
71
+ sandbox_options = SandboxBaseScanTaskRequest.Options(analysis_depth=2, passwords_for_unpack=settings.passwords)
72
+
73
+ # process custom options
74
+ compiled_rules = await _get_compiled_rules(rules_dir=rules_dir, is_local=is_local, progress=progress)
75
+
76
+ if compiled_rules:
77
+ try:
78
+ rules_uri = (await sandbox.api.upload_file(compiled_rules)).data.file_uri
79
+ sandbox_options.sandbox.debug_options["rules_url"] = rules_uri
80
+ except aiohttp.client_exceptions.ClientResponseError:
81
+ console.error(f"Can't upload compiled rules {rules_dir} to sandbox")
82
+
83
+ return (sandbox, sandbox_options)
84
+
85
+
86
+ async def rescan_internal(
87
+ *, # no not keyword args
88
+ traces: list[Path],
89
+ rules_dir: Path | None,
90
+ out_dir: Path,
91
+ key_name: str,
92
+ is_local: bool,
93
+ unpack: bool,
94
+ debug: bool,
95
+ ) -> None:
96
+ key = get_key_by_name(key_name)
97
+ sandbox_sem = asyncio.Semaphore(value=key.max_workers)
98
+
99
+ progress = Progress(
100
+ SpinnerColumn(),
101
+ TextColumn("{task.fields[idx]}"),
102
+ "•",
103
+ TextColumn("{task.description}"),
104
+ "•",
105
+ TextColumn("{task.fields[url]}"),
106
+ "•",
107
+ TimeElapsedColumn(),
108
+ console=console,
109
+ disable=True,
110
+ transient=True,
111
+ )
112
+
113
+ async def process_trace(
114
+ drakvuf_trace: Path | BytesIO,
115
+ tcpdump_pcap: Path | BytesIO,
116
+ trace: Path,
117
+ out_dir: Path,
118
+ idx: str,
119
+ ) -> None:
120
+ idx = f"[turquoise2 bold]{idx}[/]"
121
+
122
+ async with sandbox_sem:
123
+ task_id = progress.add_task(description="Creating task", idx=idx, url="...")
124
+
125
+ wait_time = (
126
+ round(sandbox_options.sandbox.analysis_duration * 1.5)
127
+ if sandbox_options.sandbox.analysis_duration > 70
128
+ else 70
129
+ )
130
+
131
+ try:
132
+ rescan_result = await sandbox.create_rescan(
133
+ drakvuf_trace,
134
+ tcpdump_pcap,
135
+ options=sandbox_options,
136
+ rules=None,
137
+ read_timeout=wait_time,
138
+ )
139
+ except SandboxUploadException as e:
140
+ console.error(f"[yellow]{trace}[/] • an error occurred when uploading a file to the server • {e}")
141
+ progress.remove_task(task_id)
142
+ return
143
+ except aiohttp.client_exceptions.ClientResponseError as e:
144
+ console.error(f"[yellow]{trace}[/] • {e}")
145
+ progress.remove_task(task_id)
146
+ return
147
+
148
+ formatted_link = f"[medium_purple]{format_link(rescan_result, key=key)}[/]"
149
+ final_output = f"[yellow]{trace.name}[/] • {formatted_link}"
150
+
151
+ progress.update(
152
+ task_id=task_id,
153
+ description=f"Waiting for full report for [yellow]{trace.name}[/]",
154
+ url=formatted_link,
155
+ )
156
+ if not (awaited_report := await sandbox.wait_for_report(rescan_result, wait_time)):
157
+ console.error(f"Rescan failed for [yellow]{trace.name}[/] • {formatted_link} • {rescan_result}")
158
+ progress.remove_task(task_id)
159
+ return
160
+
161
+ rescan_result = awaited_report
162
+
163
+ # write report.json
164
+ (out_dir / settings.report_name).write_text(rescan_result.model_dump_json(indent=4), encoding="utf-8")
165
+
166
+ # get full report?
167
+ if not (long_report := rescan_result.get_long_report()):
168
+ console.error(f"{final_output} • full report not available")
169
+ progress.remove_task(task_id)
170
+ return
171
+
172
+ progress.update(task_id=task_id, description="Downloading results...")
173
+
174
+ await download(long_report, sandbox, out_dir, logs=True, debug=debug)
175
+
176
+ console.done(final_output)
177
+
178
+ progress.remove_task(task_id)
179
+
180
+ if unpack:
181
+ Unpack(out_dir).run()
182
+
183
+ async def wrapper(trace: Path, out_dir: Path, idx: str) -> None:
184
+ """
185
+ Internal function for prepare raw traces for re-scan
186
+
187
+ :param trace
188
+ file - zip file with drakvuf-trace.log.gz/drakvuf-trace.log.zst and tcpdump.pcap inside
189
+
190
+ dir - directory with drakvuf-trace.log.gz/drakvuf-trace.log.zst and tcpdump.pcap files
191
+
192
+ :param out_dir - save dir for current trace
193
+ :param idx - just nice index for output
194
+ """
195
+
196
+ drakvuf_trace: Path | BytesIO
197
+ tcpdump_pcap: Path | BytesIO
198
+ if trace.is_dir():
199
+ drakvuf_trace = trace / "drakvuf-trace.log.gz"
200
+ if not drakvuf_trace.exists():
201
+ # handle case with modern zst format
202
+ drakvuf_trace = trace / "drakvuf-trace.log.zst"
203
+ if not drakvuf_trace.exists():
204
+ console.error(
205
+ f"drakvuf-trace.log.gz or drakvuf-trace.log.zst doesn't exists in {trace.expanduser().resolve()}"
206
+ )
207
+ sys.exit(1)
208
+
209
+ tcpdump_pcap = trace / "tcpdump.pcap"
210
+ if not tcpdump_pcap.exists():
211
+ console.error(f"tcpdump.pcap don't exists in {trace}")
212
+ sys.exit(1)
213
+ else:
214
+ try:
215
+ with ZipFile(trace) as zip:
216
+ log_file = ""
217
+ for file in zip.filelist:
218
+ if file.filename in {"drakvuf-trace.log.gz", "drakvuf-trace.log.zst"}:
219
+ log_file = file.filename
220
+ break
221
+
222
+ raw_trace = zip.read(log_file)
223
+ if raw_trace == b"":
224
+ console.error(f"Empty {log_file} in {trace}")
225
+ sys.exit(1)
226
+
227
+ drakvuf_trace = BytesIO(raw_trace)
228
+ tcpdump_pcap = BytesIO(zip.read("tcpdump.pcap"))
229
+ except BadZipFile:
230
+ console.error(f"{trace} not a zip file")
231
+ sys.exit(1)
232
+
233
+ await process_trace(drakvuf_trace, tcpdump_pcap, trace, out_dir, idx)
234
+
235
+ console.info(f"Using key: name={key.name} max_workers={key.max_workers}")
236
+
237
+ tasks: list[Coroutine[Any, Any, None]] = []
238
+ with progress:
239
+ sandbox, sandbox_options = await _prepare_rescan_options(progress, rules_dir, key, is_local)
240
+
241
+ if len(traces) == 1:
242
+ local_out_dir = out_dir / "rescan"
243
+ local_out_dir.mkdir(parents=True, exist_ok=True)
244
+ tasks.append(wrapper(traces[0], local_out_dir, "1/1"))
245
+ else:
246
+ for i, trace in enumerate(traces):
247
+ local_out_dir = out_dir / f"rescan_{i + 1}"
248
+
249
+ # nice names for zip files
250
+ if trace.suffix == ".zip":
251
+ local_out_dir = out_dir / trace.stem
252
+
253
+ local_out_dir.mkdir(parents=True, exist_ok=True)
254
+ idx = f"{i + 1}/{len(traces)}"
255
+ tasks.append(wrapper(trace, local_out_dir, idx))
256
+
257
+ await asyncio.gather(*tasks)
258
+ await sandbox.api.session.close()
@@ -0,0 +1,96 @@
1
+ import shutil
2
+ from gzip import GzipFile
3
+ from pathlib import Path
4
+ from zipfile import ZipFile
5
+
6
+ import zstandard
7
+
8
+ from sandbox_cli.console import console
9
+ from sandbox_cli.utils.unpack.plugins.abc import BasePlugin
10
+ from sandbox_cli.utils.unpack.plugins.correlation import CorrelatedRules
11
+ from sandbox_cli.utils.unpack.plugins.sort_by_plugins import SortByPlugins
12
+
13
+
14
+ class Unpack:
15
+ def __init__(self, trace: Path) -> None:
16
+ if not trace.exists():
17
+ console.error(f"{trace} not exist")
18
+ return
19
+
20
+ # unpack zip file
21
+ if trace.is_file() and trace.suffix.endswith("zip"):
22
+ self.trace = Path(trace.with_suffix(""))
23
+ self.trace.mkdir(exist_ok=True)
24
+
25
+ with ZipFile(trace, mode="r") as zip:
26
+ zip.extractall(path=self.trace)
27
+ elif trace.is_dir():
28
+ self.trace = trace
29
+ else:
30
+ console.error(f"Unsupported file: {trace}")
31
+ return
32
+
33
+ self.plugins: list[BasePlugin] = [CorrelatedRules(self.trace), SortByPlugins(self.trace)]
34
+ self.logs = {
35
+ "drakvuf-trace": Path(""), # dynamic detect what extension is using
36
+ "correlated": Path(self.trace / "events-correlated.log.gz"),
37
+ "normalized": Path(self.trace / "events-normalized.log.gz"),
38
+ "network": Path(self.trace / "tcpdump.pcap"),
39
+ }
40
+ self.raw = Path(self.trace / "raw")
41
+
42
+ def _extract_logs(self) -> None:
43
+ def _extract(file: Path) -> None:
44
+ if file.exists() and file.suffix.endswith("zst"):
45
+ dctx = zstandard.ZstdDecompressor()
46
+ with open(file, mode="rb") as zst, open(file.with_suffix(""), "wb") as out:
47
+ dctx.copy_stream(zst, out)
48
+
49
+ if file.exists() and file.suffix.endswith("gz"):
50
+ with GzipFile(file, mode="rb") as gzip, open(file.with_suffix(""), "wb") as out:
51
+ out.write(gzip.read())
52
+
53
+ for log in self.logs.values():
54
+ _extract(log)
55
+
56
+ def _create_dirs(self) -> None:
57
+ def _create(dir: Path) -> None:
58
+ if dir.exists() and dir.is_dir():
59
+ shutil.rmtree(dir)
60
+ dir.mkdir(exist_ok=True)
61
+
62
+ for dir in self.logs:
63
+ _create(Path(self.trace / dir))
64
+
65
+ def _move_files(self) -> None:
66
+ self.raw.mkdir(exist_ok=True)
67
+ for log in self.logs.values():
68
+ if log.exists() and log.is_file():
69
+ shutil.copy(log, self.raw)
70
+
71
+ for dir, file in self.logs.items():
72
+ if not file.exists() or file.is_dir():
73
+ continue
74
+
75
+ if file.suffix.endswith("gz") or file.suffix.endswith("zst"):
76
+ shutil.move(file.with_suffix(""), self.trace / dir)
77
+ else:
78
+ shutil.move(file, self.trace / dir)
79
+
80
+ def run(self) -> None:
81
+ if Path(self.trace / "drakvuf-trace.log.gz").exists():
82
+ self.logs["drakvuf-trace"] = Path(self.trace / "drakvuf-trace.log.gz")
83
+ if Path(self.trace / "drakvuf-trace.log.zst").exists():
84
+ self.logs["drakvuf-trace"] = Path(self.trace / "drakvuf-trace.log.zst")
85
+
86
+ self._extract_logs()
87
+ self._create_dirs()
88
+ self._move_files()
89
+
90
+ # Run plugins
91
+ for plugin in self.plugins:
92
+ plugin.run()
93
+
94
+ # Remove files
95
+ for log in self.logs.values():
96
+ log.unlink(missing_ok=True)
File without changes
@@ -0,0 +1,18 @@
1
+ from abc import ABC, abstractmethod
2
+ from pathlib import Path
3
+
4
+
5
+ class BasePlugin(ABC):
6
+ """
7
+ Base class for all plugins
8
+
9
+ It is necessary to designate a single entry point
10
+ """
11
+
12
+ def __init__(self, trace: Path) -> None:
13
+ self.trace = trace
14
+
15
+ @abstractmethod
16
+ def run(self) -> None:
17
+ """Invoke plugin"""
18
+ ...
@@ -0,0 +1,52 @@
1
+ from collections import defaultdict
2
+ from pathlib import Path
3
+
4
+ import orjson
5
+
6
+ from sandbox_cli.console import console
7
+ from sandbox_cli.models.detections import DetectionType
8
+ from sandbox_cli.utils.unpack.plugins.abc import BasePlugin
9
+
10
+
11
+ class CorrelatedRules(BasePlugin):
12
+ def run(self) -> None:
13
+ base_path = self.trace / "correlated"
14
+ file = Path(base_path / "events-correlated.log")
15
+ if not file.exists():
16
+ console.warning(f"{file} not exist")
17
+ return
18
+
19
+ with open(file, errors="ignore", encoding="utf-8") as fd:
20
+ raw_trace = fd.readlines()
21
+
22
+ suspicious: defaultdict[str, list[str]] = defaultdict(list)
23
+ silent: defaultdict[str, list[str]] = defaultdict(list)
24
+ malware: list[str] = []
25
+
26
+ for line in raw_trace:
27
+ data: dict[str, str] = orjson.loads(line)
28
+ if data.get("detect.type"):
29
+ match data["detect.type"]:
30
+ case DetectionType.malware:
31
+ malware.append(line)
32
+ case DetectionType.suspicious:
33
+ suspicious[data["detect.name"]].append(line)
34
+ case DetectionType.silent:
35
+ silent[data["detect.name"]].append(line)
36
+ case _:
37
+ pass
38
+
39
+ if len(malware) > 0:
40
+ with open(f"{file}.malware", "w", encoding="utf-8") as fd:
41
+ for line in malware:
42
+ fd.write(line)
43
+
44
+ for key, lines in silent.items():
45
+ with open(f"{file}.silent.{key}", "w", encoding="utf-8") as fd:
46
+ for line in lines:
47
+ fd.write(line)
48
+
49
+ for key, lines in suspicious.items():
50
+ with open(f"{file}.suspicious.{key}", "w", encoding="utf-8") as fd:
51
+ for line in lines:
52
+ fd.write(line)
@@ -0,0 +1,30 @@
1
+ from collections import defaultdict
2
+ from pathlib import Path
3
+
4
+ import orjson
5
+
6
+ from sandbox_cli.console import console
7
+ from sandbox_cli.utils.unpack.plugins.abc import BasePlugin
8
+
9
+
10
+ class SortByPlugins(BasePlugin):
11
+ def run(self) -> None:
12
+ base_path = self.trace / "normalized"
13
+ file = Path(base_path / "events-normalized.log")
14
+ if not file.exists():
15
+ console.warning(f"{file} not exist")
16
+ return
17
+
18
+ with open(file, errors="ignore") as fd:
19
+ raw_trace = fd.readlines()
20
+
21
+ plugins: defaultdict[str, list[str]] = defaultdict(list)
22
+ for line in raw_trace:
23
+ data: dict[str, str] = orjson.loads(line)
24
+ if data.get("plugin"):
25
+ plugins[data["plugin"]].append(line)
26
+
27
+ for plugin, lines in plugins.items():
28
+ with open(f"{file}.{plugin}", "w", encoding="utf-8") as fd:
29
+ for line in lines:
30
+ fd.write(line)
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: sandbox-cli
3
+ Version: 0.2.26
4
+ Summary: Command line tool for interaction with sandboxes
5
+ Project-URL: Homepage, https://github.com/Security-Experts-Community/sandbox-cli
6
+ Project-URL: Documentation, https://security-experts-community.github.io/sandbox-cli
7
+ Project-URL: Repository, https://github.com/Security-Experts-Community/sandbox-cli
8
+ Project-URL: Issues, https://github.com/Security-Experts-Community/sandbox-cli/issues
9
+ Author: Alexey Kolesnikov
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ License-File: NOTICE
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: aiofiles>=24.1.0
15
+ Requires-Dist: asyncssh>=2.19.0
16
+ Requires-Dist: cryptography>=44.0.0
17
+ Requires-Dist: cyclopts>=3.9.0
18
+ Requires-Dist: docker>=7.1.0
19
+ Requires-Dist: ptsandbox
20
+ Requires-Dist: pyzipper>=0.3.6
21
+ Requires-Dist: rich>=13.9.4
22
+ Requires-Dist: zstandard>=0.23.0
23
+ Description-Content-Type: text/markdown
24
+
25
+ ![Image](https://raw.githubusercontent.com/Security-Experts-Community/sandbox-cli/refs/heads/main/docs/assets/logo_with_text.svg)
26
+
27
+ <p align="center">
28
+ <em>Work with PT Sandbox like a pro</em>
29
+ </p>
30
+
31
+ ---
32
+
33
+ **Documentation**: <a href="https://security-experts-community.github.io/sandbox-cli">https://security-experts-community.github.io/sandbox-cli</a>
34
+
35
+ **Source Code**: <a href="https://github.com/Security-Experts-Community/sandbox-cli">https://github.com/Security-Experts-Community/sandbox-cli</a>
36
+
37
+ ---
38
+
39
+ > [!NOTE]
40
+ > `python >= 3.11` is required.
41
+
42
+ ## Installation
43
+
44
+ Using `pipx`:
45
+
46
+ ```sh
47
+ pipx install sandbox-cli
48
+ ```
49
+
50
+ Using `PyPi`:
51
+
52
+ ```sh
53
+ pip install sandbox-cli
54
+ ```
55
+
56
+ NixOS:
57
+
58
+ ```
59
+ Add inputs.sandbox-cli.overlays.default to your nixpkgs overlay
60
+ TBA: ...
61
+ ```
62
+
63
+ ### Config
64
+
65
+ You must create default config file as described in `docs/config-examples/config.toml`:
66
+
67
+ Linux/MacOS:
68
+
69
+ ```sh
70
+ ~/.config/sandbox-cli/config.toml
71
+ or
72
+ $XDG_HOME_CONFIG_HOME/sandbox-cli/config.toml
73
+ ```
74
+
75
+ Windows:
76
+
77
+ ```ps1
78
+ %APPDATA%\sandbox-cli\config.toml
79
+ ```
80
+
81
+ ## Available options
82
+
83
+ - `scanner` - Scan with the sandbox.
84
+ - `images` - Get available images in the sandbox.
85
+ - `download` - Download any artifact from the sandbox.
86
+ - `email` - Upload an email and get its headers.
87
+ - `report` - Generate short report from sandbox scans.
88
+ - `unpack`/`conv` - Convert sandbox logs into an analysis-friendly format.
89
+ - `rules` - Working with raw sandbox rules.
90
+
91
+ <p align="middle">
92
+ <img width="50%" src="https://raw.githubusercontent.com/Security-Experts-Community/sandbox-cli/refs/heads/main/docs/assets/pic_right.svg">
93
+ </p>
94
+
95
+ ## Usage examples
96
+
97
+ ### images
98
+
99
+ Get all availables images:
100
+
101
+ ```bash
102
+ sandbox-cli images
103
+ ```
104
+
105
+ ```bash
106
+ ┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
107
+ ┃ Name ┃ ID ┃ Version ┃ Product version ┃
108
+ ┡━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
109
+ │ altlinux │ altworkstation-10-x64 │ ... │ ... │
110
+ │ astra │ astralinux-smolensk-x64 │ ... │ ... │
111
+ │ redos │ redos-murom-x64 │ ... │ ... │
112
+ │ ubuntu │ ubuntu-jammy-x64 │ ... │ ... │
113
+ │ Windows 10 Pro │ win10-1803-x64 │ ... │ ... │
114
+ │ Windows 10 Enterprise │ win10-22H2-x64 │ ... │ ... │
115
+ │ Windows 10 Pro │ win11-23H2-x64 │ ... │ ... │
116
+ │ Windows 7 Enterprise │ win7-sp1-x64 │ ... │ ... │
117
+ │ Windows 7 Enterprise │ win7-sp1-x64-ics │ ... │ ... │
118
+ └───────────────────────┴─────────────────────────┴────────────┴─────────────────┘
119
+ ```
120
+
121
+ ### scanner
122
+
123
+ Scan the file on all available windows images with timeout 60s and with automatic logs unpacking:
124
+
125
+ ```bash
126
+ sandbox-cli scanner scan-new -i windows -t 60 -U malware.exe
127
+ ```
128
+
129
+ <p align="middle">
130
+ <img width="50%" src="https://raw.githubusercontent.com/Security-Experts-Community/sandbox-cli/refs/heads/main/docs/assets/pic_left.svg">
131
+ </p>
132
+
133
+ ## Development
134
+
135
+ `uv` is used to build the project.
136
+
137
+ ```bash
138
+ uv sync
139
+ ```
@@ -0,0 +1,36 @@
1
+ sandbox_cli/__main__.py,sha256=oTOdIxj9pwlwGF-QBzLjUrfqBcDl5sot0YtPeoPrwaE,657
2
+ sandbox_cli/console.py,sha256=i-jaDv2L8f2gubg2gy_FBOd8Mr12MalyuJ4gNw_ZnWw,724
3
+ sandbox_cli/cli/__init__.py,sha256=jdpd01ahycl3JKr4N-8R_CLSZvvoFFGDQEqP2op1K4A,1283
4
+ sandbox_cli/cli/downloader.py,sha256=cygTXJdVl8pkHFRULwwpNBpaiwRrjFQn_GHSilWj9J4,8633
5
+ sandbox_cli/cli/images.py,sha256=Tv-s6-7sDugeNOK0L1lmfeq8XqCF7avbBXGq_yuG1Q8,1263
6
+ sandbox_cli/cli/reporter.py,sha256=NLus8Zi8Gl7AUXLk69_lMnd2uJcHQG5golAY7J-uWio,4588
7
+ sandbox_cli/cli/unpack.py,sha256=zrgyh-ausbBMFc6XlY8qShF1zTrr3MvvjsaSu_g2WBg,1370
8
+ sandbox_cli/cli/rules/__init__.py,sha256=ASnj-zzYMYJ-K2X9-dvsDErL4FcdZ7qZyqsd3PQN7FA,2249
9
+ sandbox_cli/cli/scanner/__init__.py,sha256=FQICErnmNdo2EvmoCeWOOdcGIwEJzGmTUH_y9dV7nsI,18905
10
+ sandbox_cli/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ sandbox_cli/internal/config.py,sha256=FUARsPCCdRhVWRXAH-qmzPw635O8OGSA7s5WkmzL5mI,3856
12
+ sandbox_cli/internal/helpers.py,sha256=r9bFq_aghCU-nHEXVXgv-LpS24prFtfYYzraHSv887E,839
13
+ sandbox_cli/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ sandbox_cli/models/detections.py,sha256=wn6yBevFDVFNT8T8EvvSAZY80NJUpvzGR6D9ejgyN4c,2377
15
+ sandbox_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ sandbox_cli/utils/extractors.py,sha256=lFpIHy9hqNyqqdT9VyzGx5GJeqdXKA0E3VMU3vjAuvg,2111
17
+ sandbox_cli/utils/merge_dll_hooks.py,sha256=8prsx6OXDMZsQ7l2nju52IQovccvAMbSL2oLq0szln0,2203
18
+ sandbox_cli/utils/compiler/__init__.py,sha256=LXogwbXl9RjIf-ehiqCrliqxq39qVabthoqzi9v1m-o,1991
19
+ sandbox_cli/utils/compiler/abc.py,sha256=5kQPSks3bNn3HtL631Yj8JY37Jzb4xYU4btBZH0KEfI,1665
20
+ sandbox_cli/utils/compiler/docker.py,sha256=c85JKyRlof9twsn_FSS9jJi9nG96icU7UOfSneZc2Ps,5056
21
+ sandbox_cli/utils/compiler/ssh.py,sha256=e_ndOdat-nHrvuTEPXwuoCnTBrE9HgS57WvRVdDUBuY,7782
22
+ sandbox_cli/utils/downloader/__init__.py,sha256=OjxzpBqwg1z3BxFNXYud9FqKtsufbDtKB8O5v0RTatk,5780
23
+ sandbox_cli/utils/scanner/__init__.py,sha256=EHbuGJdd2HxOZgpHbJYPKqrpSMT1vuGjeyNaSAtqPqQ,10567
24
+ sandbox_cli/utils/scanner/advanced.py,sha256=kneArCv0_GoHH_BMAYaRJ-70oZ9pzd1Pw89wLf-xNA8,12671
25
+ sandbox_cli/utils/scanner/rescan.py,sha256=p7uAeIPja_IxaDwrF1htI7I82WUei4URsWuvMN8SIyI,8928
26
+ sandbox_cli/utils/unpack/__init__.py,sha256=GGjwn1PfBCWiCkDsn_PpUHbzjMkolU1FEKMRdXnmqWA,3414
27
+ sandbox_cli/utils/unpack/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ sandbox_cli/utils/unpack/plugins/abc.py,sha256=KO5PpLh8bOPGK8dP7kX09AlIOCOH5uiGzj9qEjT-G2E,349
29
+ sandbox_cli/utils/unpack/plugins/correlation.py,sha256=vFZ1w50bZNpJ6JNvHpgw8Bl521Gr3DfFxiybUbowFPg,1879
30
+ sandbox_cli/utils/unpack/plugins/sort_by_plugins.py,sha256=cOzvIipRfRkPzywBs3tSga5cTFziY6r07hWCT_4hmmQ,956
31
+ sandbox_cli-0.2.26.dist-info/METADATA,sha256=gNeDe1-7vsW2lv8mba9CDYAx5OtXCKpmEUNnRFeRfLc,4663
32
+ sandbox_cli-0.2.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
+ sandbox_cli-0.2.26.dist-info/entry_points.txt,sha256=MSNWgkywjiimrStOm1YiKvWRTF6AgsIeY2Pe1uH58UA,58
34
+ sandbox_cli-0.2.26.dist-info/licenses/LICENSE,sha256=ugWNDYq5trWIIzRbW1KIspTSOb5CdmIitkt2keSP4bY,1083
35
+ sandbox_cli-0.2.26.dist-info/licenses/NOTICE,sha256=dM9C3p-YClC8_a2Z7dEaHpU5cU4GxmNJKGFQrZ4ZiEQ,16036
36
+ sandbox_cli-0.2.26.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sandbox-cli = sandbox_cli.__main__:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Security Experts Community
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.