diff --git a/Payload_Type/poseidon/poseidon/agent_code/CHANGELOG.MD b/Payload_Type/poseidon/poseidon/agent_code/CHANGELOG.MD index 9b6ebd2..e97fea9 100644 --- a/Payload_Type/poseidon/poseidon/agent_code/CHANGELOG.MD +++ b/Payload_Type/poseidon/poseidon/agent_code/CHANGELOG.MD @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 2.1.11 - 2024-11-15 + +### Changed + +- Refactored the egress profiles to use a custom .Sleep() function that can be interrupted + - this allows changing sleep times to take effect immediately instead of waiting another sleep interval +- Updated the checkAlive function to be more accurate + ## 2.1.10 - 2024-11-13 ### Changed diff --git a/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/dynamichttp.go b/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/dynamichttp.go index 554cc1d..13bbec3 100644 --- a/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/dynamichttp.go +++ b/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/dynamichttp.go @@ -72,11 +72,12 @@ type C2DynamicHTTP struct { ExchangingKeys bool ChunkSize int `json:"chunk_size"` // internally set pieces - Config C2DynamicHTTPC2Config `json:"config"` - Key string `json:"encryption_key"` - RsaPrivateKey *rsa.PrivateKey - ShouldStop bool - stoppedChannel chan bool + Config C2DynamicHTTPC2Config `json:"config"` + Key string `json:"encryption_key"` + RsaPrivateKey *rsa.PrivateKey + ShouldStop bool + stoppedChannel chan bool + interruptSleepChannel chan bool } // New creates a new DynamicHTTP C2 profile from the package's global variables and returns it @@ -99,10 +100,11 @@ func init() { os.Exit(1) } profile := C2DynamicHTTP{ - Key: initialConfig.AESPSK, - Killdate: killDateTime, - ShouldStop: true, - stoppedChannel: make(chan bool, 1), + Key: initialConfig.AESPSK, + Killdate: killDateTime, + ShouldStop: true, + stoppedChannel: make(chan bool, 1), + interruptSleepChannel: make(chan bool, 1), } // Convert sleep from string to integer @@ -157,7 +159,7 @@ func (c *C2DynamicHTTP) Start() { taskResp := structs.MythicMessageResponse{} if err := json.Unmarshal(resp, &taskResp); err != nil { utils.PrintDebug(fmt.Sprintf("Error unmarshal response to task response: %s", err.Error())) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } responses.HandleInboundMythicMessageFromEgressChannel <- taskResp @@ -165,7 +167,7 @@ func (c *C2DynamicHTTP) Start() { } else { utils.PrintDebug(fmt.Sprintf("Failed to marshal message: %v\n", err)) } - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() } } else { //fmt.Printf("Uh oh, failed to checkin\n") @@ -208,6 +210,13 @@ func (c *C2DynamicHTTP) UpdateConfig(parameter string, value string) { } } } +func (c *C2DynamicHTTP) Sleep() { + // wait for either sleep time duration or sleep interrupt + select { + case <-c.interruptSleepChannel: + case <-time.After(time.Second * time.Duration(c.GetSleepTime())): + } +} func (c *C2DynamicHTTP) GetSleepTime() int { if c.ShouldStop { return -1 @@ -236,6 +245,9 @@ func (c *C2DynamicHTTP) GetKillDate() time.Time { func (c *C2DynamicHTTP) SetSleepInterval(interval int) string { if interval >= 0 { c.Interval = interval + go func() { + c.interruptSleepChannel <- true + }() return fmt.Sprintf("Sleep interval updated to %ds\n", interval) } else { return fmt.Sprintf("Sleep interval not updated, %d is not >= 0", interval) @@ -245,6 +257,9 @@ func (c *C2DynamicHTTP) SetSleepInterval(interval int) string { func (c *C2DynamicHTTP) SetSleepJitter(jitter int) string { if jitter >= 0 && jitter <= 100 { c.Jitter = jitter + go func() { + c.interruptSleepChannel <- true + }() return fmt.Sprintf("Jitter updated to %d%% \n", jitter) } else { return fmt.Sprintf("Jitter not updated, %d is not between 0 and 100", jitter) @@ -281,7 +296,7 @@ func (c *C2DynamicHTTP) CheckIn() structs.CheckInMessageResponse { } checkin := CreateCheckinMessage() if raw, err := json.Marshal(checkin); err != nil { - time.Sleep(time.Duration(c.GetSleepTime())) + c.Sleep() continue } else { resp := c.SendMessage(raw) @@ -290,14 +305,14 @@ func (c *C2DynamicHTTP) CheckIn() structs.CheckInMessageResponse { response := structs.CheckInMessageResponse{} if err = json.Unmarshal(resp, &response); err != nil { utils.PrintDebug(fmt.Sprintf("Error in unmarshal:\n %s", err.Error())) - time.Sleep(time.Duration(c.GetSleepTime())) + c.Sleep() continue } if len(response.ID) != 0 { SetMythicID(response.ID) return response } else { - time.Sleep(time.Duration(c.GetSleepTime())) + c.Sleep() continue } } @@ -400,34 +415,34 @@ func (c *C2DynamicHTTP) SendMessage(sendData []byte) []byte { if err != nil { utils.PrintDebug(fmt.Sprintf("error client.Do: %v\n", err)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } if resp.StatusCode != 200 { resp.Body.Close() utils.PrintDebug(fmt.Sprintf("error resp.StatusCode: %v\n", resp.StatusCode)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } raw, err := c.GetDynamicMessageResponse(resp, configUsed) if err != nil { utils.PrintDebug(fmt.Sprintf("error getting message response: %v\n", err)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } raw, err = base64.StdEncoding.DecodeString(string(raw)) if err != nil { utils.PrintDebug(fmt.Sprintf("error base64.StdEncoding: %v\n", err)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } if len(raw) < 36 { utils.PrintDebug(fmt.Sprintf("error len(raw) < 36: %v\n", err)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } if len(c.Key) != 0 { @@ -437,7 +452,7 @@ func (c *C2DynamicHTTP) SendMessage(sendData []byte) []byte { // failed somehow in decryption utils.PrintDebug(fmt.Sprintf("error decrypt length wrong: %v\n", err)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } else { //fmt.Printf("decrypted response: %v\n%v\n", string(raw[:36]), string(enc_raw)) diff --git a/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/http.go b/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/http.go index 17fd830..c94bc54 100644 --- a/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/http.go +++ b/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/http.go @@ -49,21 +49,22 @@ type HTTPInitialConfig struct { } type C2HTTP struct { - BaseURL string `json:"BaseURL"` - PostURI string `json:"PostURI"` - ProxyURL string `json:"ProxyURL"` - ProxyUser string `json:"ProxyUser"` - ProxyPass string `json:"ProxyPass"` - ProxyBypass bool `json:"ProxyBypass"` - Interval int `json:"Interval"` - Jitter int `json:"Jitter"` - HeaderList map[string]string `json:"Headers"` - ExchangingKeys bool - Key string `json:"EncryptionKey"` - RsaPrivateKey *rsa.PrivateKey - Killdate time.Time `json:"KillDate"` - ShouldStop bool - stoppedChannel chan bool + BaseURL string `json:"BaseURL"` + PostURI string `json:"PostURI"` + ProxyURL string `json:"ProxyURL"` + ProxyUser string `json:"ProxyUser"` + ProxyPass string `json:"ProxyPass"` + ProxyBypass bool `json:"ProxyBypass"` + Interval int `json:"Interval"` + Jitter int `json:"Jitter"` + HeaderList map[string]string `json:"Headers"` + ExchangingKeys bool + Key string `json:"EncryptionKey"` + RsaPrivateKey *rsa.PrivateKey + Killdate time.Time `json:"KillDate"` + ShouldStop bool + stoppedChannel chan bool + interruptSleepChannel chan bool } // New creates a new HTTP C2 profile from the package's global variables and returns it @@ -112,14 +113,15 @@ func init() { os.Exit(1) } profile := C2HTTP{ - BaseURL: final_url, - PostURI: initialConfig.PostURI, - ProxyUser: initialConfig.ProxyUser, - ProxyPass: initialConfig.ProxyPass, - Key: initialConfig.AESPSK, - Killdate: killDateTime, - ShouldStop: true, - stoppedChannel: make(chan bool, 1), + BaseURL: final_url, + PostURI: initialConfig.PostURI, + ProxyUser: initialConfig.ProxyUser, + ProxyPass: initialConfig.ProxyPass, + Key: initialConfig.AESPSK, + Killdate: killDateTime, + ShouldStop: true, + stoppedChannel: make(chan bool, 1), + interruptSleepChannel: make(chan bool, 1), } // Convert sleep from string to integer @@ -153,7 +155,13 @@ func init() { RegisterAvailableC2Profile(&profile) } - +func (c *C2HTTP) Sleep() { + // wait for either sleep time duration or sleep interrupt + select { + case <-c.interruptSleepChannel: + case <-time.After(time.Second * time.Duration(c.GetSleepTime())): + } +} func (c *C2HTTP) Start() { // Checkin with Mythic via an egress channel // only try to start if we're in a stopped state @@ -188,7 +196,7 @@ func (c *C2HTTP) Start() { taskResp := structs.MythicMessageResponse{} if err := json.Unmarshal(resp, &taskResp); err != nil { utils.PrintDebug(fmt.Sprintf("Error unmarshal response to task response: %s", err.Error())) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } responses.HandleInboundMythicMessageFromEgressChannel <- taskResp @@ -196,7 +204,7 @@ func (c *C2HTTP) Start() { } else { utils.PrintDebug(fmt.Sprintf("Failed to marshal message: %v\n", err)) } - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() } } else { //fmt.Printf("Uh oh, failed to checkin\n") @@ -232,11 +240,17 @@ func (c *C2HTTP) UpdateConfig(parameter string, value string) { if err == nil { c.Interval = newInt } + go func() { + c.interruptSleepChannel <- true + }() case "Jitter": newInt, err := strconv.Atoi(value) if err == nil { c.Jitter = newInt } + go func() { + c.interruptSleepChannel <- true + }() case "Killdate": killDateString := fmt.Sprintf("%sT00:00:00.000Z", value) killDateTime, err := time.Parse("2006-01-02T15:04:05.000Z", killDateString) @@ -278,6 +292,9 @@ func (c *C2HTTP) GetSleepTime() int { func (c *C2HTTP) SetSleepInterval(interval int) string { if interval >= 0 { c.Interval = interval + go func() { + c.interruptSleepChannel <- true + }() return fmt.Sprintf("Sleep interval updated to %ds\n", interval) } else { return fmt.Sprintf("Sleep interval not updated, %d is not >= 0", interval) @@ -288,6 +305,9 @@ func (c *C2HTTP) SetSleepInterval(interval int) string { func (c *C2HTTP) SetSleepJitter(jitter int) string { if jitter >= 0 && jitter <= 100 { c.Jitter = jitter + go func() { + c.interruptSleepChannel <- true + }() return fmt.Sprintf("Jitter updated to %d%% \n", jitter) } else { return fmt.Sprintf("Jitter not updated, %d is not between 0 and 100", jitter) @@ -326,7 +346,7 @@ func (c *C2HTTP) CheckIn() structs.CheckInMessageResponse { } checkin := CreateCheckinMessage() if raw, err := json.Marshal(checkin); err != nil { - time.Sleep(time.Duration(c.GetSleepTime())) + c.Sleep() continue } else { resp := c.SendMessage(raw) @@ -335,7 +355,7 @@ func (c *C2HTTP) CheckIn() structs.CheckInMessageResponse { response := structs.CheckInMessageResponse{} if err = json.Unmarshal(resp, &response); err != nil { utils.PrintDebug(fmt.Sprintf("Error in unmarshal:\n %s", err.Error())) - time.Sleep(time.Duration(c.GetSleepTime())) + c.Sleep() continue } if len(response.ID) != 0 { @@ -343,7 +363,7 @@ func (c *C2HTTP) CheckIn() structs.CheckInMessageResponse { SetAllEncryptionKeys(c.Key) return response } else { - time.Sleep(time.Duration(c.GetSleepTime())) + c.Sleep() continue } } @@ -482,7 +502,7 @@ func (c *C2HTTP) SendMessage(sendData []byte) []byte { if err != nil { utils.PrintDebug(fmt.Sprintf("error client.Do: %v\n", err)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } if resp.StatusCode != 200 { @@ -492,7 +512,7 @@ func (c *C2HTTP) SendMessage(sendData []byte) []byte { utils.PrintDebug(fmt.Sprintf("error failed to close response body: %v\n", err)) } IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } body, err := io.ReadAll(resp.Body) @@ -503,7 +523,7 @@ func (c *C2HTTP) SendMessage(sendData []byte) []byte { utils.PrintDebug(fmt.Sprintf("error failed to close response body: %v\n", err)) } IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } err = resp.Body.Close() @@ -515,13 +535,13 @@ func (c *C2HTTP) SendMessage(sendData []byte) []byte { if err != nil { utils.PrintDebug(fmt.Sprintf("error base64.StdEncoding: %v\n", err)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } if len(raw) < 36 { utils.PrintDebug(fmt.Sprintf("error len(raw) < 36: %v\n", err)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } if len(c.Key) != 0 { @@ -531,7 +551,7 @@ func (c *C2HTTP) SendMessage(sendData []byte) []byte { // failed somehow in decryption utils.PrintDebug(fmt.Sprintf("error decrypt length wrong: %v\n", err)) IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } else { if i > 0 { diff --git a/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/httpx.go b/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/httpx.go index f9c80c8..558bc11 100644 --- a/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/httpx.go +++ b/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/httpx.go @@ -81,11 +81,12 @@ type C2HTTPx struct { ExchangingKeys bool ChunkSize int `json:"chunk_size"` // internally set pieces - Config AgentVariations `json:"config"` - Key string `json:"encryption_key"` - RsaPrivateKey *rsa.PrivateKey - ShouldStop bool - stoppedChannel chan bool + Config AgentVariations `json:"config"` + Key string `json:"encryption_key"` + RsaPrivateKey *rsa.PrivateKey + ShouldStop bool + stoppedChannel chan bool + interruptSleepChannel chan bool } // New creates a new DynamicHTTP C2 profile from the package's global variables and returns it @@ -108,14 +109,15 @@ func init() { os.Exit(1) } profile := C2HTTPx{ - Key: initialConfig.AESPSK, - Killdate: killDateTime, - CallbackDomains: initialConfig.CallbackDomains, - CurrentDomain: 0, - FailoverThreshold: initialConfig.FailoverThreshold, - DomainRotationMethod: initialConfig.DomainRotationMethod, - ShouldStop: true, - stoppedChannel: make(chan bool, 1), + Key: initialConfig.AESPSK, + Killdate: killDateTime, + CallbackDomains: initialConfig.CallbackDomains, + CurrentDomain: 0, + FailoverThreshold: initialConfig.FailoverThreshold, + DomainRotationMethod: initialConfig.DomainRotationMethod, + ShouldStop: true, + stoppedChannel: make(chan bool, 1), + interruptSleepChannel: make(chan bool, 1), } // set initial fail counts to be 0 CallbackDomainFailCounts := make([]int, len(initialConfig.CallbackDomains)) @@ -181,7 +183,7 @@ func (c *C2HTTPx) Start() { taskResp := structs.MythicMessageResponse{} if err := json.Unmarshal(resp, &taskResp); err != nil { utils.PrintDebug(fmt.Sprintf("Error unmarshal response to task response: %s", err.Error())) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } responses.HandleInboundMythicMessageFromEgressChannel <- taskResp @@ -189,7 +191,7 @@ func (c *C2HTTPx) Start() { } else { utils.PrintDebug(fmt.Sprintf("Failed to marshal message: %v\n", err)) } - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() } } else { //fmt.Printf("Uh oh, failed to checkin\n") @@ -197,6 +199,13 @@ func (c *C2HTTPx) Start() { } } +func (c *C2HTTPx) Sleep() { + // wait for either sleep time duration or sleep interrupt + select { + case <-c.interruptSleepChannel: + case <-time.After(time.Second * time.Duration(c.GetSleepTime())): + } +} func (c *C2HTTPx) Stop() { if c.ShouldStop { return @@ -215,11 +224,17 @@ func (c *C2HTTPx) UpdateConfig(parameter string, value string) { if err == nil { c.Interval = newInt } + go func() { + c.interruptSleepChannel <- true + }() case "jitter": newInt, err := strconv.Atoi(value) if err == nil { c.Jitter = newInt } + go func() { + c.interruptSleepChannel <- true + }() case "killdate": killDateString := fmt.Sprintf("%sT00:00:00.000Z", value) killDateTime, err := time.Parse("2006-01-02T15:04:05.000Z", killDateString) @@ -277,6 +292,9 @@ func (c *C2HTTPx) GetKillDate() time.Time { func (c *C2HTTPx) SetSleepInterval(interval int) string { if interval >= 0 { c.Interval = interval + go func() { + c.interruptSleepChannel <- true + }() return fmt.Sprintf("Sleep interval updated to %ds\n", interval) } else { return fmt.Sprintf("Sleep interval not updated, %d is not >= 0", interval) @@ -286,6 +304,9 @@ func (c *C2HTTPx) SetSleepInterval(interval int) string { func (c *C2HTTPx) SetSleepJitter(jitter int) string { if jitter >= 0 && jitter <= 100 { c.Jitter = jitter + go func() { + c.interruptSleepChannel <- true + }() return fmt.Sprintf("Jitter updated to %d%% \n", jitter) } else { return fmt.Sprintf("Jitter not updated, %d is not between 0 and 100", jitter) @@ -322,7 +343,7 @@ func (c *C2HTTPx) CheckIn() structs.CheckInMessageResponse { } checkin := CreateCheckinMessage() if raw, err := json.Marshal(checkin); err != nil { - time.Sleep(time.Duration(c.GetSleepTime())) + c.Sleep() continue } else { resp := c.SendMessage(raw, false) @@ -331,14 +352,14 @@ func (c *C2HTTPx) CheckIn() structs.CheckInMessageResponse { response := structs.CheckInMessageResponse{} if err = json.Unmarshal(resp, &response); err != nil { utils.PrintDebug(fmt.Sprintf("Error in unmarshal:\n %s", err.Error())) - time.Sleep(time.Duration(c.GetSleepTime())) + c.Sleep() continue } if len(response.ID) != 0 { SetMythicID(response.ID) return response } else { - time.Sleep(time.Duration(c.GetSleepTime())) + c.Sleep() continue } } @@ -464,7 +485,7 @@ func (c *C2HTTPx) SendMessage(sendData []byte, isGetTaskingRequest bool) []byte utils.PrintDebug(fmt.Sprintf("error client.Do: %v\n", err)) c.increaseErrorCount() IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } if resp.StatusCode != 200 { @@ -472,7 +493,7 @@ func (c *C2HTTPx) SendMessage(sendData []byte, isGetTaskingRequest bool) []byte utils.PrintDebug(fmt.Sprintf("error resp.StatusCode: %v\n", resp.StatusCode)) c.increaseErrorCount() IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } raw, err := c.GetDynamicMessageResponse(resp, isGetTaskingRequest) @@ -480,7 +501,7 @@ func (c *C2HTTPx) SendMessage(sendData []byte, isGetTaskingRequest bool) []byte utils.PrintDebug(fmt.Sprintf("error getting message response: %v\n", err)) c.increaseErrorCount() IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } raw, err = base64.StdEncoding.DecodeString(string(raw)) @@ -488,14 +509,14 @@ func (c *C2HTTPx) SendMessage(sendData []byte, isGetTaskingRequest bool) []byte utils.PrintDebug(fmt.Sprintf("error base64.StdEncoding: %v\n", err)) c.increaseErrorCount() IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } if len(raw) < 36 { utils.PrintDebug(fmt.Sprintf("error len(raw) < 36: %v\n", err)) c.increaseErrorCount() IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } if len(c.Key) != 0 { @@ -506,7 +527,7 @@ func (c *C2HTTPx) SendMessage(sendData []byte, isGetTaskingRequest bool) []byte utils.PrintDebug(fmt.Sprintf("error decrypt length wrong: %v\n", err)) c.increaseErrorCount() IncrementFailedConnection(c.ProfileName()) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } else { //fmt.Printf("decrypted response: %v\n%v\n", string(raw[:36]), string(enc_raw)) diff --git a/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/websocket.go b/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/websocket.go index 656ba08..a22d6f7 100644 --- a/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/websocket.go +++ b/Payload_Type/poseidon/poseidon/agent_code/pkg/profiles/websocket.go @@ -49,25 +49,26 @@ const TaskingTypePush = "Push" const TaskingTypePoll = "Poll" type C2Websockets struct { - HostHeader string `json:"HostHeader"` - BaseURL string `json:"BaseURL"` - Interval int `json:"Interval"` - Jitter int `json:"Jitter"` - ExchangingKeys bool `json:"-"` - UserAgent string `json:"UserAgent"` - Key string `json:"EncryptionKey"` - RsaPrivateKey *rsa.PrivateKey - PollConn *websocket.Conn `json:"-"` - PushConn *websocket.Conn `json:"-"` - Lock sync.RWMutex `json:"-"` - ReconnectLock sync.RWMutex `json:"-"` - Endpoint string `json:"Websocket URL Endpoint"` - TaskingType string `json:"TaskingType"` - Killdate time.Time `json:"KillDate"` - FinishedStaging bool - ShouldStop bool - stoppedChannel chan bool - PushChannel chan structs.MythicMessage `json:"-"` + HostHeader string `json:"HostHeader"` + BaseURL string `json:"BaseURL"` + Interval int `json:"Interval"` + Jitter int `json:"Jitter"` + ExchangingKeys bool `json:"-"` + UserAgent string `json:"UserAgent"` + Key string `json:"EncryptionKey"` + RsaPrivateKey *rsa.PrivateKey + PollConn *websocket.Conn `json:"-"` + PushConn *websocket.Conn `json:"-"` + Lock sync.RWMutex `json:"-"` + ReconnectLock sync.RWMutex `json:"-"` + Endpoint string `json:"Websocket URL Endpoint"` + TaskingType string `json:"TaskingType"` + Killdate time.Time `json:"KillDate"` + FinishedStaging bool + ShouldStop bool + stoppedChannel chan bool + PushChannel chan structs.MythicMessage `json:"-"` + interruptSleepChannel chan bool } var websocketDialer = websocket.Dialer{ @@ -112,16 +113,17 @@ func init() { finalUrl = finalUrl + "/" } profile := C2Websockets{ - HostHeader: initialConfig.DomainFront, - BaseURL: finalUrl, - UserAgent: initialConfig.UserAgent, - Key: initialConfig.AESPSK, - Endpoint: initialConfig.Endpoint, - ShouldStop: true, - stoppedChannel: make(chan bool, 1), - PushChannel: make(chan structs.MythicMessage, 100), - PollConn: nil, - PushConn: nil, + HostHeader: initialConfig.DomainFront, + BaseURL: finalUrl, + UserAgent: initialConfig.UserAgent, + Key: initialConfig.AESPSK, + Endpoint: initialConfig.Endpoint, + ShouldStop: true, + stoppedChannel: make(chan bool, 1), + PushChannel: make(chan structs.MythicMessage, 100), + PollConn: nil, + PushConn: nil, + interruptSleepChannel: make(chan bool, 1), } // Convert sleep from string to integer @@ -156,6 +158,13 @@ func init() { RegisterAvailableC2Profile(&profile) go profile.CreateMessagesForEgressConnections() } +func (c *C2Websockets) Sleep() { + // wait for either sleep time duration or sleep interrupt + select { + case <-c.interruptSleepChannel: + case <-time.After(time.Second * time.Duration(c.GetSleepTime())): + } +} func (c *C2Websockets) CheckForKillDate() { for true { if c.ShouldStop || c.TaskingType == TaskingTypePoll { @@ -199,7 +208,7 @@ func (c *C2Websockets) Start() { SetAllEncryptionKeys(c.Key) break } else { - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } } @@ -220,13 +229,13 @@ func (c *C2Websockets) Start() { err := json.Unmarshal(resp, &taskResp) if err != nil { utils.PrintDebug(fmt.Sprintf("Error unmarshal response to task response: %s", err.Error())) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } // async handle the response back responses.HandleInboundMythicMessageFromEgressChannel <- taskResp } - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() } } else { go c.CheckForKillDate() @@ -270,11 +279,17 @@ func (c *C2Websockets) UpdateConfig(parameter string, value string) { if err == nil { c.Interval = newInt } + go func() { + c.interruptSleepChannel <- true + }() case "Jitter": newInt, err := strconv.Atoi(value) if err == nil { c.Jitter = newInt } + go func() { + c.interruptSleepChannel <- true + }() case "UserAgent": c.UserAgent = value changingConnectionParameter = true @@ -372,6 +387,9 @@ func (c *C2Websockets) SetSleepInterval(interval int) string { } if interval >= 0 { c.Interval = interval + go func() { + c.interruptSleepChannel <- true + }() return fmt.Sprintf("Sleep interval updated to %ds\n", interval) } else { return fmt.Sprintf("Sleep interval not updated, %d is not >= 0", interval) @@ -384,6 +402,9 @@ func (c *C2Websockets) SetSleepJitter(jitter int) string { } if jitter >= 0 && jitter <= 100 { c.Jitter = jitter + go func() { + c.interruptSleepChannel <- true + }() return fmt.Sprintf("Jitter updated to %d%% \n", jitter) } else { return fmt.Sprintf("Jitter not updated, %d is not between 0 and 100", jitter) @@ -588,7 +609,7 @@ func (c *C2Websockets) reconnect() { if c.ShouldStop { return } - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() } IncrementFailedConnection(c.ProfileName()) continue @@ -672,7 +693,7 @@ func (c *C2Websockets) sendData(sendData []byte) []byte { return []byte{} } utils.PrintDebug(fmt.Sprintf("Error decoding base64 data: ", err.Error())) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } @@ -682,7 +703,7 @@ func (c *C2Websockets) sendData(sendData []byte) []byte { return []byte{} } utils.PrintDebug(fmt.Sprintf("length of data < 36")) - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } @@ -697,7 +718,7 @@ func (c *C2Websockets) sendData(sendData []byte) []byte { utils.PrintDebug(fmt.Sprintf("got c.ShouldStop || c.TaskingType change in Polling sendData\n")) return []byte{} } - time.Sleep(time.Duration(c.GetSleepTime()) * time.Second) + c.Sleep() continue } } diff --git a/Payload_Type/poseidon/poseidon/agent_code/pkg/utils/structs/definitions.go b/Payload_Type/poseidon/poseidon/agent_code/pkg/utils/structs/definitions.go index f8d826d..9d11925 100644 --- a/Payload_Type/poseidon/poseidon/agent_code/pkg/utils/structs/definitions.go +++ b/Payload_Type/poseidon/poseidon/agent_code/pkg/utils/structs/definitions.go @@ -28,6 +28,8 @@ type Profile interface { GetSleepJitter() int // GetSleepTime returns the number of seconds to sleep before making another request using interval and jitter GetSleepTime() int + // Sleep performs a sleep with an optional timeout parameter + Sleep() // GetKillDate returns the kill date for the profile GetKillDate() time.Time // SetEncryptionKey to synchronize all c2 profiles once one has finished staging @@ -276,16 +278,17 @@ type FilePermission struct { Group string `json:"group,omitempty"` } type FileBrowser struct { - Files []FileData `json:"files"` - IsFile bool `json:"is_file"` - Permissions FilePermission `json:"permissions"` - Filename string `json:"name"` - ParentPath string `json:"parent_path"` - Success bool `json:"success"` - FileSize int64 `json:"size"` - LastModified int64 `json:"modify_time"` - LastAccess int64 `json:"access_time"` - UpdateDeleted bool `json:"update_deleted"` + Files []FileData `json:"files"` + IsFile bool `json:"is_file"` + Permissions FilePermission `json:"permissions"` + Filename string `json:"name"` + ParentPath string `json:"parent_path"` + Success bool `json:"success"` + FileSize int64 `json:"size"` + LastModified int64 `json:"modify_time"` + LastAccess int64 `json:"access_time"` + UpdateDeleted bool `json:"update_deleted"` + SetAsUserOutput bool `json:"set_as_user_output,omitempty"` } type FileData struct { diff --git a/Payload_Type/poseidon/poseidon/agentfunctions/builder.go b/Payload_Type/poseidon/poseidon/agentfunctions/builder.go index 1374c2f..d1e2a98 100644 --- a/Payload_Type/poseidon/poseidon/agentfunctions/builder.go +++ b/Payload_Type/poseidon/poseidon/agentfunctions/builder.go @@ -20,7 +20,7 @@ import ( "time" ) -const version = "2.1.10" +const version = "2.1.11" type sleepInfoStruct struct { Interval int `json:"interval"` @@ -144,19 +144,14 @@ var payloadDefinition = agentstructs.PayloadType{ atLeastOneCallbackWithinRange = true continue } - minAdd := sleepInfo[activeC2].Interval maxAdd := sleepInfo[activeC2].Interval if sleepInfo[activeC2].Jitter > 0 { - // minimum would be sleep_interval - (sleep_jitter % of sleep_interval) - minAdd = minAdd - ((sleepInfo[activeC2].Jitter / 100) * (sleepInfo[activeC2].Interval)) // maximum would be sleep_interval + (sleep_jitter % of sleep_interval) maxAdd = maxAdd + ((sleepInfo[activeC2].Jitter / 100) * (sleepInfo[activeC2].Interval)) } maxAdd *= 2 // double the high end in case we're on a close boundary - earliest := callback.LastCheckin.Add(time.Duration(minAdd) * time.Second) latest := callback.LastCheckin.Add(time.Duration(maxAdd) * time.Second) - - if callback.LastCheckin.After(earliest) && callback.LastCheckin.Before(latest) { + if time.Now().UTC().Before(latest) { atLeastOneCallbackWithinRange = true } } diff --git a/agent_capabilities.json b/agent_capabilities.json index 86fdcd9..dbe8a78 100644 --- a/agent_capabilities.json +++ b/agent_capabilities.json @@ -10,6 +10,6 @@ "architectures": ["x86_64", "arm_64"], "c2": ["http", "websocket", "dynamichttp", "poseidon_tcp"], "mythic_version": "3.3.0", - "agent_version": "2.1.10", + "agent_version": "2.1.11", "supported_wrappers": [] } \ No newline at end of file