Добавить в корзинуПозвонить
Найти в Дзене
Один Rust не п...Rust

Как защитить TCP-пакеты с использованием Rust

t.me/oneRustnoqRust Для чего нужна данная статья? : Реализовать асинхронный TCP-сервер на Tokio, использующий TLS для защиты соединения и HMAC для проверки целостности сообщений. Зачем Вам это уметь? : Обучить модель ML для обнаружения аномалий. Наиболее распространённый и надёжный способ защиты TCP-соединений — использование TLS. В Rust для этого можно использовать библиотеки: // Настройка клиента или сервера с поддержкой TLS Если TLS недоступен или избыточен, можно зашифровать данные вручную перед отправкой.
Библиотеки для шифрования данных: Пример: use aes_gcm::{Aes256Gcm, Key, Nonce}; // Or another cipher use aes_gcm::aead::{Aead, NewAead}; let key = Key::from_slice(b"an example very very secret key."); // 256-битный ключ let cipher = Aes256Gcm::new(key); let nonce = Nonce::from_slice(b"unique nonce"); // Уникальный для каждого сообщения let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref()) .expect("encryption failure!"); Для проверки целостности данных можно исполь
Оглавление
ML на RUST без заморочек

t.me/oneRustnoqRust

Для чего нужна данная статья? :

Реализовать асинхронный TCP-сервер на Tokio, использующий TLS для защиты соединения и HMAC для проверки целостности сообщений.

Зачем Вам это уметь? :

Обучить модель ML для обнаружения аномалий.

1. Использование TLS (Transport Layer Security)

Наиболее распространённый и надёжный способ защиты TCP-соединений — использование TLS. В Rust для этого можно использовать библиотеки:

  • rustls
    Быстрая и безопасная реализация TLS на Rust.
    Пример использования:
    use rustls::{ClientConfig, ServerConfig};

// Настройка клиента или сервера с поддержкой TLS

  • native-tls
    Обёртка для использования системных библиотек TLS (например, OpenSSL, Windows SChannel).
  • tokio-rustls
    Асинхронная интеграция rustls с Tokio для использования в асинхронных приложениях.

2. Шифрование на уровне приложения

Если TLS недоступен или избыточен, можно зашифровать данные вручную перед отправкой.
Библиотеки для шифрования данных:

  • AES-GCM
    Популярный алгоритм аутентифицированного шифрования.

Пример:

use aes_gcm::{Aes256Gcm, Key, Nonce}; // Or another cipher

use aes_gcm::aead::{Aead, NewAead};

let key = Key::from_slice(b"an example very very secret key."); // 256-битный ключ

let cipher = Aes256Gcm::new(key);

let nonce = Nonce::from_slice(b"unique nonce"); // Уникальный для каждого сообщения

let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref())

.expect("encryption failure!");

3. Хеширование и проверка целостности

Для проверки целостности данных можно использовать криптографические хэши (например, HMAC).

  • Библиотеки:ring
    Быстрая криптографическая библиотека для работы с HMAC, шифрованием и подписью.
    sha2
    Реализация SHA-256/SHA-512.

Пример HMAC с использованием ring:

use ring::hmac;

let key = hmac::Key::new(hmac::HMAC_SHA256, b"your-secret-key");

let tag = hmac::sign(&key, b"message");

4. Серийные библиотеки для упаковки/шифрования

Некоторые библиотеки обеспечивают безопасность и простоту передачи данных между системами:

  • Serde: сериализация данных.
  • MessagePack или CBOR: упаковка/сериализация с возможностью шифрования.

5. Использование VPN или SSH-туннелей

Если нет желания или необходимости изменять приложение, можно защитить трафик на уровне сети, используя:

  • VPN (например, WireGuard или OpenVPN).
  • SSH-туннели для маршрутизации TCP-пакетов через защищённое соединение.

6. Защита от атак (например, DoS, MITM):

  • Ограничение подключения:Библиотека tokio позволяет ограничивать количество соединений.
  • Проверка IP-адресов.
  • Защита от повторных подключений через токены или временные метки.

Пример асинхронного TCP-сервера на Tokio, использующего TLS для защиты соединения и HMAC для проверки целостности сообщений.

Для TLS используется библиотека tokio-rustls, а для HMAC — библиотека ring.

use std::sync::Arc;

use tokio::net::TcpListener;

use tokio::prelude::*;

use tokio_rustls::{TlsAcceptor, rustls::{ServerConfig, Certificate, PrivateKey}};

use ring::hmac;

async fn start_tls_server() -> Result<(), Box<dyn std::error::Error>> {

// 1. Настройка TLS

let certs = load_certs("server_cert.pem")?;

let key = load_private_key("server_key.pem")?;

let tls_config = ServerConfig::builder()

.with_safe_defaults()

.with_no_client_auth()

.with_single_cert(certs, key)?;

let acceptor = TlsAcceptor::from(Arc::new(tls_config));

// 2. Настройка TCP-сервера

let listener = TcpListener::bind("127.0.0.1:8080").await?;

println!("Server running on 127.0.0.1:8080");

loop {

let (stream, addr) = listener.accept().await?;

println!("New connection from {}", addr);

// 3. Оборачивание соединения в TLS

let acceptor = acceptor.clone();

tokio::spawn(async move {

match acceptor.accept(stream).await {

Ok(mut tls_stream) => {

println!("TLS handshake successful with {}", addr);

// 4. Чтение данных из клиента

let mut buf = vec![0u8; 1024];

match tls_stream.read(&mut buf).await {

Ok(n) if n > 0 => {

let received_message = &buf[..n];

println!("Received: {:?}", received_message);

// 5. Проверка HMAC

let secret_key = b"supersecretkey";

let key = hmac::Key::new(hmac::HMAC_SHA256, secret_key);

let tag = hmac::sign(&key, received_message);

println!("HMAC tag generated: {:?}", tag.as_ref());

// 6. Отправка данных клиенту

if let Err(e) = tls_stream.write_all(b"Message received").await {

println!("Failed to write: {}", e);

}

}

Ok(_) => println!("Connection closed by client"),

Err(e) => println!("Failed to read: {}", e),

}

}

Err(e) => println!("Failed TLS handshake: {}", e),

}

});

}

}

// Загрузка сертификатов

fn load_certs(filename: &str) -> Result<Vec<Certificate>, Box<dyn std::error::Error>> {

let certfile = std::fs::File::open(filename)?;

let mut reader = std::io::BufReader::new(certfile);

let certs = rustls_pemfile::certs(&mut reader)?

.into_iter()

.map(Certificate)

.collect();

Ok(certs)

}

// Загрузка закрытого ключа

fn load_private_key(filename: &str) -> Result<PrivateKey, Box<dyn std::error::Error>> {

let keyfile = std::fs::File::open(filename)?;

let mut reader = std::io::BufReader::new(keyfile);

let keys: Vec<PrivateKey> = rustls_pemfile::rsa_private_keys(&mut reader)?

.into_iter()

.map(PrivateKey)

.collect();

if keys.is_empty() {

Err("No private keys found".into())

} else {

Ok(keys[0].clone())

}

}

#[tokio::main]

async fn main() {

if let Err(e) = start_tls_server().await {

eprintln!("Error: {}", e);

}

}

Объяснение кода:

  1. TLS-сертификаты и ключи:Сертификат (server_cert.pem) и закрытый ключ (server_key.pem) необходимы для TLS. Их можно сгенерировать с помощью утилиты openssl.
    Пример команды для генерации самоподписанного сертификата:
    openssl req -x509 -newkey rsa:2048 -keyout server_key.pem -out server_cert.pem -days 365 -nodes
  2. Асинхронный TCP-сервер с Tokio:Сервер слушает входящие соединения по адресу 127.0.0.1:8080.
    Каждое соединение оборачивается в TLS с помощью TlsAcceptor.
  3. HMAC для проверки целостности данных:Используется библиотека ring.
    Генерируется HMAC-тег для входящего сообщения с использованием секретного ключа (supersecretkey).
  4. Ответ клиенту:После успешного получения данных сервер отправляет подтверждение обратно клиенту.

Запуск:

Сгенерируйте сертификаты:

openssl req -x509 -newkey rsa:2048 -keyout server_key.pem -out server_cert.pem -days 365 -nodes

Используйте любой TLS-клиент для тестирования соединения (например, openssl s_client):

openssl s_client -connect 127.0.0.1:8080

После подключения отправьте сообщение, и сервер обработает его с HMAC.

Захват TCP-пакетов, извлечение признаков (включая энтропию, размер окна TCP и статистику по портам), использование модели глубокого обучения для обнаружения аномалий, Rayon, блокировка аномальных IP-адресов через iptables и шифрование признаков для защиты системы

  • Программа запускает захват пакетов и инициализирует модель машинного обучения.
  • Пакеты обрабатываются в реальном времени: извлекаются признаки, шифруются, анализируются моделью.
  • Если модель определяет пакет как аномальный (вероятность > 0.5), IP-адрес блокируется.

use pcap::{Capture, Device, Packet};

use tch::{nn, Device as TchDevice, Tensor};

use aes::Aes128;

use aes::cipher::{NewCipher, BlockEncrypt, BlockDecrypt};

use aes::cipher::generic_array::GenericArray;

use rayon::prelude::*;

use std::time::{Instant, Duration};

use log::{info, warn, error};

use env_logger;

use std::collections::HashMap;

use std::process::Command;

// Структура для хранения признаков TCP-пакета

#[derive(Debug, Clone)]

struct TcpFeatures {

length: u32, // Длина пакета

flags: u8, // TCP-флаги (SYN, ACK, FIN и т.д.)

inter_packet_time: f32, // Время между пакетами (в секундах)

packet_rate: f32, // Скорость пакетов в соединении (пакетов/с)

entropy: f32, // Энтропия данных

window_size: u16, // Размер окна TCP

port_count: u32, // Количество пакетов для данного порта

}

// Структура для отслеживания состояния соединения

#[derive(Clone)]

struct ConnectionState {

last_packet_time: Instant,

packet_count: u32,

}

impl ConnectionState {

fn new() -> Self {

ConnectionState {

last_packet_time: Instant::now(),

packet_count: 0,

}

}

// Обновление состояния и вычисление признаков

fn update(&mut self) -> (f32, f32) {

let now = Instant::now();

let elapsed = now.duration_since(self.last_packet_time).as_secs_f32();

self.packet_count += 1;

let packet_rate = if elapsed > 0.0 { self.packet_count as f32 / elapsed } else { 0.0 };

self.last_packet_time = now;

(elapsed, packet_rate)

}

}

// Структура для отслеживания статистики по портам

#[derive(Clone)]

struct PortStats {

port_counts: HashMap<u16, u32>,

}

impl PortStats {

fn new() -> Self {

PortStats { port_counts: HashMap::new() }

}

fn update(&mut self, port: u16) {

*self.port_counts.entry(port).or_insert(0) += 1;

}

fn get_count(&self, port: u16) -> u32 {

*self.port_counts.get(&port).unwrap_or(&0)

}

}

// Захват TCP-пакетов

fn setup_capture() -> Capture<Device> {

let device = Device::lookup().expect("Не удалось найти устройство");

let mut cap = Capture::from_device(device)

.expect("Ошибка инициализации захвата")

.promisc(true) // Включаем promiscuous mode

.timeout(1000) // Тайм-аут 1 секунда

.open()

.expect("Ошибка открытия устройства");

cap.filter("tcp").expect("Ошибка установки фильтра на TCP");

info!("Захват TCP-пакетов настроен на устройстве: {}", device.name);

cap

}

// Вычисление энтропии данных

fn calculate_entropy(data: &[u8]) -> f32 {

let mut frequency = [0u32; 256];

for &byte in data {

frequency[byte as usize] += 1;

}

let total = data.len() as f32;

frequency.iter().filter(|&&f| f > 0).fold(0.0, |acc, &f| {

let p = f as f32 / total;

acc - p * p.log2()

})

}

// Извлечение размера окна TCP

fn extract_window_size(packet: &Packet) -> u16 {

if packet.data.len() >= 14 {

u16::from_be_bytes([packet.data[14], packet.data[15]])

} else {

0

}

}

// Извлечение признаков из TCP-пакета

fn extract_features(packet: &Packet, conn_state: &mut ConnectionState, port_stats: &mut PortStats) -> TcpFeatures {

let length = packet.header.len;

let flags = if packet.data.len() > 13 { packet.data[13] } else { 0 };

let (inter_packet_time, packet_rate) = conn_state.update();

let entropy = calculate_entropy(&packet.data);

let window_size = extract_window_size(packet);

let port = if packet.data.len() >= 2 { u16::from_be_bytes([packet.data[0], packet.data[1]]) } else { 0 };

port_stats.update(port);

let port_count = port_stats.get_count(port);

TcpFeatures {

length,

flags,

inter_packet_time,

packet_rate,

entropy,

window_size,

port_count,

}

}

// Определение модели нейронной сети

struct Net {

fc1: nn::Linear,

fc2: nn::Linear,

fc3: nn::Linear,

}

impl Net {

fn new(vs: &nn::Path) -> Net {

let fc1 = nn::linear(vs / "fc1", 7, 64, Default::default());

let fc2 = nn::linear(vs / "fc2", 64, 32, Default::default());

let fc3 = nn::linear(vs / "fc3", 32, 1, Default::default());

Net { fc1, fc2, fc3 }

}

fn forward(&self, xs: &Tensor) -> Tensor {

xs.apply(&self.fc1).relu()

.apply(&self.fc2).relu()

.apply(&self.fc3).sigmoid()

}

}

// Обучение модели

fn train_model() -> Net {

let vs = nn::VarStore::new(TchDevice::Cpu);

let net = Net::new(&vs.root());

let opt = nn::Adam::default().build(&vs, 1e-3).unwrap();

let features = Tensor::of_slice(&[

100.0, 2.0, 0.1, 10.0, 1.5, 1024.0, 5.0,

150.0, 4.0, 0.05, 20.0, 2.0, 2048.0, 10.0,

500.0, 8.0, 0.01, 100.0, 3.5, 512.0, 1.0,

]).view((3, 7));

let labels = Tensor::of_slice(&[0.0, 0.0, 1.0]).view((-1, 1));

for _ in 0..1000 {

let loss = net.forward(&features).binary_cross_entropy::<Tensor>(&labels, None, tch::Reduction::Mean);

opt.backward_step(&loss);

}

net

}

// Функции шифрования и дешифрования признаков

fn encrypt_features(features: &[f32], key: &GenericArray<u8, <Aes128 as NewCipher>::KeySize>) -> Vec<u8> {

let cipher = Aes128::new(key);

let mut encrypted = Vec::new();

for &feature in features {

let mut block = GenericArray::clone_from_slice(&feature.to_le_bytes());

cipher.encrypt_block(&mut block);

encrypted.extend_from_slice(&block);

}

encrypted

}

fn decrypt_features(encrypted: &[u8], key: &GenericArray<u8, <Aes128 as NewCipher>::KeySize>) -> Vec<f32> {

let cipher = Aes128::new(key);

let mut features = Vec::new();

for chunk in encrypted.chunks(16) {

let mut block = GenericArray::clone_from_slice(chunk);

cipher.decrypt_block(&mut block);

let feature = f32::from_le_bytes([block[0], block[1], block[2], block[3]]);

features.push(feature);

}

features

}

// Обнаружение аномалий

fn detect_anomaly(model: &Net, features: &TcpFeatures, key: &GenericArray<u8, <Aes128 as NewCipher>::KeySize>) -> bool {

let feature_array = [

features.length as f32,

features.flags as f32,

features.inter_packet_time,

features.packet_rate,

features.entropy,

features.window_size as f32,

features.port_count as f32,

];

let encrypted = encrypt_features(&feature_array, key);

let decrypted = decrypt_features(&encrypted, key);

let tensor = Tensor::of_slice(&decrypted).view((1, 7));

let prediction = model.forward(&tensor).double_value(&[0]);

prediction > 0.5

}

// Извлечение IP-адреса источника

fn extract_src_ip(packet: &Packet) -> String {

if packet.data.len() >= 12 {

format!("{}.{}.{}.{}", packet.data[12], packet.data[13], packet.data[14], packet.data[15])

} else {

"0.0.0.0".to_string()

}

}

// Блокировка IP-адреса через iptables

fn block_ip(ip: String) {

let output = Command::new("sudo")

.arg("iptables")

.arg("-A")

.arg("INPUT")

.arg("-s")

.arg(ip)

.arg("-j")

.arg("DROP")

.output();

match output {

Ok(output) if output.status.success() => println!("IP {} заблокирован", ip),

_ => eprintln!("Ошибка блокировки IP {}", ip),

}

}

// Обработка пакетов

async fn process_packets(mut cap: Capture<Device>, model: Net, key: GenericArray<u8, <Aes128 as NewCipher>::KeySize>) {

let mut conn_state = ConnectionState::new();

let mut port_stats = PortStats::new();

loop {

let packets: Vec<Packet> = cap.iter().take(100).collect();

packets.par_iter().for_each(|packet| {

let mut local_conn_state = conn_state.clone();

let mut local_port_stats = port_stats.clone();

let features = extract_features(packet, &mut local_conn_state, &mut local_port_stats);

if detect_anomaly(&model, &features, &key) {

let src_ip = extract_src_ip(packet);

block_ip(src_ip);

}

});

tokio::time::sleep(Duration::from_millis(10)).await;

}

}

// Главная функция

#[tokio::main]

async fn main() {

env_logger::init();

info!("Запуск системы защиты TCP-пакетов");

let cap = setup_capture();

let model = train_model();

let key = GenericArray::from([0u8; 16]); // В реальной системе ключ должен быть секретным

process_packets(cap, model, key).await;

}