appbinary-inspector 0.2.0__tar.gz → 0.6.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.6.0/PKG-INFO +120 -0
- appbinary_inspector-0.6.0/README.md +105 -0
- {appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/pyproject.toml +2 -2
- {appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/__init__.py +4 -2
- appbinary_inspector-0.6.0/src/appbinary_inspector/card_api.py +37 -0
- appbinary_inspector-0.6.0/src/appbinary_inspector/cardstack.py +246 -0
- appbinary_inspector-0.6.0/src/appbinary_inspector/chat_server.py +67 -0
- appbinary_inspector-0.6.0/src/appbinary_inspector/cli.py +83 -0
- appbinary_inspector-0.2.0/PKG-INFO +0 -65
- appbinary_inspector-0.2.0/README.md +0 -50
- appbinary_inspector-0.2.0/src/appbinary_inspector/cli.py +0 -48
- {appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/.github/workflows/publish.yml +0 -0
- {appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/LICENSE +0 -0
- {appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/analyzer.py +0 -0
- {appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/extract.py +0 -0
- {appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/formats.py +0 -0
- {appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/tokenizer.py +0 -0
- {appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/torch_builder.py +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: appbinary-inspector
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Open-source lazy-card character model plus static binary inspection tools.
|
|
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 tools plus an open-source, disk-paged character-model architecture.
|
|
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
|
+
- Keeps CardStack model cards as separately stored 4-bit SSD files
|
|
31
|
+
- Loads only router-selected cards into a small LRU cache during generation
|
|
32
|
+
- Generates one next byte at a time for true character-level output
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install appbinary-inspector
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Use
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
appbin inspect program.exe
|
|
44
|
+
appbin extract program.exe --output extracted
|
|
45
|
+
appbin binary-extract program.exe --output pe_parts
|
|
46
|
+
appbin tokenize source.py --vocab source-tokenizer.json
|
|
47
|
+
appbin model-template classifier
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## CardStack: low-VRAM model experiments
|
|
51
|
+
|
|
52
|
+
CardStack is a small trainable byte-level language model. Its expert cards are
|
|
53
|
+
stored as separate 4-bit files. At inference, inactive cards remain on disk and
|
|
54
|
+
the cache retains only a limited number of recently selected cards.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from appbinary_inspector import CardStackConfig, CardStackLM, train_next_character
|
|
58
|
+
|
|
59
|
+
model = CardStackLM(CardStackConfig(width=128, layers=4, cards_per_layer=8, active_cards=2, cache_cards=4))
|
|
60
|
+
losses = train_next_character(model, open("training.txt", encoding="utf-8").read(), steps=500)
|
|
61
|
+
model.save("my-card-model") # writes a base file plus separate 4-bit card files
|
|
62
|
+
|
|
63
|
+
paged = CardStackLM.load("my-card-model")
|
|
64
|
+
print(paged.generate("Hello", new_characters=80))
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
CLI version:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
appbin card-create --output my-card-model --width 128 --layers 4 --cards 8 --active-cards 2
|
|
71
|
+
appbin card-train my-card-model training.txt --steps 500
|
|
72
|
+
appbin card-generate my-card-model "Hello" --characters 80
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Text chat interface
|
|
76
|
+
|
|
77
|
+
Open a local chat box after training a model. It binds only to your computer,
|
|
78
|
+
opens in your browser, and never sends prompts to a remote service.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
appbin chat my-card-model
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Python import API
|
|
85
|
+
|
|
86
|
+
Use the model from normal Python code without a terminal command interface:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from appbinary_inspector import appbincardcr
|
|
90
|
+
|
|
91
|
+
mycard = appbincardcr("my-card-model")
|
|
92
|
+
reply = mycard("Explain gravity", output=True)
|
|
93
|
+
|
|
94
|
+
# Or call it in one line:
|
|
95
|
+
reply = appbincardcr(mycard, "Explain gravity", output=True)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This reduces active model-card memory. It does not make a dense, unmodified
|
|
99
|
+
70B model fit instantly on a low-VRAM GPU: disk bandwidth and CPU/GPU transfer
|
|
100
|
+
time become the tradeoff, and models need to be trained for sparse routing.
|
|
101
|
+
|
|
102
|
+
## PyTorch models
|
|
103
|
+
|
|
104
|
+
Install optional PyTorch support:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pip install "appbinary-inspector[torch]"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from appbinary_inspector.torch_builder import create_model, preset
|
|
112
|
+
|
|
113
|
+
config = preset("classifier")
|
|
114
|
+
config["layers"][0]["out_features"] = 512
|
|
115
|
+
model = create_model(config)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Safety
|
|
119
|
+
|
|
120
|
+
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,105 @@
|
|
|
1
|
+
# appbinary-inspector
|
|
2
|
+
|
|
3
|
+
Static inspection tools plus an open-source, disk-paged character-model architecture.
|
|
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
|
+
- Keeps CardStack model cards as separately stored 4-bit SSD files
|
|
16
|
+
- Loads only router-selected cards into a small LRU cache during generation
|
|
17
|
+
- Generates one next byte at a time for true character-level output
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install appbinary-inspector
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Use
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
appbin inspect program.exe
|
|
29
|
+
appbin extract program.exe --output extracted
|
|
30
|
+
appbin binary-extract program.exe --output pe_parts
|
|
31
|
+
appbin tokenize source.py --vocab source-tokenizer.json
|
|
32
|
+
appbin model-template classifier
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## CardStack: low-VRAM model experiments
|
|
36
|
+
|
|
37
|
+
CardStack is a small trainable byte-level language model. Its expert cards are
|
|
38
|
+
stored as separate 4-bit files. At inference, inactive cards remain on disk and
|
|
39
|
+
the cache retains only a limited number of recently selected cards.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from appbinary_inspector import CardStackConfig, CardStackLM, train_next_character
|
|
43
|
+
|
|
44
|
+
model = CardStackLM(CardStackConfig(width=128, layers=4, cards_per_layer=8, active_cards=2, cache_cards=4))
|
|
45
|
+
losses = train_next_character(model, open("training.txt", encoding="utf-8").read(), steps=500)
|
|
46
|
+
model.save("my-card-model") # writes a base file plus separate 4-bit card files
|
|
47
|
+
|
|
48
|
+
paged = CardStackLM.load("my-card-model")
|
|
49
|
+
print(paged.generate("Hello", new_characters=80))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
CLI version:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
appbin card-create --output my-card-model --width 128 --layers 4 --cards 8 --active-cards 2
|
|
56
|
+
appbin card-train my-card-model training.txt --steps 500
|
|
57
|
+
appbin card-generate my-card-model "Hello" --characters 80
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Text chat interface
|
|
61
|
+
|
|
62
|
+
Open a local chat box after training a model. It binds only to your computer,
|
|
63
|
+
opens in your browser, and never sends prompts to a remote service.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
appbin chat my-card-model
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Python import API
|
|
70
|
+
|
|
71
|
+
Use the model from normal Python code without a terminal command interface:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from appbinary_inspector import appbincardcr
|
|
75
|
+
|
|
76
|
+
mycard = appbincardcr("my-card-model")
|
|
77
|
+
reply = mycard("Explain gravity", output=True)
|
|
78
|
+
|
|
79
|
+
# Or call it in one line:
|
|
80
|
+
reply = appbincardcr(mycard, "Explain gravity", output=True)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This reduces active model-card memory. It does not make a dense, unmodified
|
|
84
|
+
70B model fit instantly on a low-VRAM GPU: disk bandwidth and CPU/GPU transfer
|
|
85
|
+
time become the tradeoff, and models need to be trained for sparse routing.
|
|
86
|
+
|
|
87
|
+
## PyTorch models
|
|
88
|
+
|
|
89
|
+
Install optional PyTorch support:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pip install "appbinary-inspector[torch]"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from appbinary_inspector.torch_builder import create_model, preset
|
|
97
|
+
|
|
98
|
+
config = preset("classifier")
|
|
99
|
+
config["layers"][0]["out_features"] = 512
|
|
100
|
+
model = create_model(config)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Safety
|
|
104
|
+
|
|
105
|
+
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,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "appbinary-inspector"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.6.0"
|
|
8
|
+
description = "Open-source lazy-card character model plus static binary inspection tools."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = {text = "MIT"}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from .analyzer import inspect_file
|
|
4
4
|
from .extract import extract_binary_parts, extract_embedded
|
|
5
5
|
from .tokenizer import WordTokenizer
|
|
6
|
+
from .cardstack import CardStackConfig, CardStackLM, train_next_character
|
|
7
|
+
from .card_api import Card, appbincardcr
|
|
6
8
|
|
|
7
|
-
__all__ = ["inspect_file", "extract_embedded", "extract_binary_parts", "WordTokenizer"]
|
|
8
|
-
__version__ = "0.
|
|
9
|
+
__all__ = ["inspect_file", "extract_embedded", "extract_binary_parts", "WordTokenizer", "CardStackConfig", "CardStackLM", "train_next_character", "Card", "appbincardcr"]
|
|
10
|
+
__version__ = "0.6.0"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Small Python API for using a disk-paged CardStack model in normal code."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .cardstack import CardStackLM
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Card:
|
|
10
|
+
"""A callable local text model.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
mycard = appbincardcr("my-card-model")
|
|
14
|
+
reply = mycard("Explain gravity", output=True)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, model: str | Path | CardStackLM) -> None:
|
|
18
|
+
self.model = CardStackLM.load(model) if isinstance(model, (str, Path)) else model
|
|
19
|
+
|
|
20
|
+
def __call__(self, text: str, output: bool = False, characters: int = 180, temperature: float = 0.8) -> str:
|
|
21
|
+
reply = self.model.generate(text, new_characters=characters, temperature=temperature)
|
|
22
|
+
if output:
|
|
23
|
+
print(reply)
|
|
24
|
+
return reply
|
|
25
|
+
|
|
26
|
+
def reply(self, text: str, **options) -> str:
|
|
27
|
+
return self(text, **options)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def appbincardcr(mycard: str | Path | CardStackLM | Card, text: str | None = None, output: bool = False, **options):
|
|
31
|
+
"""Create or call a CardStack text model with one short function.
|
|
32
|
+
|
|
33
|
+
``appbincardcr("my-card-model")`` returns a reusable card.
|
|
34
|
+
``appbincardcr(mycard, "Hello", output=True)`` generates and prints text.
|
|
35
|
+
"""
|
|
36
|
+
card = mycard if isinstance(mycard, Card) else Card(mycard)
|
|
37
|
+
return card if text is None else card(text, output=output, **options)
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""A compact, character-by-character language model with on-demand expert cards.
|
|
2
|
+
|
|
3
|
+
This is a research-sized model architecture, not a pre-trained replacement for
|
|
4
|
+
large commercial models. Each layer routes work to only ``active_cards`` expert
|
|
5
|
+
cards. Serialized cards live in separate files and are loaded into an LRU cache
|
|
6
|
+
only when the router selects them.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import math
|
|
12
|
+
from collections import OrderedDict
|
|
13
|
+
from dataclasses import asdict, dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _torch():
|
|
18
|
+
try:
|
|
19
|
+
import torch
|
|
20
|
+
import torch.nn as nn
|
|
21
|
+
import torch.nn.functional as functional
|
|
22
|
+
return torch, nn, functional
|
|
23
|
+
except ImportError as error:
|
|
24
|
+
raise RuntimeError("Install PyTorch first: pip install appbinary-inspector[torch]") from error
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class CardStackConfig:
|
|
29
|
+
width: int = 128
|
|
30
|
+
layers: int = 4
|
|
31
|
+
cards_per_layer: int = 8
|
|
32
|
+
active_cards: int = 2
|
|
33
|
+
context_size: int = 128
|
|
34
|
+
cache_cards: int = 4
|
|
35
|
+
card_bits: int = 4
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _pack_int4(values):
|
|
39
|
+
"""Pack signed 4-bit values two per byte for SSD storage."""
|
|
40
|
+
torch, _, _ = _torch()
|
|
41
|
+
unsigned = (values.to(torch.int16) + 8).to(torch.uint8).flatten()
|
|
42
|
+
if unsigned.numel() % 2:
|
|
43
|
+
unsigned = torch.cat([unsigned, torch.zeros(1, dtype=torch.uint8)])
|
|
44
|
+
return unsigned[0::2] | (unsigned[1::2] << 4)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _unpack_int4(packed, count: int):
|
|
48
|
+
torch, _, _ = _torch()
|
|
49
|
+
low = packed & 0x0F
|
|
50
|
+
high = packed >> 4
|
|
51
|
+
return (torch.stack((low, high), dim=1).flatten()[:count].to(torch.int16) - 8).to(torch.float32)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def quantize_card(state: dict) -> dict:
|
|
55
|
+
"""Convert a card state dict to independently scaled 4-bit tensors."""
|
|
56
|
+
torch, _, _ = _torch()
|
|
57
|
+
tensors = {}
|
|
58
|
+
for name, tensor in state.items():
|
|
59
|
+
source = tensor.detach().cpu().float()
|
|
60
|
+
scale = max(float(source.abs().max()) / 7.0, 1e-8)
|
|
61
|
+
quantized = torch.round(source / scale).clamp(-8, 7).to("int8")
|
|
62
|
+
tensors[name] = {"shape": list(source.shape), "scale": scale, "count": source.numel(), "packed": _pack_int4(quantized)}
|
|
63
|
+
return {"format": "cardstack-int4-v1", "tensors": tensors}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def dequantize_card(payload: dict) -> dict:
|
|
67
|
+
"""Restore a quantized card state dict just before the card is needed."""
|
|
68
|
+
if payload.get("format") != "cardstack-int4-v1":
|
|
69
|
+
return payload
|
|
70
|
+
torch, _, _ = _torch()
|
|
71
|
+
result = {}
|
|
72
|
+
for name, item in payload["tensors"].items():
|
|
73
|
+
values = _unpack_int4(item["packed"], item["count"]) * item["scale"]
|
|
74
|
+
result[name] = values.reshape(item["shape"])
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CardStackLM:
|
|
79
|
+
"""Byte-level LM. A generation step emits exactly one next byte/character."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, config: CardStackConfig, card_directory: str | Path | None = None) -> None:
|
|
82
|
+
torch, nn, _ = _torch()
|
|
83
|
+
if not 1 <= config.active_cards <= config.cards_per_layer:
|
|
84
|
+
raise ValueError("active_cards must be between 1 and cards_per_layer")
|
|
85
|
+
self.torch, self.nn, self.config = torch, nn, config
|
|
86
|
+
self.embedding = nn.Embedding(256, config.width)
|
|
87
|
+
self.position = nn.Embedding(config.context_size, config.width)
|
|
88
|
+
self.routers = nn.ModuleList([nn.Linear(config.width, config.cards_per_layer) for _ in range(config.layers)])
|
|
89
|
+
self.norms = nn.ModuleList([nn.LayerNorm(config.width) for _ in range(config.layers)])
|
|
90
|
+
self.head = nn.Linear(config.width, 256)
|
|
91
|
+
self.card_directory = Path(card_directory) if card_directory else None
|
|
92
|
+
self.experts = nn.ModuleList() if not card_directory else None
|
|
93
|
+
if self.experts is not None:
|
|
94
|
+
for _ in range(config.layers):
|
|
95
|
+
self.experts.append(nn.ModuleList([self._new_card() for _ in range(config.cards_per_layer)]))
|
|
96
|
+
self.cache: OrderedDict[tuple[int, int], object] = OrderedDict()
|
|
97
|
+
|
|
98
|
+
def _new_card(self):
|
|
99
|
+
nn = self.nn
|
|
100
|
+
return nn.Sequential(nn.Linear(self.config.width, self.config.width * 2), nn.GELU(), nn.Linear(self.config.width * 2, self.config.width))
|
|
101
|
+
|
|
102
|
+
def _card(self, layer: int, index: int):
|
|
103
|
+
if self.experts is not None:
|
|
104
|
+
return self.experts[layer][index]
|
|
105
|
+
key = (layer, index)
|
|
106
|
+
if key in self.cache:
|
|
107
|
+
self.cache.move_to_end(key)
|
|
108
|
+
return self.cache[key]
|
|
109
|
+
if self.card_directory is None:
|
|
110
|
+
raise RuntimeError("No card storage configured")
|
|
111
|
+
card = self._new_card()
|
|
112
|
+
state = self.torch.load(self.card_directory / f"layer_{layer:02d}_card_{index:02d}.pt", map_location="cpu", weights_only=True)
|
|
113
|
+
state = dequantize_card(state)
|
|
114
|
+
card.load_state_dict(state)
|
|
115
|
+
card.eval()
|
|
116
|
+
self.cache[key] = card
|
|
117
|
+
while len(self.cache) > self.config.cache_cards:
|
|
118
|
+
self.cache.popitem(last=False)
|
|
119
|
+
return card
|
|
120
|
+
|
|
121
|
+
def prefetch(self, layer: int, card_indices: list[int]) -> None:
|
|
122
|
+
"""Load likely next cards before the caller reaches that layer."""
|
|
123
|
+
if self.experts is None:
|
|
124
|
+
for index in card_indices:
|
|
125
|
+
self._card(layer, int(index))
|
|
126
|
+
|
|
127
|
+
def parameters(self):
|
|
128
|
+
modules = [self.embedding, self.position, self.routers, self.norms, self.head]
|
|
129
|
+
if self.experts is not None:
|
|
130
|
+
modules.append(self.experts)
|
|
131
|
+
for module in modules:
|
|
132
|
+
yield from module.parameters()
|
|
133
|
+
|
|
134
|
+
def train(self) -> None:
|
|
135
|
+
for module in (self.embedding, self.position, self.routers, self.norms, self.head):
|
|
136
|
+
module.train()
|
|
137
|
+
if self.experts is not None:
|
|
138
|
+
self.experts.train()
|
|
139
|
+
|
|
140
|
+
def eval(self) -> None:
|
|
141
|
+
for module in (self.embedding, self.position, self.routers, self.norms, self.head):
|
|
142
|
+
module.eval()
|
|
143
|
+
if self.experts is not None:
|
|
144
|
+
self.experts.eval()
|
|
145
|
+
|
|
146
|
+
def forward(self, token_ids):
|
|
147
|
+
torch = self.torch
|
|
148
|
+
_, sequence = token_ids.shape
|
|
149
|
+
if sequence > self.config.context_size:
|
|
150
|
+
token_ids = token_ids[:, -self.config.context_size:]
|
|
151
|
+
sequence = token_ids.shape[1]
|
|
152
|
+
positions = torch.arange(sequence, device=token_ids.device)
|
|
153
|
+
hidden = self.embedding(token_ids) + self.position(positions)[None, :, :]
|
|
154
|
+
for layer, (router, norm) in enumerate(zip(self.routers, self.norms)):
|
|
155
|
+
normalized = norm(hidden)
|
|
156
|
+
scores = router(normalized)
|
|
157
|
+
values, indices = scores.topk(self.config.active_cards, dim=-1)
|
|
158
|
+
# After layer 0 the current representation is a useful cheap hint for
|
|
159
|
+
# the next router, so prefetch its likely cards from SSD into the LRU.
|
|
160
|
+
if self.experts is None and layer + 1 < self.config.layers:
|
|
161
|
+
next_scores = self.routers[layer + 1](normalized)
|
|
162
|
+
next_indices = next_scores.topk(self.config.active_cards, dim=-1).indices.unique().tolist()
|
|
163
|
+
self.prefetch(layer + 1, next_indices)
|
|
164
|
+
weights = values.softmax(dim=-1)
|
|
165
|
+
routed = torch.zeros_like(hidden)
|
|
166
|
+
# Run only cards selected anywhere in this batch, then apply their gate.
|
|
167
|
+
for card_index in indices.unique().tolist():
|
|
168
|
+
card_output = self._card(layer, card_index)(normalized)
|
|
169
|
+
gate = (weights * (indices == card_index)).sum(dim=-1, keepdim=True)
|
|
170
|
+
routed = routed + card_output * gate
|
|
171
|
+
hidden = hidden + routed
|
|
172
|
+
return self.head(hidden)
|
|
173
|
+
|
|
174
|
+
def save(self, directory: str | Path, quantize_cards: bool = True) -> None:
|
|
175
|
+
if self.experts is None:
|
|
176
|
+
raise RuntimeError("A paged inference model cannot be saved as a new training bundle")
|
|
177
|
+
destination = Path(directory)
|
|
178
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
179
|
+
(destination / "config.json").write_text(json.dumps(asdict(self.config), indent=2), encoding="utf-8")
|
|
180
|
+
self.torch.save({"embedding": self.embedding.state_dict(), "position": self.position.state_dict(), "routers": self.routers.state_dict(), "norms": self.norms.state_dict(), "head": self.head.state_dict()}, destination / "base.pt")
|
|
181
|
+
for layer, cards in enumerate(self.experts):
|
|
182
|
+
for index, card in enumerate(cards):
|
|
183
|
+
payload = quantize_card(card.state_dict()) if quantize_cards else card.state_dict()
|
|
184
|
+
self.torch.save(payload, destination / f"layer_{layer:02d}_card_{index:02d}.pt")
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def load(cls, directory: str | Path) -> "CardStackLM":
|
|
188
|
+
folder = Path(directory)
|
|
189
|
+
config = CardStackConfig(**json.loads((folder / "config.json").read_text(encoding="utf-8")))
|
|
190
|
+
model = cls(config, card_directory=folder)
|
|
191
|
+
state = model.torch.load(folder / "base.pt", map_location="cpu", weights_only=True)
|
|
192
|
+
for name in ("embedding", "position", "routers", "norms", "head"):
|
|
193
|
+
getattr(model, name).load_state_dict(state[name])
|
|
194
|
+
model.eval()
|
|
195
|
+
return model
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def load_for_training(cls, directory: str | Path) -> "CardStackLM":
|
|
199
|
+
"""Load every card into memory for training. Use ``load`` for low-memory inference."""
|
|
200
|
+
folder = Path(directory)
|
|
201
|
+
config = CardStackConfig(**json.loads((folder / "config.json").read_text(encoding="utf-8")))
|
|
202
|
+
model = cls(config)
|
|
203
|
+
state = model.torch.load(folder / "base.pt", map_location="cpu", weights_only=True)
|
|
204
|
+
for name in ("embedding", "position", "routers", "norms", "head"):
|
|
205
|
+
getattr(model, name).load_state_dict(state[name])
|
|
206
|
+
for layer, cards in enumerate(model.experts):
|
|
207
|
+
for index, card in enumerate(cards):
|
|
208
|
+
payload = model.torch.load(folder / f"layer_{layer:02d}_card_{index:02d}.pt", map_location="cpu", weights_only=True)
|
|
209
|
+
card.load_state_dict(dequantize_card(payload))
|
|
210
|
+
return model
|
|
211
|
+
|
|
212
|
+
def generate(self, prompt: str, new_characters: int = 80, temperature: float = 0.8) -> str:
|
|
213
|
+
torch = self.torch
|
|
214
|
+
if temperature <= 0:
|
|
215
|
+
raise ValueError("temperature must be positive")
|
|
216
|
+
values = list(prompt.encode("utf-8", "replace")) or [32]
|
|
217
|
+
self.eval()
|
|
218
|
+
with torch.no_grad():
|
|
219
|
+
for _ in range(new_characters):
|
|
220
|
+
input_ids = torch.tensor([values[-self.config.context_size:]], dtype=torch.long)
|
|
221
|
+
logits = self.forward(input_ids)[0, -1] / temperature
|
|
222
|
+
next_value = torch.multinomial(torch.softmax(logits, dim=-1), 1).item()
|
|
223
|
+
values.append(next_value)
|
|
224
|
+
return bytes(values).decode("utf-8", "replace")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def train_next_character(model: CardStackLM, text: str, steps: int = 500, learning_rate: float = 3e-4) -> list[float]:
|
|
228
|
+
"""Train a small model from user-supplied text. Returns sampled loss values."""
|
|
229
|
+
torch, _, functional = _torch()
|
|
230
|
+
data = torch.tensor(list(text.encode("utf-8", "replace")), dtype=torch.long)
|
|
231
|
+
if len(data) <= model.config.context_size:
|
|
232
|
+
raise ValueError("Training text must exceed context_size bytes")
|
|
233
|
+
model.train()
|
|
234
|
+
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
|
|
235
|
+
losses = []
|
|
236
|
+
for step in range(steps):
|
|
237
|
+
start = torch.randint(0, len(data) - model.config.context_size - 1, (1,)).item()
|
|
238
|
+
inputs = data[start:start + model.config.context_size].unsqueeze(0)
|
|
239
|
+
targets = data[start + 1:start + model.config.context_size + 1].unsqueeze(0)
|
|
240
|
+
optimizer.zero_grad()
|
|
241
|
+
loss = functional.cross_entropy(model.forward(inputs).reshape(-1, 256), targets.reshape(-1))
|
|
242
|
+
loss.backward()
|
|
243
|
+
optimizer.step()
|
|
244
|
+
if step % max(1, steps // 10) == 0 or step == steps - 1:
|
|
245
|
+
losses.append(round(float(loss.detach()), 4))
|
|
246
|
+
return losses
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Local browser chat UI for a trained CardStack model."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import threading
|
|
6
|
+
import webbrowser
|
|
7
|
+
from http import HTTPStatus
|
|
8
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .cardstack import CardStackLM
|
|
12
|
+
|
|
13
|
+
PAGE = """<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>CardStack Chat</title><style>
|
|
14
|
+
:root{--bg:#0a0d13;--panel:#121823;--line:#263142;--text:#f1f5f9;--muted:#9dacbf;--accent:#67e8f9;--user:#193352}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:16px system-ui,-apple-system,Segoe UI,sans-serif}.shell{max-width:920px;min-height:100vh;margin:auto;padding:28px 18px;display:flex;flex-direction:column;gap:20px}header{display:flex;justify-content:space-between;align-items:flex-start;border-bottom:1px solid var(--line);padding-bottom:18px}h1{font-size:22px;margin:0 0 5px}p{margin:0;color:var(--muted)}.badge{font-size:12px;color:var(--accent);border:1px solid #1d6976;border-radius:999px;padding:6px 9px}.chat{flex:1;display:flex;flex-direction:column;gap:14px}.empty{margin:auto;text-align:center;max-width:470px}.empty h2{font-size:24px;margin-bottom:8px}.message{max-width:84%;padding:13px 15px;border:1px solid var(--line);border-radius:14px;white-space:pre-wrap;line-height:1.45}.assistant{align-self:flex-start;background:var(--panel)}.user{align-self:flex-end;background:var(--user)}form{display:flex;gap:10px;position:sticky;bottom:0;background:var(--bg);padding-top:12px}textarea{resize:none;min-height:54px;max-height:180px;flex:1;border:1px solid var(--line);background:var(--panel);color:var(--text);border-radius:12px;padding:15px;font:inherit}button{border:0;border-radius:12px;padding:0 18px;background:var(--accent);color:#062027;font-weight:700;cursor:pointer}button:disabled{opacity:.55;cursor:wait}.hint{font-size:12px;color:var(--muted);text-align:center}@media(max-width:560px){.shell{padding:18px 12px}.badge{display:none}.message{max-width:94%}button{padding:0 14px}}</style></head><body><main class="shell"><header><div><h1>CardStack Chat</h1><p>Disk-paged character model · cards load only when routed</p></div><span class="badge">LOCAL ONLY</span></header><section class="chat" id="chat"><div class="empty" id="empty"><h2>Start a conversation</h2><p>This model produces one next character at a time. Quality depends on the text used to train its cards.</p></div></section><form id="form"><textarea id="prompt" aria-label="Message" placeholder="Type a message" required></textarea><button id="send" type="submit">Send</button></form><div class="hint">Enter to send · Shift+Enter for a new line</div></main><script>const chat=document.querySelector('#chat'),empty=document.querySelector('#empty'),form=document.querySelector('#form'),prompt=document.querySelector('#prompt'),send=document.querySelector('#send');function add(kind,text){empty?.remove();const el=document.createElement('div');el.className='message '+kind;el.textContent=text;chat.append(el);el.scrollIntoView({block:'end',behavior:'smooth'});return el}form.addEventListener('submit',async e=>{e.preventDefault();const text=prompt.value.trim();if(!text)return;add('user',text);prompt.value='';send.disabled=true;const reply=add('assistant','Thinking…');try{const r=await fetch('/api/generate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt:text,characters:180})});const body=await r.json();reply.textContent=body.text||body.error||'No response.'}catch(err){reply.textContent='Could not reach the local model: '+err.message}finally{send.disabled=false;prompt.focus()}});prompt.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();form.requestSubmit()}});prompt.focus();</script></body></html>"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def serve_chat(model_directory: str | Path, host: str = "127.0.0.1", port: int = 8765, open_browser: bool = True) -> None:
|
|
18
|
+
"""Serve a local-only chat box for a disk-paged CardStack model."""
|
|
19
|
+
model = CardStackLM.load(model_directory)
|
|
20
|
+
model_lock = threading.Lock()
|
|
21
|
+
|
|
22
|
+
class Handler(BaseHTTPRequestHandler):
|
|
23
|
+
def _send(self, status: int, content_type: str, body: str) -> None:
|
|
24
|
+
encoded = body.encode("utf-8")
|
|
25
|
+
self.send_response(status)
|
|
26
|
+
self.send_header("Content-Type", f"{content_type}; charset=utf-8")
|
|
27
|
+
self.send_header("Content-Length", str(len(encoded)))
|
|
28
|
+
self.end_headers()
|
|
29
|
+
self.wfile.write(encoded)
|
|
30
|
+
|
|
31
|
+
def do_GET(self) -> None:
|
|
32
|
+
if self.path != "/":
|
|
33
|
+
self._send(HTTPStatus.NOT_FOUND, "text/plain", "Not found")
|
|
34
|
+
return
|
|
35
|
+
self._send(HTTPStatus.OK, "text/html", PAGE)
|
|
36
|
+
|
|
37
|
+
def do_POST(self) -> None:
|
|
38
|
+
if self.path != "/api/generate":
|
|
39
|
+
self._send(HTTPStatus.NOT_FOUND, "application/json", '{"error":"Not found"}')
|
|
40
|
+
return
|
|
41
|
+
try:
|
|
42
|
+
size = int(self.headers.get("Content-Length", "0"))
|
|
43
|
+
request = json.loads(self.rfile.read(min(size, 20_000)))
|
|
44
|
+
prompt = str(request.get("prompt", "")).strip()
|
|
45
|
+
characters = max(1, min(int(request.get("characters", 180)), 500))
|
|
46
|
+
if not prompt:
|
|
47
|
+
raise ValueError("Write a message first.")
|
|
48
|
+
with model_lock:
|
|
49
|
+
text = model.generate(prompt, new_characters=characters)
|
|
50
|
+
self._send(HTTPStatus.OK, "application/json", json.dumps({"text": text}))
|
|
51
|
+
except Exception as error:
|
|
52
|
+
self._send(HTTPStatus.BAD_REQUEST, "application/json", json.dumps({"error": str(error)}))
|
|
53
|
+
|
|
54
|
+
def log_message(self, *_args) -> None:
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
server = ThreadingHTTPServer((host, port), Handler)
|
|
58
|
+
address = f"http://{host}:{port}"
|
|
59
|
+
if open_browser:
|
|
60
|
+
webbrowser.open(address)
|
|
61
|
+
print(f"CardStack Chat is running at {address}. Press Ctrl+C to stop.")
|
|
62
|
+
try:
|
|
63
|
+
server.serve_forever()
|
|
64
|
+
except KeyboardInterrupt:
|
|
65
|
+
pass
|
|
66
|
+
finally:
|
|
67
|
+
server.server_close()
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
from .cardstack import CardStackConfig, CardStackLM, train_next_character
|
|
13
|
+
from .chat_server import serve_chat
|
|
14
|
+
|
|
15
|
+
def main() -> None:
|
|
16
|
+
parser = argparse.ArgumentParser(description="Static application-binary inspector. Never executes targets.")
|
|
17
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
18
|
+
inspect_cmd = commands.add_parser("inspect", help="inspect a binary")
|
|
19
|
+
inspect_cmd.add_argument("file")
|
|
20
|
+
inspect_cmd.add_argument("--strings", type=int, default=200)
|
|
21
|
+
extract_cmd = commands.add_parser("extract", help="extract recognizable embedded files")
|
|
22
|
+
extract_cmd.add_argument("file")
|
|
23
|
+
extract_cmd.add_argument("--output", required=True)
|
|
24
|
+
parts_cmd = commands.add_parser("binary-extract", help="export PE headers and raw sections")
|
|
25
|
+
parts_cmd.add_argument("file")
|
|
26
|
+
parts_cmd.add_argument("--output", required=True)
|
|
27
|
+
tokenize_cmd = commands.add_parser("tokenize", help="train a tokenizer and encode a text/source file")
|
|
28
|
+
tokenize_cmd.add_argument("file")
|
|
29
|
+
tokenize_cmd.add_argument("--vocab", default="tokenizer.json")
|
|
30
|
+
tokenize_cmd.add_argument("--max-vocab", type=int, default=20_000)
|
|
31
|
+
preset_cmd = commands.add_parser("model-template", help="print a PyTorch model configuration preset")
|
|
32
|
+
preset_cmd.add_argument("name", choices=sorted(PRESETS))
|
|
33
|
+
card_create = commands.add_parser("card-create", help="create a disk-paged character model")
|
|
34
|
+
card_create.add_argument("--output", required=True)
|
|
35
|
+
card_create.add_argument("--width", type=int, default=128)
|
|
36
|
+
card_create.add_argument("--layers", type=int, default=4)
|
|
37
|
+
card_create.add_argument("--cards", type=int, default=8)
|
|
38
|
+
card_create.add_argument("--active-cards", type=int, default=2)
|
|
39
|
+
card_create.add_argument("--cache-cards", type=int, default=4)
|
|
40
|
+
card_train = commands.add_parser("card-train", help="train a CardStack model from text")
|
|
41
|
+
card_train.add_argument("model")
|
|
42
|
+
card_train.add_argument("text_file")
|
|
43
|
+
card_train.add_argument("--steps", type=int, default=500)
|
|
44
|
+
card_generate = commands.add_parser("card-generate", help="generate one character at a time from disk-paged cards")
|
|
45
|
+
card_generate.add_argument("model")
|
|
46
|
+
card_generate.add_argument("prompt")
|
|
47
|
+
card_generate.add_argument("--characters", type=int, default=80)
|
|
48
|
+
chat_cmd = commands.add_parser("chat", help="open a local browser chat box for a trained CardStack model")
|
|
49
|
+
chat_cmd.add_argument("model")
|
|
50
|
+
chat_cmd.add_argument("--port", type=int, default=8765)
|
|
51
|
+
chat_cmd.add_argument("--no-browser", action="store_true")
|
|
52
|
+
args = parser.parse_args()
|
|
53
|
+
if args.command == "inspect":
|
|
54
|
+
result = inspect_file(args.file, args.strings)
|
|
55
|
+
elif args.command == "extract":
|
|
56
|
+
result = extract_embedded(args.file, args.output)
|
|
57
|
+
elif args.command == "binary-extract":
|
|
58
|
+
result = extract_binary_parts(args.file, args.output)
|
|
59
|
+
elif args.command == "tokenize":
|
|
60
|
+
text = Path(args.file).read_text(encoding="utf-8", errors="replace")
|
|
61
|
+
tokenizer = WordTokenizer().fit([text], max_vocab=args.max_vocab)
|
|
62
|
+
tokenizer.save(args.vocab)
|
|
63
|
+
result = {"vocabulary_path": args.vocab, "vocabulary_size": len(tokenizer.vocabulary), "tokens": tokenizer.tokenize(text), "ids": tokenizer.encode(text)}
|
|
64
|
+
elif args.command == "card-create":
|
|
65
|
+
config = CardStackConfig(width=args.width, layers=args.layers, cards_per_layer=args.cards, active_cards=args.active_cards, cache_cards=args.cache_cards)
|
|
66
|
+
CardStackLM(config).save(args.output)
|
|
67
|
+
result = {"model": args.output, "config": config.__dict__, "note": "Cards were saved in 4-bit files. Train before expecting useful output."}
|
|
68
|
+
elif args.command == "card-train":
|
|
69
|
+
model = CardStackLM.load_for_training(args.model)
|
|
70
|
+
losses = train_next_character(model, Path(args.text_file).read_text(encoding="utf-8", errors="replace"), steps=args.steps)
|
|
71
|
+
model.save(args.model)
|
|
72
|
+
result = {"model": args.model, "steps": args.steps, "loss_samples": losses}
|
|
73
|
+
elif args.command == "card-generate":
|
|
74
|
+
result = {"text": CardStackLM.load(args.model).generate(args.prompt, new_characters=args.characters)}
|
|
75
|
+
elif args.command == "chat":
|
|
76
|
+
serve_chat(args.model, port=args.port, open_browser=not args.no_browser)
|
|
77
|
+
return
|
|
78
|
+
else:
|
|
79
|
+
result = PRESETS[args.name]
|
|
80
|
+
print(json.dumps(result, indent=2))
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
main()
|
|
@@ -1,65 +0,0 @@
|
|
|
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.
|
|
@@ -1,50 +0,0 @@
|
|
|
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.
|
|
@@ -1,48 +0,0 @@
|
|
|
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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/tokenizer.py
RENAMED
|
File without changes
|
{appbinary_inspector-0.2.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/torch_builder.py
RENAMED
|
File without changes
|