appbinary-inspector 0.1.0__tar.gz → 0.2.0__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.
- appbinary_inspector-0.2.0/PKG-INFO +65 -0
- appbinary_inspector-0.2.0/README.md +50 -0
- {appbinary_inspector-0.1.0 → appbinary_inspector-0.2.0}/pyproject.toml +10 -3
- appbinary_inspector-0.2.0/src/appbinary_inspector/__init__.py +8 -0
- appbinary_inspector-0.2.0/src/appbinary_inspector/cli.py +48 -0
- appbinary_inspector-0.2.0/src/appbinary_inspector/extract.py +78 -0
- appbinary_inspector-0.2.0/src/appbinary_inspector/tokenizer.py +48 -0
- appbinary_inspector-0.2.0/src/appbinary_inspector/torch_builder.py +70 -0
- appbinary_inspector-0.1.0/PKG-INFO +0 -41
- appbinary_inspector-0.1.0/README.md +0 -28
- appbinary_inspector-0.1.0/src/appbinary_inspector/__init__.py +0 -7
- appbinary_inspector-0.1.0/src/appbinary_inspector/cli.py +0 -23
- appbinary_inspector-0.1.0/src/appbinary_inspector/extract.py +0 -41
- {appbinary_inspector-0.1.0 → appbinary_inspector-0.2.0}/.github/workflows/publish.yml +0 -0
- {appbinary_inspector-0.1.0 → appbinary_inspector-0.2.0}/LICENSE +0 -0
- {appbinary_inspector-0.1.0 → appbinary_inspector-0.2.0}/src/appbinary_inspector/analyzer.py +0 -0
- {appbinary_inspector-0.1.0 → appbinary_inspector-0.2.0}/src/appbinary_inspector/formats.py +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: appbinary-inspector
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Safe static inspection and embedded-file extraction for application binaries.
|
|
5
|
+
Author: William Laurento
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Provides-Extra: torch
|
|
13
|
+
Requires-Dist: torch>=2.1; extra == 'torch'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# appbinary-inspector
|
|
17
|
+
|
|
18
|
+
Static inspection of application binaries, text tokenization, and config-first PyTorch model building without executing analyzed files.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- Detects PE, ELF, Mach-O, ZIP, 7z, and RAR headers
|
|
23
|
+
- Calculates SHA-256 and SHA-1 hashes
|
|
24
|
+
- Extracts readable strings and entropy
|
|
25
|
+
- Reads basic PE header metadata
|
|
26
|
+
- Extracts embedded ZIP archives and carves recognizable binary signatures
|
|
27
|
+
- Exports PE headers and raw binary sections for authorized analysis
|
|
28
|
+
- Builds a compact tokenizer for source code, logs, or extracted text
|
|
29
|
+
- Provides editable PyTorch classifier, autoencoder, and transformer templates
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install appbinary-inspector
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Use
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
appbin inspect program.exe
|
|
41
|
+
appbin extract program.exe --output extracted
|
|
42
|
+
appbin binary-extract program.exe --output pe_parts
|
|
43
|
+
appbin tokenize source.py --vocab source-tokenizer.json
|
|
44
|
+
appbin model-template classifier
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## PyTorch models
|
|
48
|
+
|
|
49
|
+
Install optional PyTorch support:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install "appbinary-inspector[torch]"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from appbinary_inspector.torch_builder import create_model, preset
|
|
57
|
+
|
|
58
|
+
config = preset("classifier")
|
|
59
|
+
config["layers"][0]["out_features"] = 512
|
|
60
|
+
model = create_model(config)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Safety
|
|
64
|
+
|
|
65
|
+
Targets are read as bytes only and never executed. The package does not defeat encryption, DRM, licensing controls, passwords, or code protections. It can analyze plaintext, export raw PE sections, and extract recognizable embedded content from files you are authorized to inspect.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# appbinary-inspector
|
|
2
|
+
|
|
3
|
+
Static inspection of application binaries, text tokenization, and config-first PyTorch model building without executing analyzed files.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Detects PE, ELF, Mach-O, ZIP, 7z, and RAR headers
|
|
8
|
+
- Calculates SHA-256 and SHA-1 hashes
|
|
9
|
+
- Extracts readable strings and entropy
|
|
10
|
+
- Reads basic PE header metadata
|
|
11
|
+
- Extracts embedded ZIP archives and carves recognizable binary signatures
|
|
12
|
+
- Exports PE headers and raw binary sections for authorized analysis
|
|
13
|
+
- Builds a compact tokenizer for source code, logs, or extracted text
|
|
14
|
+
- Provides editable PyTorch classifier, autoencoder, and transformer templates
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install appbinary-inspector
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Use
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
appbin inspect program.exe
|
|
26
|
+
appbin extract program.exe --output extracted
|
|
27
|
+
appbin binary-extract program.exe --output pe_parts
|
|
28
|
+
appbin tokenize source.py --vocab source-tokenizer.json
|
|
29
|
+
appbin model-template classifier
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## PyTorch models
|
|
33
|
+
|
|
34
|
+
Install optional PyTorch support:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install "appbinary-inspector[torch]"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from appbinary_inspector.torch_builder import create_model, preset
|
|
42
|
+
|
|
43
|
+
config = preset("classifier")
|
|
44
|
+
config["layers"][0]["out_features"] = 512
|
|
45
|
+
model = create_model(config)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Safety
|
|
49
|
+
|
|
50
|
+
Targets are read as bytes only and never executed. The package does not defeat encryption, DRM, licensing controls, passwords, or code protections. It can analyze plaintext, export raw PE sections, and extract recognizable embedded content from files you are authorized to inspect.
|
|
@@ -4,17 +4,24 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "appbinary-inspector"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Safe static inspection and embedded-file extraction for application binaries."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = {text = "MIT"}
|
|
12
12
|
authors = [{name = "William Laurento"}]
|
|
13
|
-
classifiers = [
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
14
18
|
dependencies = []
|
|
15
19
|
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
torch = ["torch>=2.1"]
|
|
22
|
+
|
|
16
23
|
[project.scripts]
|
|
17
24
|
appbin = "appbinary_inspector.cli:main"
|
|
18
25
|
|
|
19
26
|
[tool.hatch.build.targets.wheel]
|
|
20
|
-
packages = ["src/appbinary_inspector"]
|
|
27
|
+
packages = ["src/appbinary_inspector"]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Static binary inspection without executing the analyzed file."""
|
|
2
|
+
|
|
3
|
+
from .analyzer import inspect_file
|
|
4
|
+
from .extract import extract_binary_parts, extract_embedded
|
|
5
|
+
from .tokenizer import WordTokenizer
|
|
6
|
+
|
|
7
|
+
__all__ = ["inspect_file", "extract_embedded", "extract_binary_parts", "WordTokenizer"]
|
|
8
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .analyzer import inspect_file
|
|
8
|
+
from .extract import extract_embedded
|
|
9
|
+
from .extract import extract_binary_parts
|
|
10
|
+
from .tokenizer import WordTokenizer
|
|
11
|
+
from .torch_builder import PRESETS
|
|
12
|
+
|
|
13
|
+
def main() -> None:
|
|
14
|
+
parser = argparse.ArgumentParser(description="Static application-binary inspector. Never executes targets.")
|
|
15
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
16
|
+
inspect_cmd = commands.add_parser("inspect", help="inspect a binary")
|
|
17
|
+
inspect_cmd.add_argument("file")
|
|
18
|
+
inspect_cmd.add_argument("--strings", type=int, default=200)
|
|
19
|
+
extract_cmd = commands.add_parser("extract", help="extract recognizable embedded files")
|
|
20
|
+
extract_cmd.add_argument("file")
|
|
21
|
+
extract_cmd.add_argument("--output", required=True)
|
|
22
|
+
parts_cmd = commands.add_parser("binary-extract", help="export PE headers and raw sections")
|
|
23
|
+
parts_cmd.add_argument("file")
|
|
24
|
+
parts_cmd.add_argument("--output", required=True)
|
|
25
|
+
tokenize_cmd = commands.add_parser("tokenize", help="train a tokenizer and encode a text/source file")
|
|
26
|
+
tokenize_cmd.add_argument("file")
|
|
27
|
+
tokenize_cmd.add_argument("--vocab", default="tokenizer.json")
|
|
28
|
+
tokenize_cmd.add_argument("--max-vocab", type=int, default=20_000)
|
|
29
|
+
preset_cmd = commands.add_parser("model-template", help="print a PyTorch model configuration preset")
|
|
30
|
+
preset_cmd.add_argument("name", choices=sorted(PRESETS))
|
|
31
|
+
args = parser.parse_args()
|
|
32
|
+
if args.command == "inspect":
|
|
33
|
+
result = inspect_file(args.file, args.strings)
|
|
34
|
+
elif args.command == "extract":
|
|
35
|
+
result = extract_embedded(args.file, args.output)
|
|
36
|
+
elif args.command == "binary-extract":
|
|
37
|
+
result = extract_binary_parts(args.file, args.output)
|
|
38
|
+
elif args.command == "tokenize":
|
|
39
|
+
text = Path(args.file).read_text(encoding="utf-8", errors="replace")
|
|
40
|
+
tokenizer = WordTokenizer().fit([text], max_vocab=args.max_vocab)
|
|
41
|
+
tokenizer.save(args.vocab)
|
|
42
|
+
result = {"vocabulary_path": args.vocab, "vocabulary_size": len(tokenizer.vocabulary), "tokens": tokenizer.tokenize(text), "ids": tokenizer.encode(text)}
|
|
43
|
+
else:
|
|
44
|
+
result = PRESETS[args.name]
|
|
45
|
+
print(json.dumps(result, indent=2))
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
main()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import json
|
|
5
|
+
import struct
|
|
6
|
+
import zipfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .formats import EMBEDDED_SIGNATURES
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def extract_binary_parts(path: str | Path, output_dir: str | Path) -> dict:
|
|
13
|
+
"""Write raw PE sections and a manifest. Reads bytes only; never executes code."""
|
|
14
|
+
source, destination = Path(path), Path(output_dir)
|
|
15
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
data = source.read_bytes()
|
|
17
|
+
manifest: dict = {"source": str(source), "size_bytes": len(data), "parts": []}
|
|
18
|
+
if not data.startswith(b"MZ") or len(data) < 0x40:
|
|
19
|
+
raise ValueError("binary-parts currently supports Windows PE files beginning with MZ")
|
|
20
|
+
pe_offset = struct.unpack_from("<I", data, 0x3C)[0]
|
|
21
|
+
if data[pe_offset:pe_offset + 4] != b"PE\0\0":
|
|
22
|
+
raise ValueError("invalid PE header")
|
|
23
|
+
_, section_count, _, _, _, optional_size, _ = struct.unpack_from("<HHIIIHH", data, pe_offset + 4)
|
|
24
|
+
header_end = pe_offset + 24 + optional_size + section_count * 40
|
|
25
|
+
header_path = destination / "pe_headers.bin"
|
|
26
|
+
header_path.write_bytes(data[:min(header_end, len(data))])
|
|
27
|
+
manifest["parts"].append({"name": "headers", "offset": 0, "size": header_path.stat().st_size, "path": str(header_path)})
|
|
28
|
+
table_offset = pe_offset + 24 + optional_size
|
|
29
|
+
for index in range(section_count):
|
|
30
|
+
entry = table_offset + index * 40
|
|
31
|
+
name = data[entry:entry + 8].split(b"\0", 1)[0].decode("ascii", "replace") or f"section_{index}"
|
|
32
|
+
raw_size, raw_offset = struct.unpack_from("<II", data, entry + 16)
|
|
33
|
+
content = data[raw_offset:min(raw_offset + raw_size, len(data))] if raw_offset < len(data) else b""
|
|
34
|
+
safe_name = "".join(char if char.isalnum() or char in "._-" else "_" for char in name)
|
|
35
|
+
section_path = destination / f"{index:02d}_{safe_name}.bin"
|
|
36
|
+
section_path.write_bytes(content)
|
|
37
|
+
manifest["parts"].append({"name": name, "offset": raw_offset, "size": len(content), "path": str(section_path)})
|
|
38
|
+
(destination / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
39
|
+
return manifest
|
|
40
|
+
|
|
41
|
+
def extract_embedded(path: str | Path, output_dir: str | Path, min_offset: int = 1) -> list[dict]:
|
|
42
|
+
"""Carve recognizable embedded files without executing or decrypting content.
|
|
43
|
+
|
|
44
|
+
ZIP data is extracted only when it starts at a detected signature and passes
|
|
45
|
+
ZIP validation. Other formats are preserved from their signature to EOF for
|
|
46
|
+
analyst review, avoiding guesses about encrypted or packed payload bounds.
|
|
47
|
+
"""
|
|
48
|
+
source = Path(path)
|
|
49
|
+
destination = Path(output_dir)
|
|
50
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
data = source.read_bytes()
|
|
52
|
+
found: list[dict] = []
|
|
53
|
+
seen: set[tuple[int, str]] = set()
|
|
54
|
+
for signature, (kind, extension) in EMBEDDED_SIGNATURES.items():
|
|
55
|
+
start = 0
|
|
56
|
+
while True:
|
|
57
|
+
index = data.find(signature, start)
|
|
58
|
+
if index < 0:
|
|
59
|
+
break
|
|
60
|
+
start = index + 1
|
|
61
|
+
if index < min_offset or (index, kind) in seen:
|
|
62
|
+
continue
|
|
63
|
+
seen.add((index, kind))
|
|
64
|
+
stem = f"{source.stem}_offset_{index:08x}"
|
|
65
|
+
if kind == "zip":
|
|
66
|
+
try:
|
|
67
|
+
with zipfile.ZipFile(io.BytesIO(data[index:])) as archive:
|
|
68
|
+
member_dir = destination / stem
|
|
69
|
+
member_dir.mkdir(exist_ok=True)
|
|
70
|
+
archive.extractall(member_dir)
|
|
71
|
+
found.append({"offset": index, "type": kind, "path": str(member_dir), "members": archive.namelist()})
|
|
72
|
+
except (zipfile.BadZipFile, OSError):
|
|
73
|
+
pass
|
|
74
|
+
else:
|
|
75
|
+
carved = destination / f"{stem}{extension}"
|
|
76
|
+
carved.write_bytes(data[index:])
|
|
77
|
+
found.append({"offset": index, "type": kind, "path": str(carved), "note": "carved from signature through EOF"})
|
|
78
|
+
return found
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Small, dependency-free tokenizer for source, logs, and extracted text."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from collections import Counter
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
TOKEN_PATTERN = re.compile(r"[A-Za-z_][A-Za-z_0-9]*|\d+(?:\.\d+)?|[^\s]", re.UNICODE)
|
|
10
|
+
SPECIAL_TOKENS = ("<pad>", "<unk>", "<bos>", "<eos>")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WordTokenizer:
|
|
14
|
+
"""Train a compact word/code tokenizer without external model downloads."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, vocabulary: dict[str, int] | None = None) -> None:
|
|
17
|
+
self.vocabulary = vocabulary or {token: index for index, token in enumerate(SPECIAL_TOKENS)}
|
|
18
|
+
self.inverse_vocabulary = {index: token for token, index in self.vocabulary.items()}
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def tokenize(text: str) -> list[str]:
|
|
22
|
+
return TOKEN_PATTERN.findall(text)
|
|
23
|
+
|
|
24
|
+
def fit(self, texts: list[str], max_vocab: int = 20_000, min_frequency: int = 1) -> "WordTokenizer":
|
|
25
|
+
if max_vocab < len(SPECIAL_TOKENS):
|
|
26
|
+
raise ValueError("max_vocab must allow the special tokens")
|
|
27
|
+
counts = Counter(token for text in texts for token in self.tokenize(text))
|
|
28
|
+
self.vocabulary = {token: index for index, token in enumerate(SPECIAL_TOKENS)}
|
|
29
|
+
for token, count in counts.most_common(max_vocab - len(SPECIAL_TOKENS)):
|
|
30
|
+
if count < min_frequency:
|
|
31
|
+
continue
|
|
32
|
+
self.vocabulary[token] = len(self.vocabulary)
|
|
33
|
+
self.inverse_vocabulary = {index: token for token, index in self.vocabulary.items()}
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
def encode(self, text: str, add_boundaries: bool = False) -> list[int]:
|
|
37
|
+
ids = [self.vocabulary.get(token, self.vocabulary["<unk>"]) for token in self.tokenize(text)]
|
|
38
|
+
return ([self.vocabulary["<bos>"]] + ids + [self.vocabulary["<eos>"]]) if add_boundaries else ids
|
|
39
|
+
|
|
40
|
+
def decode(self, ids: list[int]) -> list[str]:
|
|
41
|
+
return [self.inverse_vocabulary.get(index, "<unk>") for index in ids]
|
|
42
|
+
|
|
43
|
+
def save(self, path: str | Path) -> None:
|
|
44
|
+
Path(path).write_text(json.dumps({"vocabulary": self.vocabulary}, indent=2), encoding="utf-8")
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def load(cls, path: str | Path) -> "WordTokenizer":
|
|
48
|
+
return cls(json.loads(Path(path).read_text(encoding="utf-8"))["vocabulary"])
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Config-first PyTorch model construction with no hidden training framework."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
|
|
6
|
+
PRESETS = {
|
|
7
|
+
"classifier": {"input_features": 128, "layers": [{"type": "linear", "out_features": 256}, {"type": "relu"}, {"type": "dropout", "p": 0.2}, {"type": "linear", "out_features": 10}]},
|
|
8
|
+
"autoencoder": {"input_features": 128, "layers": [{"type": "linear", "out_features": 64}, {"type": "relu"}, {"type": "linear", "out_features": 16}, {"type": "relu"}, {"type": "linear", "out_features": 64}, {"type": "relu"}, {"type": "linear", "out_features": 128}]},
|
|
9
|
+
"text_transformer": {"vocab_size": 20_000, "embedding_dim": 128, "sequence_length": 128, "transformer_layers": 2, "heads": 4, "classes": 2},
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def preset(name: str) -> dict:
|
|
14
|
+
if name not in PRESETS:
|
|
15
|
+
raise ValueError(f"Unknown preset: {name}. Choose from {', '.join(PRESETS)}")
|
|
16
|
+
return deepcopy(PRESETS[name])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_model(config: dict):
|
|
20
|
+
"""Build an nn.Module from a transparent config. Requires ``pip install appbinary-inspector[torch]``."""
|
|
21
|
+
try:
|
|
22
|
+
import torch.nn as nn
|
|
23
|
+
except ImportError as error:
|
|
24
|
+
raise RuntimeError("PyTorch is optional. Install it with: pip install appbinary-inspector[torch]") from error
|
|
25
|
+
|
|
26
|
+
if config.get("kind") == "transformer" or "vocab_size" in config:
|
|
27
|
+
required = ("vocab_size", "embedding_dim", "sequence_length", "transformer_layers", "heads", "classes")
|
|
28
|
+
missing = [key for key in required if key not in config]
|
|
29
|
+
if missing:
|
|
30
|
+
raise ValueError(f"Missing transformer settings: {', '.join(missing)}")
|
|
31
|
+
d_model = config["embedding_dim"]
|
|
32
|
+
encoder = nn.TransformerEncoder(
|
|
33
|
+
nn.TransformerEncoderLayer(d_model=d_model, nhead=config["heads"], batch_first=True),
|
|
34
|
+
num_layers=config["transformer_layers"],
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
class TokenClassifier(nn.Module):
|
|
38
|
+
def __init__(self) -> None:
|
|
39
|
+
super().__init__()
|
|
40
|
+
self.embedding = nn.Embedding(config["vocab_size"], d_model)
|
|
41
|
+
self.encoder = encoder
|
|
42
|
+
self.head = nn.Linear(d_model, config["classes"])
|
|
43
|
+
|
|
44
|
+
def forward(self, token_ids):
|
|
45
|
+
encoded = self.encoder(self.embedding(token_ids))
|
|
46
|
+
return self.head(encoded.mean(dim=1))
|
|
47
|
+
|
|
48
|
+
return TokenClassifier()
|
|
49
|
+
|
|
50
|
+
features = config.get("input_features")
|
|
51
|
+
if not isinstance(features, int) or features < 1:
|
|
52
|
+
raise ValueError("input_features must be a positive integer")
|
|
53
|
+
layers = []
|
|
54
|
+
for spec in config.get("layers", []):
|
|
55
|
+
kind = spec.get("type", "").lower()
|
|
56
|
+
if kind == "linear":
|
|
57
|
+
out_features = spec.get("out_features")
|
|
58
|
+
if not isinstance(out_features, int) or out_features < 1:
|
|
59
|
+
raise ValueError("linear layers require positive out_features")
|
|
60
|
+
layers.append(nn.Linear(features, out_features))
|
|
61
|
+
features = out_features
|
|
62
|
+
elif kind in {"relu", "gelu", "tanh", "sigmoid", "flatten"}:
|
|
63
|
+
layers.append({"relu": nn.ReLU, "gelu": nn.GELU, "tanh": nn.Tanh, "sigmoid": nn.Sigmoid, "flatten": nn.Flatten}[kind]())
|
|
64
|
+
elif kind == "dropout":
|
|
65
|
+
layers.append(nn.Dropout(float(spec.get("p", 0.5))))
|
|
66
|
+
else:
|
|
67
|
+
raise ValueError(f"Unsupported layer type: {kind}")
|
|
68
|
+
if not layers:
|
|
69
|
+
raise ValueError("config requires at least one layer")
|
|
70
|
+
return nn.Sequential(*layers)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: appbinary-inspector
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Safe static inspection and embedded-file extraction for application binaries.
|
|
5
|
-
Author: William Laurento
|
|
6
|
-
License: MIT
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Requires-Python: >=3.10
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
|
|
14
|
-
# appbinary-inspector
|
|
15
|
-
|
|
16
|
-
Static inspection of application binaries without executing them.
|
|
17
|
-
|
|
18
|
-
## Features
|
|
19
|
-
|
|
20
|
-
- Detects PE, ELF, Mach-O, ZIP, 7z, and RAR headers
|
|
21
|
-
- Calculates SHA-256 and SHA-1 hashes
|
|
22
|
-
- Extracts readable strings and entropy
|
|
23
|
-
- Reads basic PE header metadata
|
|
24
|
-
- Extracts embedded ZIP archives and carves recognizable binary signatures
|
|
25
|
-
|
|
26
|
-
## Install
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
pip install appbinary-inspector
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Use
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
appbin inspect program.exe
|
|
36
|
-
appbin extract program.exe --output extracted
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Safety
|
|
40
|
-
|
|
41
|
-
Targets are read as bytes only and never executed. The package does not defeat encryption, DRM, licensing controls, passwords, or code protections. It can analyze plaintext and extract recognizable embedded content from files you are authorized to inspect.
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# appbinary-inspector
|
|
2
|
-
|
|
3
|
-
Static inspection of application binaries without executing them.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- Detects PE, ELF, Mach-O, ZIP, 7z, and RAR headers
|
|
8
|
-
- Calculates SHA-256 and SHA-1 hashes
|
|
9
|
-
- Extracts readable strings and entropy
|
|
10
|
-
- Reads basic PE header metadata
|
|
11
|
-
- Extracts embedded ZIP archives and carves recognizable binary signatures
|
|
12
|
-
|
|
13
|
-
## Install
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
pip install appbinary-inspector
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Use
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
appbin inspect program.exe
|
|
23
|
-
appbin extract program.exe --output extracted
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Safety
|
|
27
|
-
|
|
28
|
-
Targets are read as bytes only and never executed. The package does not defeat encryption, DRM, licensing controls, passwords, or code protections. It can analyze plaintext and extract recognizable embedded content from files you are authorized to inspect.
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import argparse
|
|
4
|
-
import json
|
|
5
|
-
|
|
6
|
-
from .analyzer import inspect_file
|
|
7
|
-
from .extract import extract_embedded
|
|
8
|
-
|
|
9
|
-
def main() -> None:
|
|
10
|
-
parser = argparse.ArgumentParser(description="Static application-binary inspector. Never executes targets.")
|
|
11
|
-
commands = parser.add_subparsers(dest="command", required=True)
|
|
12
|
-
inspect_cmd = commands.add_parser("inspect", help="inspect a binary")
|
|
13
|
-
inspect_cmd.add_argument("file")
|
|
14
|
-
inspect_cmd.add_argument("--strings", type=int, default=200)
|
|
15
|
-
extract_cmd = commands.add_parser("extract", help="extract recognizable embedded files")
|
|
16
|
-
extract_cmd.add_argument("file")
|
|
17
|
-
extract_cmd.add_argument("--output", required=True)
|
|
18
|
-
args = parser.parse_args()
|
|
19
|
-
result = inspect_file(args.file, args.strings) if args.command == "inspect" else extract_embedded(args.file, args.output)
|
|
20
|
-
print(json.dumps(result, indent=2))
|
|
21
|
-
|
|
22
|
-
if __name__ == "__main__":
|
|
23
|
-
main()
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import io
|
|
4
|
-
import zipfile
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from .formats import EMBEDDED_SIGNATURES
|
|
8
|
-
|
|
9
|
-
def extract_embedded(path: str | Path, output_dir: str | Path, min_offset: int = 1) -> list[dict]:
|
|
10
|
-
"""Carve recognizable embedded files without executing or decrypting content."""
|
|
11
|
-
source = Path(path)
|
|
12
|
-
destination = Path(output_dir)
|
|
13
|
-
destination.mkdir(parents=True, exist_ok=True)
|
|
14
|
-
data = source.read_bytes()
|
|
15
|
-
found: list[dict] = []
|
|
16
|
-
seen: set[tuple[int, str]] = set()
|
|
17
|
-
for signature, (kind, extension) in EMBEDDED_SIGNATURES.items():
|
|
18
|
-
start = 0
|
|
19
|
-
while True:
|
|
20
|
-
index = data.find(signature, start)
|
|
21
|
-
if index < 0:
|
|
22
|
-
break
|
|
23
|
-
start = index + 1
|
|
24
|
-
if index < min_offset or (index, kind) in seen:
|
|
25
|
-
continue
|
|
26
|
-
seen.add((index, kind))
|
|
27
|
-
stem = f"{source.stem}_offset_{index:08x}"
|
|
28
|
-
if kind == "zip":
|
|
29
|
-
try:
|
|
30
|
-
with zipfile.ZipFile(io.BytesIO(data[index:])) as archive:
|
|
31
|
-
member_dir = destination / stem
|
|
32
|
-
member_dir.mkdir(exist_ok=True)
|
|
33
|
-
archive.extractall(member_dir)
|
|
34
|
-
found.append({"offset": index, "type": kind, "path": str(member_dir), "members": archive.namelist()})
|
|
35
|
-
except (zipfile.BadZipFile, OSError):
|
|
36
|
-
pass
|
|
37
|
-
else:
|
|
38
|
-
carved = destination / f"{stem}{extension}"
|
|
39
|
-
carved.write_bytes(data[index:])
|
|
40
|
-
found.append({"offset": index, "type": kind, "path": str(carved), "note": "carved from signature through EOF"})
|
|
41
|
-
return found
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|