Пост канала
- фото
Призрак освобожденной памяти: как работает Use-After-Free и почему его боятся даже создатели браузеров Мы уже посмотрели, как компилятор отбивает атаки на стек и как ошибки форматирования сливают токены из памяти. Теперь шагнем на территорию, где находят самые дорогие уязвимости в ядрах ОС, браузерах и демонах - в динамическую память (Heap / Кучу). Сегодня в Parrot Security OS на ARM64 я разобрал уязвимость класса Use-After-Free (UAF) и получил доступ администратора без единой ошибки сегментации. Делюсь пошаговым разбором того, как аллокатор памяти сам отдает контроль над чужими объектами. 1. В чем суть бага Use-After-Free? Когда программа выделяет блок памяти через malloc(), а затем освобождает его через free(), программист обязан занулить указатель (ptr = NULL). Если этого не сделать, указатель становится «висячим» (Dangling Pointer). Он продолжает указывать на адрес в куче, хотя система считает этот кусок свободным. Если программа позже снова обратится по этому указателю, она прочитает то, что окажется на этом месте. 2. Эксперимент: симулируем веб-сессию Я написал сервис, где есть две сущности: 1. Сессия пользователя UserSession: логин (24 байта) и флаг роли is_admin (4 байта). 2. Заметка пользователя global_note: обычный текстовый буфер того же размера. Смотрим на первый скриншот: 1. Создаем сессию: выделяется блок по адресу 0xaaaaaaac16b0, статус is_admin = 0. 2. Пользователь выходит: вызывается free(), но указатель не зануляется. 3. Создаем заметку: и аллокатор glibc выделяет под нее ровно тот же самый адрес 0xaaaaaaac16b0! Почему адреса совпали? В glibc работает механизм кэширования Tcache (Thread Local Cache). Чтобы не тратить время на системные вызовы, аллокатор складывает недавно освобожденные чанки одинакового размера в быстрый список и при следующем malloc мгновенно отдает их повторно! 3. Ловушки ядра: почему нельзя передать нули в argv Казалось бы, достаточно записать в новую заметку 24 символа имени и байт \x01\x00\x00\x00, чтобы выставить флаг is_admin = 1. Но тут включились правила Linux и POSIX (второй и третий скриншоты): - Командная строка argv считает нулевой байт \x00 концом строки. - Python при попытке передать бинарный ноль в аргумент выбросил системную ошибку ValueError: embedded null byte. - А когда мы передали строку без нулей, memcpy скопировал наш байт \x01, но не затер старые служебные указатели аллокатора, выдав статус is_admin = 1213399041 вместо единицы! 4. Взлом через сырой поток stdin (четвертый и пятый скриншоты) В реальных сетевых службах данные приходят не из аргументов запуска, а из сокета или стандартного ввода stdin. Я переписал чтение заметки на fread() из потока ввода и передал пейлоад через пайп: python3 -c 'import sys; sys.stdout.buffer.write(b"A"*24 + b"\x01\x00\x00\x00")' | ./uaf Все 28 байт прошли без искажений: - 24 буквы A заполнили поле логина; - байты \x01\x00\x00\x00 аккуратно легли на место флага роли; - программа обратилась к старому висячему указателю global_user->is_admin, увидела значение 1 и выдала флаг: [+++] ДОСТУП РАЗРЕШЕН! Привет, Администратор! Флаг: FLAG{UAF_HEAP_MASTER_2026}. Никакого переполнения буфера, никаких сработавших канареек - мы просто переиспользовали штатную логику менеджера памяти. Главные выводы по безопасности кучи 1. Правило чистого указателя: Вызвал free(ptr) - сразу пиши ptr = NULL;. Обращение к нулевому адресу приведет к мгновенному и безопасному завершению программы вместо скрытой эксплуатации. 2. Динамический анализ (ASan): Собирайте тестовые билды с флагом -fsanitize=address. AddressSanitizer блокирует использование освобожденной памяти и сразу выводит имя проблемной строки. 3. Тренд на Memory-Safe языки: Именно сложности контроля времени жизни объектов в Си/C++ подталкивают индустрию к переходу на Rust и Go, где компилятор проверяет владение памятью на этапе сборки.
Открыть в MAX- Просмотры
- 12
- ER
- —
- Реакции
- —