Курс — Хакинг на Rust. #29 Продвинутые темы. Обход защиты: Работа с DEP и ASLR
Здравствуйте, дорогие друзья.
Современные операционные системы используют многоуровневые механизмы защиты для предотвращения эксплуатации уязвимостей. Два ключевых из них — DEP (Data Execution Prevention) и ASLR (Address Space Layout Randomization) . DEP блокирует выполнение кода в областях памяти, помеченных как «некодовые» (например, стек или куча), а ASLR усложняет атаки, рандомизируя расположение сегментов памяти. В этом разделе мы разберем, как эти механизмы работают, и изучим методы их обхода с помощью Rust.
9.1. DEP: От теории к практике
Что такое DEP?
DEP (Data Execution Prevention) — технология, которая помечает определенные регионы памяти (стек, куча) как неисполняемые . Это предотвращает выполнение shellcode, внедренного через уязвимости вроде переполнения буфера.
Как DEP работает на низком уровне?
- При включении DEP процессор и ОС используют бит NX (No-eXecute) в таблицах страниц памяти.
- Попытка выполнить код в защищенной области вызывает исключение (например, segmentation fault).
Обход DEP через ROP-цепочки
Если DEP блокирует выполнение shellcode, атакующие используют уже существующий код в памяти — ROP-гаджеты (Return-Oriented Programming). Эти гаджеты — короткие последовательности инструкций, завершающиеся командой ret
. Их комбинация позволяет выполнять произвольные действия, не нарушая DEP.
Пример на Rust: Генерация ROP-цепочки
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
use std::process::Command; fn main() { // Пример вызова ROP-цепочки через уязвимый бинарник (упрощенно) let payload = vec![ 0x41414141, // Адрес гаджета 1: pop rdi; ret 0xdeadbeef, // Значение для rdi 0x42424242, // Адрес system() из libc ]; // Запуск уязвимой программы с payload Command::new("./vulnerable_app") .arg(payload.as_ptr() as *const u8 as _) .spawn() .unwrap(); } |
Важно: Для работы с адресами памяти и ROP-гаджетами потребуется анализ бинарников (например, с помощью gdb
и pwntools
).
9.2. ASLR: Рандомизация и ее слабые места
Как работает ASLR?
ASLR случайным образом перемещает базовые адреса ключевых сегментов памяти:
- Стек.
- Куча.
- Библиотеки (libc, libstdc++).
Это делает невозможным заранее знать адреса функций или буферов.
Методы обхода ASLR
- Утечка адресов
Если в приложении есть уязвимость, возвращающая указатель (например, форматная строка), можно получить базовый адрес библиотеки и рассчитать смещения. - Частичный контроль памяти
Даже при включенном ASLR некоторые адреса могут быть предсказуемыми. Например, в 32-битных системах энтропия ASLR низкая. - Использование памяти без ASLR
Некоторые регионы (например, стек в старых ядрах) могут не рандомизироваться.
Пример на Rust: Чтение /proc/self/maps (Linux)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
use std::fs::File; use std::io::{self, Read}; fn read_memory_maps() -> io::Result<()> { let mut file = File::open("/proc/self/maps")?; let mut contents = String::new(); file.read_to_string(&mut contents)?; println!("{}", contents); Ok(()) } fn main() { // Вывод карты памяти текущего процесса read_memory_maps().unwrap(); } |
Этот код показывает расположение сегментов памяти, что полезно для анализа ASLR.
9.3. Совместный обход DEP и ASLR
Комбинированная атака
- Сначала обойти ASLR, получив утечку адреса (например, через уязвимость чтения за пределами буфера).
- Затем использовать ROP-цепочку для обхода DEP, выполнив полезную нагрузку в известной области памяти.
Пример: Обход DEP+ASLR через утечку libc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Псевдокод для иллюстрации fn exploit() { let libc_base = leak_libc_address(); // Утечка базы libc let system_addr = libc_base + 0x45390; // Смещение system() let bin_sh_addr = libc_base + 0x18cd57; // Смещение "/bin/sh" // Построение ROP-цепочки let rop_chain = [ pop_rdi_ret(), // Гаджет для помещения bin_sh_addr в RDI bin_sh_addr, system_addr, ]; // Передача цепочки в уязвимый процесс send_payload(rop_chain); } |
9.4. Использование unsafe и FFI для низкоуровневых манипуляций
Работа с сырыми указателями
Для обхода защит часто требуется манипулировать памятью напрямую. В Rust это делается через unsafe
:
1 2 3 4 5 |
unsafe { let mut buffer = [0u8; 8]; let ptr = buffer.as_mut_ptr(); std::ptr::write(ptr, 0x41414141u32); // Запись в память } |
Интеграция с C-библиотеками
Через FFI можно вызывать функции из libc для работы с памятью:
1 2 3 4 5 6 7 8 |
extern "C" { fn mprotect(addr: *mut c_void, len: size_t, prot: c_int) -> c_int; } unsafe { // Сделать стек исполняемым (обход DEP) mprotect(stack_addr, 4096, PROT_READ | PROT_WRITE | PROT_EXEC); } |
9.5. Этические аспекты и защита
Ответственное использование
- Техники из этой главы предназначены для обучения и этического хакинга.
- Всегда получайте разрешение перед тестированием систем.
Защита своих приложений
- Используйте Rust для минимизации уязвимостей (Ownership, Borrow Checker).
- Включайте флаги компилятора:
-fstack-protector
,-D_FORTIFY_SOURCE
.
Заключение
DEP и ASLR — мощные, но не идеальные механизмы. Понимание их работы и методов обхода критически важно для кибербезопасности. Rust, благодаря строгой типизации и управлению памятью, помогает писать безопасный код, но знание низкоуровневых атак позволяет защищать системы на новом уровне. В следующем разделе мы рассмотрим интеграцию Rust с другими языками для создания высокопроизводительных инструментов.

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