mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
AI generated docs.
This commit is contained in:
+392
@@ -0,0 +1,392 @@
|
||||
# Tree Protocol Specification
|
||||
|
||||
## Overview
|
||||
|
||||
The Tree Protocol is a lightweight, extensible message format designed for hierarchical message routing in a modular C2 (Command & Control) system. It provides RPC-like interactions, streaming support, event notifications, and peer-to-peer pivoting capabilities.
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Loose typing** - Actions and payloads are flexible JSON values, not strict enums
|
||||
2. **Namespacing** - Use dot notation for action categorization (e.g., `rpc.call`, `stream.data`)
|
||||
3. **Extensibility** - Add new capabilities without modifying core structure
|
||||
4. **Simplicity** - Minimal required fields, optional metadata for flexibility
|
||||
|
||||
## Message Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid-string",
|
||||
"source": "path/to/sender",
|
||||
"target": "path/to/recipient",
|
||||
"action": "action.name",
|
||||
"payload": {},
|
||||
"routing": {},
|
||||
"meta": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Definitions
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | string | No | Unique message identifier for correlation |
|
||||
| `source` | string/array | No | Origin path for responses |
|
||||
| `target` | string/array | No | Destination path for routing |
|
||||
| `action` | value | Yes | Operation to perform |
|
||||
| `payload` | any | No | Data for the action |
|
||||
| `routing` | object | No | P2P/pivoting metadata |
|
||||
| `meta` | object | No | Extensible metadata |
|
||||
|
||||
## Action System
|
||||
|
||||
Actions are loose values - any JSON that components can interpret:
|
||||
|
||||
```json
|
||||
"action": "query"
|
||||
"action": "rpc.call"
|
||||
"action": "stream.open"
|
||||
"action": {"name": "custom", "version": 1}
|
||||
```
|
||||
|
||||
### Common Action Categories
|
||||
|
||||
| Category | Prefix | Purpose |
|
||||
|----------|--------|---------|
|
||||
| Core | `query`, `create`, `delete`, `update` | CRUD operations |
|
||||
| RPC | `rpc.call`, `rpc.response` | Remote procedure calls |
|
||||
| Streams | `stream.open`, `stream.data`, `stream.close` | Bidirectional streams |
|
||||
| Events | `subscribe`, `event` | Event notifications |
|
||||
| Network | `connect`, `disconnect`, `forward` | Connection handling |
|
||||
|
||||
## Payload Formats
|
||||
|
||||
Payloads vary by action - components interpret them flexibly:
|
||||
|
||||
### RPC Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "rpc.call",
|
||||
"target": ["endpoints", "ep1", "components", "tcp-client"],
|
||||
"payload": {
|
||||
"method": "connect",
|
||||
"params": {"address": "127.0.0.1", "port": 443}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "rpc.response",
|
||||
"payload": {
|
||||
"success": true,
|
||||
"result": {"connected": true}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Stream Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "stream.open",
|
||||
"payload": {
|
||||
"channel": "stdio",
|
||||
"mode": "bidirectional"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "stream.data",
|
||||
"payload": {
|
||||
"channel": "stdout",
|
||||
"data": "SGVsbG8gd29ybGQ=", // base64 encoded
|
||||
"chunk": 0,
|
||||
"total": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "stream.close",
|
||||
"payload": {
|
||||
"channel": "stdio",
|
||||
"reason": "eof"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "subscribe",
|
||||
"target": ["endpoints", "ep1", "logs"],
|
||||
"payload": {
|
||||
"event": "new_entry",
|
||||
"callback": "components/event-handler"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "event",
|
||||
"source": ["endpoints", "ep1", "logs"],
|
||||
"payload": {
|
||||
"event": "new_entry",
|
||||
"data": "some log message"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### P2P Routing Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "forward",
|
||||
"target": ["peers", "peer-2", "endpoints", "ep2"],
|
||||
"routing": {
|
||||
"via": "peer-1",
|
||||
"hop": 1,
|
||||
"max_hops": 3
|
||||
},
|
||||
"payload": {...}
|
||||
}
|
||||
```
|
||||
|
||||
## Response Pattern
|
||||
|
||||
All responses use a generic format:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "req-123",
|
||||
"action": "response",
|
||||
"source": ["endpoints", "ep1"],
|
||||
"payload": {
|
||||
"success": true,
|
||||
"result": {"connected": true},
|
||||
"error": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "req-123",
|
||||
"action": "response",
|
||||
"payload": {
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": 404,
|
||||
"message": "not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Path Format
|
||||
|
||||
Paths can be represented in multiple ways:
|
||||
|
||||
```json
|
||||
"target": "components/tcp-client"
|
||||
"target": ["components", "tcp-client"]
|
||||
"target": ["endpoints", "ep1", "connections", "conn-1"]
|
||||
```
|
||||
|
||||
## Message Types (Optional Wrapper)
|
||||
|
||||
For transport-level distinction:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `req` | Request - expecting a response |
|
||||
| `resp` | Response - reply to a request |
|
||||
| `event` | Unsolicited notification |
|
||||
| `stream` | Stream data message |
|
||||
|
||||
Example with type wrapper:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "msg-123",
|
||||
"type": "req",
|
||||
"target": ["components", "tcp-client"],
|
||||
"action": "rpc.call",
|
||||
"payload": {"method": "connect", "params": {...}}
|
||||
}
|
||||
```
|
||||
|
||||
## Binary Framing (Optional)
|
||||
|
||||
For bandwidth-constrained links:
|
||||
|
||||
```
|
||||
[4 bytes: length][1 byte: flags][id (16 bytes, optional)][payload...]
|
||||
```
|
||||
|
||||
- Length: Big-endian u32
|
||||
- Flags: 0x01 = compressed, 0x02 = encrypted
|
||||
- ID: UUID v4 (optional, for correlation)
|
||||
|
||||
## Examples
|
||||
|
||||
### Full RPC Call
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "req-123",
|
||||
"source": "server",
|
||||
"target": ["endpoints", "ep1", "components", "tcp-client"],
|
||||
"action": "rpc.call",
|
||||
"payload": {
|
||||
"method": "connect",
|
||||
"params": {"address": "127.0.0.1", "port": 8080}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "resp-123",
|
||||
"source": ["endpoints", "ep1"],
|
||||
"target": "server",
|
||||
"action": "response",
|
||||
"payload": {
|
||||
"success": true,
|
||||
"result": {
|
||||
"connected": true,
|
||||
"local_addr": "192.168.1.100:12345",
|
||||
"remote_addr": "127.0.0.1:8080"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Log Subscription + Events
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "sub-1",
|
||||
"source": "server",
|
||||
"target": ["endpoints", "ep1", "logs"],
|
||||
"action": "subscribe",
|
||||
"payload": {
|
||||
"event": "new_entry",
|
||||
"callback": ["components", "log-forwarder"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "evt-1",
|
||||
"source": ["endpoints", "ep1", "logs"],
|
||||
"target": ["components", "log-forwarder"],
|
||||
"action": "event",
|
||||
"payload": {
|
||||
"event": "new_entry",
|
||||
"data": "2024-01-15 10:30:45 - Connection established"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### P2P Forward
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "req-456",
|
||||
"source": "server",
|
||||
"target": ["endpoints", "ep2", "components", "shell"],
|
||||
"action": "rpc.call",
|
||||
"routing": {
|
||||
"via": "peer-1",
|
||||
"hop": 1,
|
||||
"max_hops": 3
|
||||
},
|
||||
"payload": {
|
||||
"method": "execute",
|
||||
"params": {"command": "whoami"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Action Matching
|
||||
|
||||
```rust
|
||||
impl TreeMessage {
|
||||
/// Check if action matches a pattern
|
||||
pub fn action_is(&self, action: &str) -> bool {
|
||||
match &self.action {
|
||||
Value::String(s) => s == action || s.ends_with(&format!(".{}", action)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get method name from RPC payload
|
||||
pub fn get_method(&self) -> Option<String> {
|
||||
self.payload.get("method")
|
||||
.and_then(|m| m.as_str())
|
||||
.map(String::from)
|
||||
}
|
||||
|
||||
/// Get stream channel from payload
|
||||
pub fn get_channel(&self) -> Option<String> {
|
||||
self.payload.get("channel")
|
||||
.and_then(|c| c.as_str())
|
||||
.map(String::from)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Component Interpretation
|
||||
|
||||
Components define their own action handlers:
|
||||
|
||||
```rust
|
||||
impl TreeElement for TcpClient {
|
||||
fn send_message(&mut self, target: Value, message: Value) -> Value {
|
||||
match message.get("method").and_then(|m| m.as_str()) {
|
||||
Some("connect") => self.handle_connect(message),
|
||||
Some("send") => self.handle_send(message),
|
||||
Some("recv") => self.handle_recv(message),
|
||||
_ => json!({"error": "unknown method"}),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Comparison to Other Protocols
|
||||
|
||||
| Aspect | Tree Protocol | OpenC2 | OST-C2 | Mythic |
|
||||
|--------|--------------|--------|--------|--------|
|
||||
| Typing | Loose/JSON | Strict enum | Binary enum | JSON |
|
||||
| Actions | Namespaced strings | Fixed enum | Type/Code | Action strings |
|
||||
| RPC | Yes | No | Limited | Yes |
|
||||
| Streams | Yes | No | No | No |
|
||||
| Events | Yes | No | No | Yes |
|
||||
| P2P | Via paths | No | Yes | Via delegates |
|
||||
| Extensibility | High | Medium | Low | Medium |
|
||||
|
||||
## Extensibility Points
|
||||
|
||||
1. **New actions**: Add any namespaced string - no code changes needed
|
||||
2. **Payload structure**: Any JSON - components interpret as needed
|
||||
3. **Meta field**: Add transport, timing, or custom metadata
|
||||
4. **Routing field**: Add P2P-specific metadata as needed
|
||||
5. **Namespacing**: Use prefixes like `openc2.`, `custom.`, `vendor.` for compatibility
|
||||
|
||||
## Serialization
|
||||
|
||||
- Default: JSON (human-readable, debuggable)
|
||||
- Optional: CBOR (binary, compact)
|
||||
- Custom: Any format via `meta.serialization` hint
|
||||
Reference in New Issue
Block a user