Курс — Хакинг на Rust. #22 Инструменты хакера на Rust. Фаззинг и тестирование на устойчивость. Генерация тестовых данных для обнаружения уязвимостей
Здравствуйте, дорогие друзья.
Зачем генерировать тестовые данные?
Генерация тестовых данных — основа фаззинга и стресс-тестирования. Она позволяет:
- Найти краши из-за некорректной обработки ввода (переполнение буфера, use-after-free).
- Выявить логические ошибки в парсерах, сериализаторах, сетевых протоколах.
- Проверить устойчивость к нечеловеческим сценариям (например, гигантские пакеты, невалидные UTF-8 строки).
В Rust это особенно важно для unsafe
-кода, где защита языка отключена.
Стратегии генерации данных
1. Случайные мутации
Генерация данных на основе мутаций исходных шаблонов.
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
use rand::{Rng, RngCore}; fn mutate(data: &[u8]) -> Vec<u8> { let mut rng = rand::thread_rng(); let mut mutated = data.to_vec(); match rng.gen_range(0..3) { 0 => mutated.push(rng.gen()), // Добавить байт 1 => { if !mutated.is_empty() { mutated.remove(0); } }, // Удалить байт 2 => { if !mutated.is_empty() { mutated[0] = rng.gen(); } }, // Изменить байт _ => {} } mutated } |
2. Генерация на основе грамматик
Для структурированных данных (JSON, HTTP-запросы) используют грамматики.
Пример с arbitrary
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
use arbitrary::{Arbitrary, Unstructured}; #[derive(Arbitrary)] struct HttpRequest { method: String, path: String, headers: Vec<(String, String)>, } fn generate_http_request(data: &[u8]) -> HttpRequest { let mut unstructured = Unstructured::new(data); HttpRequest::arbitrary(&mut unstructured).unwrap() } |
3. Словари
Использование известных паттернов для мутации (например, ключевые слова SQL для инъекций).
Пример словаря для SQLi:
1 2 3 4 5 6 |
const SQL_KEYWORDS: &[&str] = &["SELECT", "DROP", "UNION", ";--"]; fn inject_sql_keywords(data: &str) -> String { let mut rng = rand::thread_rng(); format!("{}{}", data, SQL_KEYWORDS[rng.gen_range(0..SQL_KEYWORDS.len())]) } |
Инструменты для генерации
Cargo-fuzz: Умные мутации с LibFuzzer
cargo-fuzz
использует LLVM LibFuzzer, который комбинирует мутации с анализом покрытия кода.
Пример фазз-таргета для парсера CSV:
1 2 3 4 5 6 7 8 9 |
// fuzz/fuzz_targets/csv_parser.rs #![no_main] use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { if let Ok(s) = std::str::from_utf8(data) { let _ = my_parser::parse_csv(s); // Проверяем парсер } }); |
Запуск:
1 |
cargo fuzz run csv_parser |
AFL.rs: Генетический фаззинг
AFL использует генетические алгоритмы для эволюции тестовых данных.
Пример интеграции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// src/main.rs #[cfg(test)] mod tests { use afl::fuzz; use std::str; #[test] fn fuzz_json_parser() { fuzz!(|data: &[u8]| { if let Ok(s) = str::from_utf8(data) { my_parser::parse_json(s).unwrap(); } }); } } |
Запуск:
1 2 |
cargo afl build cargo afl fuzz -i in_dir -o out_dir target/debug/my_app |
Обработка нестандартных данных
Бинарные протоколы
Для бинарных данных (например, DNS-пакеты) используйте arbitrary
или ручную генерацию.
Пример DNS-пакета:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
use arbitrary::Arbitrary; #[derive(Arbitrary)] struct DnsHeader { id: u16, flags: u16, qdcount: u16, // ... } fn fuzz_dns(data: &[u8]) { let header = DnsHeader::arbitrary(&mut Unstructured::new(data)).unwrap(); // Парсинг и проверка } |
Невалидные UTF-8 строки
Проверка обработки некорректных символов:
1 2 3 4 5 |
use std::str; fn fuzz_utf8(data: &[u8]) { let _ = str::from_utf8(data); // Проверяем обработку ошибок } |
Анализ крашей
При падении фаззера сохраняется тест-кейс , вызвавший ошибку. Пример анализа:
1 2 3 |
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000010 READ of size 1 at 0x602000000010 thread T0 #0 0x55a5d43f113e in parse_packet /src/parser.rs:42 |
Действия:
- Проверьте, связан ли краш с
unsafe
-кодом. - Используйте
gdb
для отладки: - Воспроизведите ошибку вручную.
Этические аспекты
- Только разрешенные цели: Тестирование на чужих системах без согласия — нарушение закона.
- Ответственное раскрытие: Сообщайте о найденных уязвимостях разработчикам.
Задание для самостоятельной работы:
- Напишите генератор HTTP-запросов с мутациями методов и заголовков.
- Проверьте парсер XML на устойчивость к внешним сущностям (XXE).
- Проанализируйте краш-лог из
cargo-fuzz
и найдите уязвимый код.
Итог:
Генерация тестовых данных — ключевой этап поиска уязвимостей. Rust-инструменты вроде cargo-fuzz
и afl.rs
делают этот процесс эффективным, но успех зависит от понимания структуры данных и креативности в их мутациях. Тестируйте без устали — и пусть ваши программы сломаются раньше, чем их сломают злоумышленники.

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