Курс — Хакинг на Rust. #10 Типы данных и структуры: Структуры, перечисления и паттерн-матчинг
Здравствуйте, дорогие друзья.
Структуры и перечисления в Rust — это не просто контейнеры для данных. Они позволяют моделировать сложные объекты (сетевые пакеты, статусы атак, метаданные эксплойтов) с точностью, которая критична для кибербезопасности. Паттерн-матчинг (match
) добавляет контроль над этими данными, предотвращая ошибки, которые часто приводят к уязвимостям.
3.1 Структуры: моделируем реальность
Структуры (struct
) позволяют объединять связанные данные в единый тип.
Классические структуры
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct IPPacket { src_ip: [u8; 4], dst_ip: [u8; 4], payload: Vec<u8>, } impl IPPacket { fn new(src: [u8; 4], dst: [u8; 4], data: &[u8]) -> Self { Self { src_ip: src, dst_ip: dst, payload: data.to_vec(), } } } |
Пример использования :
1 |
let packet = IPPacket::new([192, 168, 1, 1], [10, 0, 0, 1], b"malicious_payload"); |
Кортежные структуры
Для простых типов без именованных полей:
1 2 3 |
struct Port(u16); let ssh = Port(22); |
Unit-структуры
Для типов без данных (например, маркеры состояния):
1 |
struct ConnectionClosed; |
3.2 Перечисления: структурируем варианты
Перечисления (enum
) описывают тип, который может принимать фиксированный набор значений .
Пример: сетевые протоколы
1 2 3 4 5 |
enum Protocol { TCP(u16), UDP(u16), ICMP, } |
Пример использования :
1 |
let scan_target = Protocol::TCP(80); |
Пример: статус эксплойта
1 2 3 4 5 |
enum ExploitStatus { InProgress, Success { shellcode: Vec<u8> }, Failed(String), } |
3.3 Паттерн-матчинг: исчерпывающая обработка
Конструкция match
позволяет безопасно работать с перечислениями и структурами:
Анализ сетевого протокола
1 2 3 4 5 6 7 |
fn analyze_protocol(protocol: Protocol) { match protocol { Protocol::TCP(port) => println!("TCP-порт: {}", port), Protocol::UDP(port) => println!("UDP-порт: {}", port), Protocol::ICMP => println!("ICMP-запрос"), } } |
Важно :
match
проверяет все варианты. Если забыть какой-то — код не скомпилируется.- Можно использовать стражи (
if
) для дополнительных условий:
1 2 3 4 |
match port { Protocol::TCP(p) if p == 80 => println!("HTTP-трафик"), _ => {} } |
Деструктуризация структур
Извлечение полей структуры в match
:
1 2 3 4 5 6 |
fn inspect_packet(packet: IPPacket) { match packet { IPPacket { src_ip: [192, 168, _, _], .. } => println!("Локальный трафик"), IPPacket { payload, .. } => println!("Полезная нагрузка: {:?}", payload), } } |
3.4 Паттерн-матчинг и обработка ошибок
В Rust ошибки возвращаются через Result
и Option
, которые обрабатываются через match
:
Пример: подключение к хосту
1 2 3 4 5 6 7 8 9 10 11 12 |
use std::net::TcpStream; fn connect(host: &str, port: u16) -> Result<TcpStream, String> { TcpStream::connect((host, port)) .map_err(|e| format!("Ошибка: {}", e)) } // Обработка результата match connect("127.0.0.1", 80) { Ok(stream) => println!("Подключено!"), Err(e) => eprintln!("Ошибка: {}", e), } |
Обработка Option
1 2 3 4 5 6 7 8 9 |
fn find_admin(data: &[User]) -> Option<&User> { data.iter().find(|u| u.is_admin) } // Использование match find_admin(&users) { Some(admin) => println!("Админ: {}", admin.name), None => println!("Админ не найден"), } |
3.5 Паттерн-матчинг в низкоуровневых задачах
Пример: парсинг TCP-заголовка
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct TCPHeader { src_port: u16, dst_port: u16, seq_num: u32, } impl TCPHeader { fn from_bytes(data: &[u8]) -> Option<Self> { if data.len() < 8 { return None; } Some(TCPHeader { src_port: u16::from_be_bytes([data[0], data[1]]), dst_port: u16::from_be_bytes([data[2], data[3]]), seq_num: u32::from_be_bytes([data[4], data[5], data[6], data[7]]), }) } } |
3.6 Защита от уязвимостей
- Переполнение буфера :
В C:
1 2 |
char buffer[10]; strcpy(buffer, "This is too long!"); // Переполнение |
В Rust:
1 2 |
let mut buffer = vec![0u8; 10]; buffer.extend(b"This is too long!"); // Ошибка компиляции при превышении |
Некорректные состояния :
Вместо магических чисел (например, status = 0
для успеха) — перечисления:
1 2 3 4 |
enum AttackResult { Success, Failed(String), } |
3.7 Практическое задание: парсер DNS-запросов
Задача : Напишите функцию, которая извлекает домен из DNS-запроса.
Решение :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
struct DNSHeader { id: u16, flags: u16, qdcount: u16, } enum DNSRecord { A { domain: String, ip: [u8; 4] }, CNAME { domain: String, alias: String }, } fn parse_dns(data: &[u8]) -> Option<DNSRecord> { let header = DNSHeader { id: u16::from_be_bytes([data[0], data[1]]), flags: u16::from_be_bytes([data[2], data[3]]), qdcount: u16::from_be_bytes([data[4], data[5]]), }; if header.qdcount == 0 { return None; } // Упрощенный парсинг домена let domain = String::from_utf8_lossy(&data[12..18]).into_owned(); Some(DNSRecord::A { domain, ip: [192, 168, 1, 1], }) } |
Итог
Структуры, перечисления и паттерн-матчинг делают код:
- Безопасным : Невозможно забыть обработать состояние или переполнить буфер.
- Читаемым : Данные и логика четко структурированы.
- Эффективным : Нет накладных расходов, как в ООП-языках.
Это ключевой инструментарий для хакера: анализ пакетов, управление эксплойтами, обработка ошибок — все это становится предсказуемым и защищенным. В следующих главах мы углубимся в низкоуровневые операции и создание эксплойтов. А пока — попробуйте написать парсер для UDP-пакета, используя структуры и match
.

На этом все. Всем хорошего дня!