behave-shell 0.1.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.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: behave-shell
3
+ Version: 0.1.0
4
+ Summary: BEHAVE-SHELL — shell-session behavioral observation registry, layered on behave-core
5
+ Author: ANTI
6
+ License: GPL-3.0-or-later
7
+ Project-URL: Source, https://git.resacachile.cl/anti/BEHAVE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: pydantic>=2.6
10
+ Requires-Dist: behave-core>=0.1.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8; extra == "dev"
13
+ Requires-Dist: pytest-cov; extra == "dev"
14
+ Requires-Dist: ruff; extra == "dev"
@@ -0,0 +1,296 @@
1
+ <!-- SPDX-License-Identifier: CC-BY-SA-4.0 -->
2
+ # behave-shell
3
+
4
+ [← repo](../README.md)
5
+
6
+ Shell-session behavioral observation registry. Defines what can be observed
7
+ about an operator through their terminal interaction — typing mechanics, cognitive
8
+ style, operational patterns, infrastructure fingerprints, and cultural timing signals.
9
+
10
+ BEHAVE-SHELL does not read command content. It measures *how* someone operates a
11
+ terminal, not *what* they type. The observations are categorical labels, numeric
12
+ aggregates, and cryptographic hashes — never raw keystrokes or command text.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install -e ../core/ -e .
18
+ # development (pytest + ruff):
19
+ pip install -e ../core/ -e ".[dev]"
20
+ ```
21
+
22
+ ## Quickstart
23
+
24
+ ```python
25
+ from behave_shell.spec import Observation, Window, TOPIC_PREFIX, event_topic_for
26
+
27
+ obs = Observation(
28
+ primitive="motor.keystroke_cadence",
29
+ value="bursty",
30
+ confidence=0.87,
31
+ window=Window(start_ts=1714000000.0, end_ts=1714003600.0),
32
+ source="behave/shell-sensor/timing.py",
33
+ )
34
+ # Serialize to an event bus topic + payload:
35
+ topic = event_topic_for("motor.keystroke_cadence")
36
+ # → "attacker.observation.shell.motor.keystroke_cadence"
37
+ ```
38
+
39
+ ## Public API (`behave_shell.spec`)
40
+
41
+ | Symbol | Description |
42
+ |---|---|
43
+ | `Observation` | Registry-aware subclass of `behave_core.spec.Observation`. Validates `primitive` against `PRIMITIVE_REGISTRY` and `value` against the primitive's type spec. |
44
+ | `Window` | Re-exported from `behave_core` — measurement time window. |
45
+ | `ObservationValue` | Re-exported union type for valid value shapes. |
46
+ | `PRIMITIVE_REGISTRY` | `dict[str, ValueTypeSpec]` — the full primitive catalog (76 entries). |
47
+ | `ValueKind` | Enum: `CATEGORICAL`, `NUMERIC`, `HASH`, `ARRAY`, `FREE_STRING`, `BOOL`. |
48
+ | `ValueTypeSpec` | Pydantic model holding a primitive's kind, allowed values, bounds, and notes. |
49
+ | `is_known(primitive)` | `bool` — whether a primitive path is registered. |
50
+ | `get(primitive)` | Returns the `ValueTypeSpec` for a primitive; raises `KeyError` if unknown. |
51
+ | `TOPIC_PREFIX` | `"attacker.observation.shell"` |
52
+ | `event_topic_for(primitive)` | Returns the full event bus topic string. |
53
+ | `to_event_payload(obs)` | Serializes an `Observation` to a bus-ready `dict`. |
54
+ | `from_event_payload(payload)` | Reconstructs an `Observation` from a bus payload. |
55
+
56
+ ## Primitives
57
+
58
+ 76 primitives across 9 categories. Each observation captures one measured value
59
+ for one primitive over one time window. A behavioral profile is built by collecting
60
+ many observations across many sessions.
61
+
62
+ ---
63
+
64
+ ### `motor.*` — Physical typing mechanics (9 primitives)
65
+
66
+ Motor primitives capture the physical mechanics of keyboard interaction: rhythm,
67
+ precision, and habitual movements that are hard to fake and stable across sessions
68
+ even when operators change tools or objectives. These are the closest BEHAVE comes
69
+ to biometrics — they exploit the fact that typing style is unconscious and consistent.
70
+
71
+ | Primitive | Kind | Description |
72
+ |---|---|---|
73
+ | `motor.keystroke_cadence` | categorical | Overall rhythm of key input. `steady` = metronomic confident typist. `bursty` = fast bursts with thinking pauses. `hunt_and_peck` = search-first-type. `machine` = mechanically regular, suggesting scripted input. |
74
+ | `motor.motor_stability` | categorical | Consistency of key hold/flight times. `steady` = low variance. `variable` = high variance (cognitive load or unfamiliar keyboard). `tremor` = rhythmic instability distinct from load-induced variance. |
75
+ | `motor.error_correction` | categorical | Response to typing mistakes. `immediate` = backspace within ~1s (automatic monitoring). `deferred` = corrects after reading output. `absent` = proceeds despite errors (scripted behavior). `route_around` = uses history or rewrites rather than backspacing. |
76
+ | `motor.command_chunking` | categorical | Flow of command composition. `fluent` = typed in one pass from memory. `fragmented` = chunks with mid-command pauses (composing while typing). `single_command` = one complete command at a time, no inline pipelines. |
77
+ | `motor.paste_burst_rate` | categorical | Frequency of large clipboard-paste events relative to typed input. `habitual` = primarily works by pasting pre-prepared blocks. |
78
+ | `motor.input_modality` | categorical | Dominant input mode. `typed` = character-by-character. `pasted` = pre-prepared blocks. `mixed` = both substantially. |
79
+ | `motor.shell_mastery.tab_completion` | categorical | Tab completion usage. `habitual` = operator relies on it constantly (inferred from short pause then rapid continuation). Strong indicator of shell familiarity. |
80
+ | `motor.shell_mastery.shortcut_usage` | categorical | Use of shell shortcuts (Ctrl+R, Ctrl+A/E, Ctrl+L, Alt+.). `heavy` = deep shell muscle memory. |
81
+ | `motor.shell_mastery.pipe_chaining_depth` | categorical | Maximum pipeline depth (cmd \| cmd \| cmd). `shallow` = 0-1 pipes. `deep` = 4+. Reflects tool-composition fluency. |
82
+
83
+ ---
84
+
85
+ ### `cognitive.*` — Decision-making and cognition (11 primitives)
86
+
87
+ Cognitive primitives capture how the operator thinks: their planning style, how they
88
+ respond to uncertainty and failure, and whether their timing patterns are consistent
89
+ with a human, a script, or an LLM agent. These are among the most attribution-relevant
90
+ primitives — they're stable per-operator and hard to sustain as deliberate deception.
91
+
92
+ | Primitive | Kind | Description |
93
+ |---|---|---|
94
+ | `cognitive.cognitive_load` | categorical | Inferred mental workload from timing, error rate, and inter-command variance. `high` = long pauses, frequent error-retry cycles, fragmented chunking. Composite feature for downstream attribution. |
95
+ | `cognitive.exploration_style` | categorical | Navigation style in unfamiliar environments. `methodical` = systematic enumeration (ls→cat→id→uname). `chaotic` = non-sequential jumps. `targeted` = straight to objective without exploring. |
96
+ | `cognitive.planning_depth` | categorical | Whether the operator works from a pre-formed plan. `deep` = visible logical sequence (recon→pivot→exfil). `shallow` = opportunistic. `reactive` = responds only to errors. |
97
+ | `cognitive.tool_vocabulary` | categorical | Breadth of tools used. `narrow` = fixed small toolset. `broad` = reaches for the best tool per subtask. |
98
+ | `cognitive.inter_command_latency_class` | categorical | Time between commands. `instant` (<200ms), `typing_speed` (200ms-2s), `deliberate` (2s), `llm_lightweight` (2-8s, small model agent), `llm_heavyweight` (8-30s, reasoning-class agent), `long` (>30s, human-supervised LLM). |
99
+ | `cognitive.inter_command_consistency` | categorical | Dispersion of inter-command pauses. `metronomic` = LLM-pure. `variable` = human. `bimodal` = LLM-assisted human (LLM-paced bursts + human thinking gaps). |
100
+ | `cognitive.command_branch_diversity` | categorical | Content-based script vs. adaptive discriminator. `linear_playbook` = low first-token repetition (each step uses a different tool). `adaptive_branching` = high repetition of the same tool with varying arguments (operator following a thread). |
101
+ | `cognitive.feedback_loop_engagement` | categorical | Whether pace correlates with output volume. `closed_loop` = pause grows with preceding output (reading before continuing). `fire_and_forget` = paces independently of output (scripted or unread). Cuts across the LLM/human axis. |
102
+ | `cognitive.error_resilience.retry_tactic` | categorical | Response to command failure. `rerun` = identical retry. `modify` = adjusts before retrying. `switch` = tries a different tool. `abort` = gives up on objective. |
103
+ | `cognitive.error_resilience.frustration_typing` | categorical | Speed/error spike immediately after failure. `high` = sharp burst post-failure. Strong human indicator; absent in scripts. |
104
+ | `cognitive.error_resilience.fallback_to_man` | categorical | Whether the operator invokes `man`/`--help` when stuck. `present` signals unfamiliarity with the specific tool. |
105
+
106
+ ---
107
+
108
+ ### `temporal.*` — Session timing and lifecycle (7 primitives)
109
+
110
+ When and how long an operator works. These signals are stable per-campaign and
111
+ hard to fake consistently across many sessions, because they reflect biological and
112
+ social rhythms (sleep, work hours, habits) rather than conscious technical choices.
113
+
114
+ | Primitive | Kind | Description |
115
+ |---|---|---|
116
+ | `temporal.session_timing` | categorical | Hour-of-day distribution. `diurnal` = business-hours peaks. `nocturnal` = late-night peaks. `irregular` = no discernible daily pattern. Requires a known timezone from `cultural.*` to interpret. |
117
+ | `temporal.session_duration` | categorical | Typical session length. `short` <15min, `medium` 15-90min, `long` 90min-4hr, `marathon` >4hr. Stable per-operator characteristic. |
118
+ | `temporal.escalation_pattern` | categorical | Activity intensity across a session. `sustained` = constant rate. `bursty` = concentrated activity then silence (waiting for long-running processes). `erratic` = unpredictable spikes. |
119
+ | `temporal.persistence` | categorical | Cross-session return behavior. `hit_and_run` = few sessions then disappears. `return_visitor` = periodic return. `resident` = near-continuous presence. |
120
+ | `temporal.lifecycle_markers.landing_ritual` | categorical | Whether a recognizable start-of-session sequence is detected (whoami → id → uname → hostname → ip addr). `present` = fingerprinted checklist habit. |
121
+ | `temporal.lifecycle_markers.exit_behavior` | categorical | Session end pattern. `graceful` = explicit logout. `abrupt` = dropped connection. `cleanup` = deletes logs/tools before exiting — strongest opsec signal in this category. |
122
+ | `temporal.lifecycle_markers.idle_periodicity` | categorical | Whether in-session idle gaps (>30s) are statistically periodic or random. `periodic` = heartbeat-like — may indicate an LLM polling loop, an automated keepalive, or a human following a timed workflow. |
123
+
124
+ ---
125
+
126
+ ### `operational.*` — Mission and opsec (4 primitives)
127
+
128
+ Operational primitives are coarser inferences from command patterns — what the
129
+ operator is trying to accomplish and how carefully they're hiding their footprint.
130
+
131
+ | Primitive | Kind | Description |
132
+ |---|---|---|
133
+ | `operational.opsec_discipline` | categorical | Forensic footprint management. `careful` = history disabled, tools removed, proxy/VPN confirmed. `careless` = no precautions. `learning` = inconsistent and improving mid-campaign. |
134
+ | `operational.cleanup_behavior` | categorical | Artifact handling at session end. `thorough` = removes tools, temp files, bash history. `partial` = removes some but misses others. `none` = leaves everything. |
135
+ | `operational.objective` | categorical | Inferred mission from command patterns: `recon`, `exfil`, `persistence`, `lateral` (pivoting), `destructive`. |
136
+ | `operational.multi_actor_indicators` | categorical | Signs of multiple operators. `handoff_detected` = detectable style break mid-session. `team_coordinated` = multiple signatures interleaved or simultaneous. |
137
+
138
+ ---
139
+
140
+ ### `environmental.*` — Physical and software context (5 primitives)
141
+
142
+ Environmental primitives describe where the operator works from. Stable per-campaign;
143
+ often reveals national origin or infrastructure choices.
144
+
145
+ | Primitive | Kind | Description |
146
+ |---|---|---|
147
+ | `environmental.keyboard_layout` | categorical | Inferred layout from characteristic key-sequence errors. An AZERTY-trained typist on QWERTY makes specific substitutions (q↔a, z↔w, m→,) that are statistically distinguishable from random errors. Reliable when error volume is sufficient (>50 errors). |
148
+ | `environmental.locale` | free_string | BCP-47 tag (e.g. `en-US`, `pt-BR`). Inferred from layout, cultural timing, and command-line encoding artifacts. Free string — locale is not a closed enum. |
149
+ | `environmental.numpad_usage` | categorical | Numeric keypad use inferred from keycode patterns. `detected` signals a desktop keyboard. |
150
+ | `environmental.terminal_multiplexer` | categorical | Presence of tmux/screen, inferred from escape sequences (Ctrl+B / Ctrl+A prefixes) and window-switching patterns. |
151
+ | `environmental.shell_type` | categorical | Shell environment inferred from syntax (array syntax, quoting style, builtin names). `powershell`/`cmd.exe` immediately flags a Windows-native operator. |
152
+
153
+ ---
154
+
155
+ ### `cultural.*` — Social and biological rhythms (5 primitives)
156
+
157
+ Cultural primitives exploit the fact that human work patterns are shaped by local
158
+ time, religion, and social convention. These signals are hard to sustain as deliberate
159
+ deception across a long campaign because they reflect unconscious biological rhythms.
160
+
161
+ | Primitive | Kind | Description |
162
+ |---|---|---|
163
+ | `cultural.meal_break_gaps` | categorical | Whether activity gaps align with regional meal times (`morning`, `midday`, `evening`, `late_night`). Requires a known timezone to interpret. |
164
+ | `cultural.periodic_micro_pauses` | categorical | Short rhythmic pauses of 5-15 min recurring at consistent intervals. May correspond to Salah prayer times (5 daily, spaced ~2-3hr), smoke breaks, or other cultural micro-rituals. `regular_intervals_detected` rejects the null hypothesis of random pauses at p<0.05. |
165
+ | `cultural.dst_behavior` | categorical | Whether the operator's active hours shift by 1 hour at DST transitions. `shifts_with_dst` = follows local civil time. `anchored_to_utc` = schedule is clock-fixed (automated infrastructure or deliberate counter-analysis). |
166
+ | `cultural.weekend_cadence` | categorical | Which two-day block is low-activity. `fri_sat` = Middle Eastern/Israeli pattern. `sat_sun` = Western/East Asian. Reliable national-origin signal across multiple weeks. |
167
+ | `cultural.holiday_gaps` | categorical | Whether multi-day inactivity gaps align with public holiday calendars. Requires a multi-session corpus spanning calendar events. |
168
+
169
+ ---
170
+
171
+ ### `emotional_valence.*` — Affective state (4 primitives)
172
+
173
+ Emotional valence primitives infer affective state from **typing dynamics** — pace,
174
+ error rate, and key-input aggression. BEHAVE-SHELL is content-blind; these
175
+ observations are derived entirely from timing and motor signals, not from what
176
+ was typed.
177
+
178
+ | Primitive | Kind | Description |
179
+ |---|---|---|
180
+ | `emotional_valence.valence` | categorical | Overall affective tone: `positive` (fluent, low-error), `neutral`, `negative` (error-heavy, erratic). Coarse aggregate; see `arousal` and `stress_response` for finer breakdown. |
181
+ | `emotional_valence.arousal` | categorical | Activation level. `low_calm` = slow deliberate pace. `high_agitated` = fast error-prone bursts. Orthogonal to valence — a calm script and a calm professional are both `low_calm`. |
182
+ | `emotional_valence.stress_response` | categorical | Whether high arousal is positive (`eustress_positive` = speed-up with low error rate, operator in the zone) or negative (`distress_negative` = speed-up with rising errors, panic). |
183
+ | `emotional_valence.frustration_venting` | categorical | Transient outburst signal: sudden speed spike or rapid backspace/delete bursts after command failures. Absent in scripted runs; strong human indicator. |
184
+
185
+ ---
186
+
187
+ ### `toolchain.*` — Infrastructure fingerprints (19 primitives)
188
+
189
+ Toolchain primitives fingerprint the software stack the operator uses, from TLS
190
+ handshake parameters to SSH key exchange preferences to C2 beaconing behavior.
191
+ Even fully encrypted traffic leaves structural fingerprints that identify specific
192
+ tools, libraries, and operator configurations.
193
+
194
+ #### `toolchain.tls.*` — TLS fingerprints (6)
195
+
196
+ TLS fingerprints identify the client and server stacks by their handshake parameters.
197
+ Each tool, library, and OS produces recognizable fingerprints even when the payload
198
+ is encrypted.
199
+
200
+ | Primitive | Kind | Description |
201
+ |---|---|---|
202
+ | `toolchain.tls.ja3_client` | hash | MD5 hash of TLS ClientHello parameters (SSLVersion, Ciphers, Extensions, EllipticCurves, EllipticCurvePointFormats). Salesforce, 2017. Each tool stack (curl, Metasploit, Cobalt Strike) produces a distinct hash. Searchable against databases like ja3er.com. |
203
+ | `toolchain.tls.ja3s_server` | hash | MD5 hash of TLS ServerHello (SSLVersion, Cipher, Extensions). Fingerprints the server stack — useful for identifying C2 servers by TLS response even when IPs rotate. |
204
+ | `toolchain.tls.ja4_client` | hash | FoxIO JA4 (2023): human-readable format (e.g. `t13d1516h2_8daaf6152771_e5627efa2ab1`) robust to TLS extension order randomization. Encodes TLS version, cipher count, extension count, ALPN, cipher hash, extension hash. Preferred over JA3 for new sensors. |
205
+ | `toolchain.tls.ja4s_server` | hash | JA4 server-side: fingerprints ServerHello using chosen cipher, extension list, and ALPN. More stable than JA3S when cipher ordering is randomized server-side. |
206
+ | `toolchain.tls.jarm_server` | hash | 62-char JARM hash (Salesforce, 2020). Actively probes the server with 10 crafted ClientHellos and hashes the responses. Reliably detects Cobalt Strike, Metasploit, and major C2 frameworks even with custom certificates. |
207
+ | `toolchain.tls.tls_cert_simhash` | hash | SHA-256 hex of the leaf certificate DER bytes. Tracks a specific certificate across infrastructure — useful for correlating C2 that reuses self-signed certs. |
208
+
209
+ #### `toolchain.transport.*` — Network stack fingerprints (3)
210
+
211
+ | Primitive | Kind | Description |
212
+ |---|---|---|
213
+ | `toolchain.transport.tcp_stack` | free_string | p0f OS label (e.g. `Linux 5.x`). Inferred from TCP header quirks (TTL, window size, options order, DF bit). Identifies the connecting OS before any application protocol is visible. |
214
+ | `toolchain.transport.h2_akamai_fingerprint` | free_string | HTTP/2 SETTINGS + priority + pseudo-header order hash. Different HTTP/2 libraries emit distinct SETTINGS combinations (curl vs. Python requests vs. Go net/http). `status: planned` |
215
+ | `toolchain.transport.quic_client` | free_string | QUIC initial packet fingerprint from transport parameters and connection ID length. `status: planned` |
216
+
217
+ #### `toolchain.ssh.*` — SSH fingerprints (4)
218
+
219
+ | Primitive | Kind | Description |
220
+ |---|---|---|
221
+ | `toolchain.ssh.hassh_client` | hash | MD5 hash of SSH client KEX parameters (kex_algorithms, encryption_algorithms, mac_algorithms, compression_algorithms). Salesforce, 2018. Each SSH library (OpenSSH, PuTTY, Paramiko, Impacket) produces a distinct HASSH. |
222
+ | `toolchain.ssh.hassh_server` | hash | MD5 hash of SSH server KEX parameters. Fingerprints the SSH daemon — detects honeypots, implants, or non-standard servers. `status: partial` |
223
+ | `toolchain.ssh.ssh_client_banner` | free_string | RFC 4253 protocol version string (e.g. `SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6`). Often unmodified even in offensive tooling. |
224
+ | `toolchain.ssh.kex_algorithm_order` | array[free_string] | Ordered KEX algorithm list from the SSH ClientHello. Different clients (OpenSSH, PuTTY, Impacket smbexec) advertise distinct orderings — secondary fingerprint beyond HASSH. |
225
+
226
+ #### `toolchain.http.*` — HTTP fingerprints (3)
227
+
228
+ | Primitive | Kind | Description |
229
+ |---|---|---|
230
+ | `toolchain.http.user_agent_tool_class` | categorical | Tool class from User-Agent and HTTP behavior. Known offensive tools use default or absent User-Agents. Values: `nmap_nse`, `sqlmap`, `nuclei`, `masscan`, `curl`, `metasploit`, `ffuf`, `gobuster`, `feroxbuster`, `nikto`, `wpscan`, `evilwinrm`, `impacket`, `unknown`. |
231
+ | `toolchain.http.header_order_fingerprint` | free_string | Hash of HTTP request header name order. Different libraries emit distinct sequences. `status: planned` |
232
+ | `toolchain.http.body_oddities` | array[free_string] | Anomalous body characteristics (e.g. `multipart_boundary_static`, `json_key_order_fixed`). `status: planned` |
233
+
234
+ #### `toolchain.c2.*` — C2 beaconing (6)
235
+
236
+ C2 primitives characterize implant beaconing behavior. Even fully encrypted C2
237
+ traffic leaves timing and structural fingerprints.
238
+
239
+ | Primitive | Kind | Description |
240
+ |---|---|---|
241
+ | `toolchain.c2.beacon_family` | categorical | C2 framework identified from traffic fingerprints: `cobalt_strike`, `sliver`, `havoc`, `mythic`, `merlin` *(planned)*, `brc4` *(planned)*, `nighthawk` *(planned)*, `unknown`. |
242
+ | `toolchain.c2.beacon_interval_ms` | numeric | Median IAT between callbacks, in milliseconds. Cobalt Strike default is 60000ms. Very short intervals (<1000ms) suggest an interactive shell, not a beacon. |
243
+ | `toolchain.c2.beacon_jitter_cv` | numeric | Coefficient of variation (std/mean) of beacon IATs. Higher CV = more randomized jitter. Cobalt Strike default jitter is 0% (CV≈0); operators who understand detection set it to 20-50%. |
244
+ | `toolchain.c2.sleep_skew` | categorical | Jitter type applied to sleep intervals. `none` = fixed (detectable). `gaussian` = normally distributed. `uniform` = flat random range. `walk` = random-walk drift. `status: partial` |
245
+ | `toolchain.c2.c2_callback_endpoint` | free_string | URL or `host:port` of the C2 callback endpoint. |
246
+ | `toolchain.c2.attack_software_id` | free_string | MITRE ATT&CK Software ID (e.g. `S0154` for Cobalt Strike). |
247
+
248
+ #### `toolchain.protocol_abuse.*` — Protocol abuse (6)
249
+
250
+ Non-standard or offensive use of standard protocols.
251
+
252
+ | Primitive | Kind | Description |
253
+ |---|---|---|
254
+ | `toolchain.protocol_abuse.dns_exfil_tool` | categorical | DNS tunneling tool. `iodine` = base32-encoded data in subdomains with TYPE NULL queries. `dnscat2` = TYPE TXT queries with specific entropy patterns. `custom_high_entropy` = tunneling-consistent but no known-tool match. `status: planned` |
255
+ | `toolchain.protocol_abuse.smb_dialect` | categorical | SMB dialect negotiated by the client. SMB1 in 2024+ is a strong indicator of legacy tooling or deliberate EternalBlue-era downgrade. `status: planned` |
256
+ | `toolchain.protocol_abuse.kerberos_etype_offer` | hash | Hash of the Kerberos AS-REQ etype list. Clients offering RC4-HMAC (etype 23) alongside modern etypes are candidates for Kerberoasting (Rubeus, Impacket GetUserSPNs). `status: planned` |
257
+ | `toolchain.protocol_abuse.ldap_bind_pattern` | categorical | LDAP bind mechanism. `simple` = cleartext (immediately suspicious). `sasl_gssapi` = Kerberos-backed (normal). `ntlm`, `ntlmssp_v1`, `responder_like` = NTLM and Responder-class MITM. `status: partial` |
258
+ | `toolchain.protocol_abuse.responder_signature` | free_string | Responder detection. Convention: `'false'` or `'true:llmnr'` / `'true:nbtns'` / `'true:mdns'`. Responder poisons LLMNR/NBNS/mDNS broadcasts to capture Net-NTLMv2 hashes. `status: planned` |
259
+ | `toolchain.protocol_abuse.mitm6_signature` | bool | Whether mitm6 activity is detected. mitm6 abuses IPv6 router advertisement on IPv4-only networks to hijack DNS and enable credential relay attacks. `status: planned` |
260
+
261
+ #### `toolchain.payload.*` — Payload analysis (3)
262
+
263
+ | Primitive | Kind | Description |
264
+ |---|---|---|
265
+ | `toolchain.payload.payload_simhash` | hash | 64-bit SimHash of the payload binary/shellcode. Preserves near-duplicate relationships: payloads that are 90% similar have low Hamming distance (<4 bits on 64-bit), enabling family clustering despite minor obfuscation. 16-char hex. |
266
+ | `toolchain.payload.payload_entropy_class` | categorical | Shannon entropy of payload bytes. `packed` >7.2 bits/byte (UPX, encrypted shellcode, base64-compressed). `high` 6.5-7.2 (unencrypted compiled code). `low` <5.5 (scripts, plaintext). `status: planned` |
267
+ | `toolchain.payload.loader_family` | categorical | Shellcode/loader family from structural signatures. `donut` = Donut framework (TheWover), converts .NET/PE to PIC shellcode. `sgn` = Shikata-Ga-Nai XOR encoder (Metasploit), recognizable feedback register pattern. `pe2sh` = PE-to-shellcode. `nimcrypt` = Nim-based loader with AES-encrypted payload. `status: planned` |
268
+
269
+ ---
270
+
271
+ ## Schema
272
+
273
+ Machine-readable JSON Schema for the observation envelope:
274
+ [`json/observation.schema.json`](json/observation.schema.json)
275
+
276
+ Regenerate after model changes:
277
+ ```bash
278
+ python scripts/generate_schema.py
279
+ ```
280
+
281
+ ## Tests
282
+
283
+ ```bash
284
+ pytest tests/
285
+ ```
286
+
287
+ ## Attribution recipes
288
+
289
+ [`attribution-recipes.md`](attribution-recipes.md) — out-of-scope reference document
290
+ describing how an external attribution engine might consume `attacker.observation.shell.*`
291
+ topics to build operator profiles. Not part of the BEHAVE spec.
292
+
293
+ ## License
294
+
295
+ Code and schemas: [GPL-3.0-or-later](../LICENSE)
296
+ Spec prose (this file, attribution-recipes.md): [CC-BY-SA-4.0](../LICENSE.docs)
File without changes
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """BEHAVE observation envelope and primitive registry — DECNET-aligned.
3
+
4
+ Public API:
5
+
6
+ from spec import Observation, Window, OBSERVATION_SCHEMA_VERSION
7
+ from spec import PRIMITIVE_REGISTRY, ValueKind, ValueTypeSpec
8
+ from spec import event_topic_for, to_event_payload, from_event_payload
9
+
10
+ See ``spec.envelope`` for the central PII-discipline statement that binds every
11
+ sensor emitting BEHAVE observations.
12
+ """
13
+
14
+ from .envelope import OBSERVATION_SCHEMA_VERSION, Observation, ObservationValue, Window
15
+ from .event_adapter import (
16
+ TOPIC_PREFIX,
17
+ event_topic_for,
18
+ from_event_payload,
19
+ to_event_payload,
20
+ )
21
+ from .primitives import PRIMITIVE_REGISTRY, ValueKind, ValueTypeSpec, get, is_known
22
+
23
+ __all__ = [
24
+ "OBSERVATION_SCHEMA_VERSION",
25
+ "Observation",
26
+ "ObservationValue",
27
+ "Window",
28
+ "PRIMITIVE_REGISTRY",
29
+ "ValueKind",
30
+ "ValueTypeSpec",
31
+ "is_known",
32
+ "get",
33
+ "TOPIC_PREFIX",
34
+ "event_topic_for",
35
+ "to_event_payload",
36
+ "from_event_payload",
37
+ ]
@@ -0,0 +1,57 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """BEHAVE-SHELL Observation envelope (registry-aware subclass).
3
+
4
+ The base envelope (`Observation`, `Window`, `OBSERVATION_SCHEMA_VERSION`,
5
+ `ObservationValue`) lives in `behave-core`; it enforces only structural
6
+ invariants (window ordering, confidence bounds, schema version, no extras).
7
+
8
+ This module subclasses the core `Observation` to add registry-aware validation
9
+ against `BEHAVE-SHELL`'s `PRIMITIVE_REGISTRY`. The subclass is exported under
10
+ the same name `Observation` so existing imports (``from spec.envelope import
11
+ Observation``) continue to resolve to the registry-validated form without
12
+ consumer changes.
13
+
14
+ PII discipline (lifted from DECNET ``attackers.py:268-285,308-311``) — see the
15
+ core envelope module docstring for the binding statement.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from pydantic import model_validator
21
+
22
+ from behave_core.spec.envelope import (
23
+ OBSERVATION_SCHEMA_VERSION,
24
+ ObservationValue,
25
+ Window,
26
+ )
27
+ from behave_core.spec.envelope import Observation as _BaseObservation
28
+
29
+ from .primitives import PRIMITIVE_REGISTRY
30
+
31
+
32
+ class Observation(_BaseObservation):
33
+ """Shell-domain Observation: base envelope + BEHAVE-SHELL registry check."""
34
+
35
+ @model_validator(mode="after")
36
+ def _validate_against_shell_registry(self) -> "Observation":
37
+ spec = PRIMITIVE_REGISTRY.get(self.primitive)
38
+ if spec is None:
39
+ raise ValueError(
40
+ f"unknown primitive {self.primitive!r}; "
41
+ f"add it to spec/primitives.py:PRIMITIVE_REGISTRY first"
42
+ )
43
+ try:
44
+ spec.validate_value(self.value)
45
+ except ValueError as exc:
46
+ raise ValueError(
47
+ f"value invalid for primitive {self.primitive!r}: {exc}"
48
+ ) from None
49
+ return self
50
+
51
+
52
+ __all__ = [
53
+ "OBSERVATION_SCHEMA_VERSION",
54
+ "Observation",
55
+ "ObservationValue",
56
+ "Window",
57
+ ]
@@ -0,0 +1,58 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """DECNET bus interop. Aligns BEHAVE Observation with DECNET Event payload shape.
3
+
4
+ DECNET's Event (decnet/bus/base.py:26) carries ``(topic, payload, type, v, ts, id)``.
5
+ A BEHAVE Observation maps onto that envelope as follows:
6
+
7
+ topic = "attacker.observation." + observation.primitive
8
+ payload = observation.model_dump(exclude={"id", "ts", "v"})
9
+ type = observation.primitive
10
+ v = observation.v
11
+ ts = observation.ts
12
+ id = observation.id
13
+
14
+ The publisher must set ``topic`` from the primitive when calling ``bus.publish()``;
15
+ DECNET's bus does not trust topic from the wire (anti-spoofing, base.py:60-76).
16
+
17
+ This module does NOT import DECNET. The adapter speaks dicts; consumers wire it
18
+ to their own bus.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Any
24
+
25
+ from .envelope import Observation
26
+
27
+ TOPIC_PREFIX: str = "attacker.observation"
28
+
29
+
30
+ def event_topic_for(primitive: str) -> str:
31
+ """Return the canonical DECNET bus topic for a BEHAVE primitive."""
32
+ return f"{TOPIC_PREFIX}.{primitive}"
33
+
34
+
35
+ def to_event_payload(obs: Observation) -> dict[str, Any]:
36
+ """Project an Observation into a dict suitable for ``Event.payload``.
37
+
38
+ Excludes ``id``, ``ts``, and ``v`` because those are carried at the Event
39
+ envelope level by DECNET, not in the payload body.
40
+ """
41
+ return obs.model_dump(exclude={"id", "ts", "v"}, mode="json")
42
+
43
+
44
+ def from_event_payload(primitive: str, payload: dict[str, Any]) -> Observation:
45
+ """Reconstruct an Observation from ``(topic-derived primitive, Event.payload)``.
46
+
47
+ The ``primitive`` argument is the trailing segment of the bus topic, NOT a
48
+ field read from the payload — relying on the wire-side ``primitive`` field
49
+ would let a misbehaving publisher spoof observations on topics they don't
50
+ actually publish to. This mirrors DECNET's ``Event.from_dict`` discipline
51
+ (decnet/bus/base.py:60-76).
52
+ """
53
+ if "primitive" in payload and payload["primitive"] != primitive:
54
+ raise ValueError(
55
+ f"payload.primitive ({payload['primitive']!r}) does not match "
56
+ f"topic-derived primitive ({primitive!r}); refusing to reconstruct"
57
+ )
58
+ return Observation.model_validate({**payload, "primitive": primitive})