Skip to content

Commit

Permalink
Fixed SOCKS issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Ne0nd0g committed Mar 21, 2024
1 parent 0d7d1fd commit d1a76ee
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 101 deletions.
22 changes: 11 additions & 11 deletions clients/mythic/mythic.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,17 @@ func (client *Client) Deconstruct(data []byte) (returnMessages []messages.Base,
err = fmt.Errorf("there was an error unmarshalling the JSON object to a mythic.ServerTaskResponse structure in the message handler:\n%s", err)
return
}
// SOCKS5
if len(msg.SOCKS) > 0 {
// There is SOCKS data to send to the SOCKS server
returnMessage, err = client.convertSocksToJobs(msg.SOCKS)
if err != nil {
cli.Message(cli.WARN, err.Error())
}
if len(returnMessage.Payload.([]jobs.Job)) > 0 {
returnMessages = append(returnMessages, returnMessage)
}
}
cli.Message(cli.DEBUG, fmt.Sprintf("post_response results from the server: %+v", msg))
for _, response := range msg.Responses {
if response.Error != "" {
Expand Down Expand Up @@ -978,7 +989,6 @@ func (client *Client) convertSocksToJobs(socks []Socks) (base messages.Base, err
// Load the data packet counter
i, ok := socksCounter.Load(id)
if !ok {
fmt.Println("******* ERROR ******")
err = fmt.Errorf("there was an error getting the SOCKS counter for the UUID: %s", id)
return
}
Expand Down Expand Up @@ -1054,15 +1064,6 @@ func (client *Client) convertTasksToJobs(tasks []Task) (messages.Base, error) {
}
job.Payload = payload
returnJobs = append(returnJobs, job)
case jobs.SOCKS:
// TODO: I don't think this code is ever used?
var payload jobs.Socks
err = json.Unmarshal([]byte(mythicJob.Payload), &payload)
if err != nil {
return base, fmt.Errorf("there was an error unmarshalling the Mythic job payload to a jobs.Socks structure:\n%s", err)
}
job.Payload = payload
returnJobs = append(returnJobs, job)
case 0:
// case 0 means that a job type was not added to the task from the Mythic server
// Commonly seen with SOCKS messages
Expand All @@ -1075,7 +1076,6 @@ func (client *Client) convertTasksToJobs(tasks []Task) (messages.Base, error) {
}
switch params.Action {
case "start", "stop":
// TODO Set agent sleep to 0 if start
// Send message back to Mythic that SOCKS has been started/stopped
job.Type = jobs.RESULT
job.Payload = jobs.Results{}
Expand Down
3 changes: 2 additions & 1 deletion clients/mythic/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ type Tasking struct {
type Tasks struct {
Action string `json:"action"`
Tasks []Task `json:"tasks"`
SOCKS []Socks `json:"socks"`
SOCKS []Socks `json:"socks,omitempty"`
}

// Task contains the task identifier, command, and parameters for the agent to execute
Expand Down Expand Up @@ -139,6 +139,7 @@ type ServerTaskResponse struct {
type ServerPostResponse struct {
Action string `json:"action"`
Responses []ServerTaskResponse `json:"responses"`
SOCKS []Socks `json:"socks,omitempty"`
}

// PostResponseFile is the structure used to send a list of messages from the agent to the server
Expand Down
8 changes: 8 additions & 0 deletions docs/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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.3.1 - 2024-03-21

### Fixed

- Resolved several SOCKS5 issues
- Updated Mythic client to handle `post_response` actions with `ServerPostResponse` structure to include SOCKS information
- Created a go routine and a channel just for sending SOCKS data in place of using the Jobs channel

## 2.3.0 - 2023-12-26

### Added
Expand Down
8 changes: 4 additions & 4 deletions services/job/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func NewJobService(agentID uuid.UUID) *Service {
return memoryService
}

// AddResult creates a Job Results structure and places it in the out going channel
// AddResult creates a Job Results structure and places it in the outgoing channel
func (s *Service) AddResult(agent uuid.UUID, stdOut, stdErr string) {
cli.Message(cli.DEBUG, fmt.Sprintf("services/job.AddResult(): entering into function with agent: %s, stdOut: %s, stdErr: %s", agent, stdOut, stdErr))
result := jobs.Results{
Expand Down Expand Up @@ -278,7 +278,7 @@ func (s *Service) Control(job jobs.Job) {
cli.Message(cli.DEBUG, fmt.Sprintf("services/job.Control(): leaving function with %+v", aInfo))
}

// Handle takes a list of jobs and places them into job channel if they are a valid type, so they can be executed
// Handle takes a list of jobs and places them into a job channel if they are a valid type, so they can be executed
func (s *Service) Handle(Jobs []jobs.Job) {
cli.Message(cli.DEBUG, fmt.Sprintf("services/job.Handle(): entering into function with %+v", Jobs))
for _, job := range Jobs {
Expand All @@ -305,7 +305,7 @@ func (s *Service) Handle(Jobs []jobs.Job) {
case jobs.RESULT:
out <- job
case jobs.SOCKS:
socks.Handler(job, &out, &in)
socks.Handler(job, &out)
default:
var result jobs.Results
result.Stderr = fmt.Sprintf("%s is not a valid job type", job.Type)
Expand Down Expand Up @@ -398,7 +398,7 @@ func execute() {
case jobs.SHELLCODE:
result = commands.ExecuteShellcode(job.Payload.(jobs.Shellcode))
case jobs.SOCKS:
socks.Handler(job, &out, &in)
socks.Handler(job, &out)
return
default:
result.Stderr = fmt.Sprintf("Invalid job type: %d", job.Type)
Expand Down
169 changes: 84 additions & 85 deletions socks/socks.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ var done = sync.Map{}

// Handler is the entry point for SOCKS connections.
// This function starts a SOCKS server and processes incoming SOCKS connections
func Handler(msg jobs.Job, jobsOut *chan jobs.Job, jobsIn *chan jobs.Job) {
func Handler(msg jobs.Job, jobsOut *chan jobs.Job) {
//fmt.Printf("socks.Handler(): Received SOCKS job ID: %s, Index: %d, Close: %t, Data Length: %d\n", msg.Payload.(jobs.Socks).ID, msg.Payload.(jobs.Socks).Index, msg.Payload.(jobs.Socks).Close, len(msg.Payload.(jobs.Socks).Data))
//defer fmt.Printf("\tsocks.Handler(): Exiting ID: %s, Index: %d, Close: %t, Data Length: %d\n", msg.Payload.(jobs.Socks).ID, msg.Payload.(jobs.Socks).Index, msg.Payload.(jobs.Socks).Close, len(msg.Payload.(jobs.Socks).Data))
job := msg.Payload.(jobs.Socks)

// See if the SOCKS server has already been created
if server == nil {
err := start()
err := newSOCKSServer()
if err != nil {
cli.Message(cli.WARN, err.Error())
return
Expand All @@ -61,102 +61,33 @@ func Handler(msg jobs.Job, jobsOut *chan jobs.Job, jobsIn *chan jobs.Job) {
_, ok := connections.Load(job.ID)
if !ok && !job.Close {
client, target := net.Pipe()
in := make(chan jobs.Socks, 100)
connection := Connection{
Job: msg,
In: client,
Out: target,
JobChan: jobsOut,
in: &in,
}
connections.Store(job.ID, &connection)
done.Store(job.ID, false)

// Start the go routine to send read data in and send it to the SOCKS server
go sendToSOCKSServer(job.ID)
go receiveFromSOCKSServer(job.ID)
go start(job.ID)
go listen(job.ID)
go send(job.ID)
}

conn, ok := connections.Load(job.ID)
if !ok {
cli.Message(cli.WARN, fmt.Sprintf("connection ID %s was not found", job.ID))
return
}

// Check to ensure the index is correct, if not, return it to the job channel to be processed again

if conn.(*Connection).Count != job.Index {
//fmt.Printf("Index mismatch, expected %d, got %d\n", conn.(*Connection).Count, job.Index)
*jobsIn <- msg
return
}

// If there is data, write it to the SOCKS server
// Send data, if any, before closing the connection
if len(job.Data) > 0 {
conn.(*Connection).Count++
// Write the received data to the agent side pipe
var buff bytes.Buffer
_, err := buff.Write(job.Data)
if err != nil {
cli.Message(cli.WARN, fmt.Sprintf("there was an error writing SOCKS data to the buffer: %s", err))
return
}

//fmt.Printf("Writing %d bytes to SOCKS target \n", len(job.Data))
n, err := conn.(*Connection).Out.Write(buff.Bytes())
if err != nil {
cli.Message(cli.WARN, fmt.Sprintf("there was an error writing data to the SOCKS %s OUTBOUND pipe: %s", job.ID, err))
return
}
//time.Sleep(40 * time.Millisecond)

cli.Message(cli.DEBUG, fmt.Sprintf("Wrote %d bytes to the SOCKS %s OUTBOUND pipe with error %s", n, job.ID, err))
}

// If the SOCKS client has sent io.EOF to close the connection
if job.Close {
// Mythic is sending two Close messages so the counter needs to increment on close too
if len(job.Data) <= 0 {
conn.(*Connection).Count++
}
cli.Message(cli.NOTE, fmt.Sprintf("Closing SOCKS connection %s", job.ID))

cli.Message(cli.DEBUG, fmt.Sprintf("Closing SOCKS connection %s OUTBOUND pipe", job.ID))
err := conn.(*Connection).Out.Close()
if err != nil {
cli.Message(cli.WARN, fmt.Sprintf("there was an error closing the SOCKS connection %s OUTBOUND pipe: %s", job.ID, err))
}

cli.Message(cli.DEBUG, fmt.Sprintf("Closing SOCKS connection %s INBOUND pipe", job.ID))
err = conn.(*Connection).In.Close()
if err != nil {
cli.Message(cli.WARN, fmt.Sprintf("there was an error closing the SOCKS connection %s INBOUND pipe: %s", job.ID, err))
}

// Send a message back to the server, so it knows the connection has been shutdown/completed
/*
j := jobs.Job{
AgentID: msg.AgentID,
ID: msg.ID,
Token: msg.Token,
Type: jobs.SOCKS,
}
j.Payload = jobs.Socks{
ID: job.ID,
Close: true,
}
*conn.(*Connection).JobChan <- j
*/
// Remove the connection from the map
// Don't remove the connection, it is removed in the receiveFromSOCKSServer function
connections.Delete(job.ID)
done.Store(job.ID, true)
return
}
// TODO: When is the connection removed from the map?
*conn.(*Connection).in <- job
}

// start uses an empty SOCKS server configuration and creates a new instance
func start() (err error) {
// newSOCKSServer is a factory to create and return a global SOCKS5 server instance
func newSOCKSServer() (err error) {
cli.Message(cli.NOTE, "Starting SOCKS5 server")
// Create SOCKS5 server
conf := &socks5.Config{}
Expand All @@ -167,8 +98,8 @@ func start() (err error) {
return
}

// sendToSOCKSServer reads data from an incoming job and sends it to the SOCKS server which will in turn send it to the target
func sendToSOCKSServer(id uuid.UUID) {
// start the SOCKS server to serve the connection
func start(id uuid.UUID) {
cli.Message(cli.NOTE, fmt.Sprintf("Serving new SOCKS connection ID %s", id))

connection, ok := connections.Load(id)
Expand All @@ -184,8 +115,8 @@ func sendToSOCKSServer(id uuid.UUID) {
cli.Message(cli.DEBUG, fmt.Sprintf("Finished serving SOCKS connection ID %s", id))
}

// receiveFromSOCKSServer continuously listens for data being returned from the SOCKS server to be sent to the agent
func receiveFromSOCKSServer(id uuid.UUID) {
// listen continuously for data being returned from the SOCKS server to be sent to the agent
func listen(id uuid.UUID) {
// Listen for data on the agent-side write pipe
connection, ok := connections.Load(id)
if !ok {
Expand Down Expand Up @@ -240,11 +171,79 @@ func receiveFromSOCKSServer(id uuid.UUID) {
}
}

// send continuously sends data to the SOCKS server from the SOCKS client
func send(id uuid.UUID) {
conn, ok := connections.Load(id)
if !ok {
cli.Message(cli.WARN, fmt.Sprintf("connection ID %s was not found", id))
return
}

for {
// Get SOCKS job from the channel
job := <-*conn.(*Connection).in

// Check to ensure the index is correct, if not, return it to the channel to be processed again
if conn.(*Connection).Count != job.Index {
*conn.(*Connection).in <- job
continue
}

// If there is data, write it to the SOCKS server
// Send data, if any, before closing the connection
if len(job.Data) > 0 {
conn.(*Connection).Count++
// Write the received data to the agent side pipe
var buff bytes.Buffer
_, err := buff.Write(job.Data)
if err != nil {
cli.Message(cli.WARN, fmt.Sprintf("there was an error writing SOCKS data to the buffer: %s", err))
return
}

//fmt.Printf("Writing %d bytes to SOCKS target \n", len(job.Data))
n, err := conn.(*Connection).Out.Write(buff.Bytes())
if err != nil {
cli.Message(cli.WARN, fmt.Sprintf("there was an error writing data to the SOCKS %s OUTBOUND pipe: %s", job.ID, err))
return
}
cli.Message(cli.DEBUG, fmt.Sprintf("Wrote %d bytes to the SOCKS %s OUTBOUND pipe with error %s", n, job.ID, err))
}

// If the SOCKS client has sent io.EOF to close the connection
if job.Close {
// Mythic is sending two Close messages so the counter needs to increment on close too
if len(job.Data) <= 0 {
conn.(*Connection).Count++
}
cli.Message(cli.NOTE, fmt.Sprintf("Closing SOCKS connection %s", job.ID))

cli.Message(cli.DEBUG, fmt.Sprintf("Closing SOCKS connection %s OUTBOUND pipe", job.ID))
err := conn.(*Connection).Out.Close()
if err != nil {
cli.Message(cli.WARN, fmt.Sprintf("there was an error closing the SOCKS connection %s OUTBOUND pipe: %s", job.ID, err))
}

cli.Message(cli.DEBUG, fmt.Sprintf("Closing SOCKS connection %s INBOUND pipe", job.ID))
err = conn.(*Connection).In.Close()
if err != nil {
cli.Message(cli.WARN, fmt.Sprintf("there was an error closing the SOCKS connection %s INBOUND pipe: %s", job.ID, err))
}

// Remove the connection from the map
connections.Delete(job.ID)
done.Store(job.ID, true)
return
}
}
}

// Connection is a structure used to track new SOCKS client connections
type Connection struct {
Job jobs.Job
In net.Conn
Out net.Conn
JobChan *chan jobs.Job
Count int
JobChan *chan jobs.Job // Channel to send jobs back to the server
in *chan jobs.Socks // Channel to receive and process SOCKS data locally
Count int // Counter to track the number of SOCKS messages sent
}

0 comments on commit d1a76ee

Please sign in to comment.