The Jerusalem PostThe alliance with America is changing, and Israel cannot afford to ignore it - opinionInquirerMunicipal employee shot dead in Bukidnon; manhunt on for suspectsDaily MaverickThe varsity research subsidy game is up — the education department must not back downInquirer EntertainmentBea Millan-Windorski flies to Puerto Rico ahead of 2026 Miss UniversePunch2026 set to be second-hottest year on record — EUColliderApple TV Officially Resurrects Christopher Nolan’s 169-Minute Sci-Fi MasterpieceCBS NewsDemocrats could dust off plans for court reforms after capturing CongressZDF heuteAktuelle Pressemitteilungen des ZDFThe Hollywood Reporter“Saviors of Cinema” and “Money Machine”: Tallinn Puts Spotlight on Kids, Youth Films and Social Media BansThe RegisterUK ministers seek powers to investigate when AI agents are let off the leashGIGAZINEAIのオープンモデル利用率が大きく拡大中、オープンウェイトモデルとクローズドウェイトモデルの違いは何か?BillboardInterscope Expands Into Brazil With Hip-Hop Artist BK, Supernova Entertainment Joint Venture
The Daily Newsstand · Free, Always
Friday, November 6, 2026

Как я разбиралась с CVE-2019-2215: путь от сборки ядра до root на Android

Translate

Заметка о том, как я применила навыки реверс‑инжиниринга бинарных файлов к исследованию уязвимости ядра Android, с какими трудностями столкнулась и что из этого вынесла.

Дисклеймер: Материал носит исследовательский характер. Уязвимость закрыта, PoC не предназначен для атаки на реальные устройства.

Вместо вступления

В моей повседневной работе я занимаюсь реверс‑инжинирингом и разработкой под Android: статический анализ APK и нативных библиотек, разбор алгоритмов аутентификации в IDA Pro и Jadx, автоматизация через Python.

Когда мне на тестовом задании предложили воспроизвести CVE-2019-2215 (Use‑After‑Free в драйвере Binder), это было логичным продолжением моего стека, но с погружением на уровень ниже — на уровень ядра Linux 4.14.150+ (goldfish, x86_64). Здесь привычные методы вроде Jadx уже не работали. Нужно было собирать кастомное ядро, разбираться с отладкой vmlinux через GDB и работать со структурами памяти напрямую.

Две недели я занималась этим. Итог — успешное локальное повышение привилегий до root. Но самое интересное было не в результате, а в том, как я к нему шла, и какие трудности решала по пути. Этот опыт стал для меня отличным тестом на умение адаптировать инструменты реверса под ядерную специфику.

Подготовка: почему нельзя взять готовое ядро

Первое, с чем я столкнулась — в актуальных ветках ядра уязвимость уже была закрыта. Значит, чтобы её воспроизвести, нужно было принудительно вернуть код в уязвимое состояние. Я сделала реверс‑патчинг функции binder_poll: убрала вызовы wake_up_poll с флагом POLLFREE и synchronize_rcu(), которые предотвращали сохранение «висячего» указателя в epoll.

Отдельной задачей стала сборка. В базовой конфигурации был активен KASAN (KernelAddressSanitizer), который моментально вызывал Kernel Panic при обращении к освобождённому объекту. Пришлось пересобирать ядро (ревизия #2) с отключённым KASAN — только так можно было вернуть стандартное поведение аллокатора SLUB. Патчи и конфигурация сборки лежат в репозитории.

Статический анализ: где искать addr_limit

Эксплойт не работает «на глазок». Нужно было точно знать, где в памяти ядра лежат нужные структуры. Для этого я использовала связку pahole и gdb‑multiarch.

Главная сложность была в том, что в тестируемом ядре активна опция CONFIG_THREAD_INFO_IN_TASK. Я ожидала найти переменную addr_limit внутри thread_info, но её там не оказалось — структура занимала всего 16 байт. Проверка через ptype /o struct thread_info в GDB подтвердила это.

Пришлось копать глубже. С помощью pahole и анализа нулевого указателя в GDB (p &((struct task_struct )0)→thread.addr_limit) я вычислила точное смещение: 0xa18 внутри встроенной thread_struct. После контроля через GDB ($1 = (mm_segment_t ) 0xa18) макросы были исправлены, и эксплойт перестал промахиваться.

Race condition и мусор в iovec

Самой мучительной частью было заставить эксплойт работать стабильно. Две проблемы, которые я решала:

  1. Race condition при утечке данных. Вызов writev() возвращал 0×1000 вместо 0×2000. Перезапись структуры в куче не происходила, и я долго не могла понять, почему. Мониторинг через ps показал, что процесс уходил в бесконечный сон pipe_wait из‑за нарушения логики наполнения пайпа. Я переписала алгоритм синхронизации: пайп оставлен пустым, а 10-й элемент iovec сам забивает его до предела, усыпляя родительский поток на 11-м элементе. Это дало дочернему процессу стабильное окно на выполнение unlink.

  2. Случайные Kernel Panic. Смещение указателей в куче приводило к Spinlock Lockup при вызове unlink. Причина оказалась банальной: стековый мусор попадал в проверки access_ok(). Решение — очистка локальных массивов iovec через memset() с правильным вычислением размера (sizeof(struct iovec) * IOVEC_COUNT) и строгое выравнивание отправляемого буфера до 40 байт.

Для обхода проверок ядра при чтении/записи в память также потребовалось внести изменения в lib/iov_iter.c (принудительный вызов raw_copy_to_user/raw_copy_from_user). Это позволило снять ограничение addr_limit и получить примитив Arbitrary R/W через обычные системные вызовы на пайпах.

Результат

После снятия addr_limit (0 xfffffffffffffffe) я прочитала указатель на struct cred из task_struct и обнулила первые 8 полей (UID/GID). Получение root подтверждено выводом getuid().

[+] addr_limit = 0xfffffffffffffffe
[+] Модификация структуры cred (UID/GID -> 0)...
[*] Текущий UID: 0
[==================================================]
[  УСПЕХ! Мы получили права ROOT (UID=0) в ядре!  ]
[==================================================]

Что я вынесла из этой задачи

Инструменты решают. Когда я перестала гадать и пошла в GDB и pahole, смещения нашлись быстро. Ошибка была не в непонимании ядра, а в том, что я искала addr_limit не там, где он лежит в этой конфигурации.

Синхронизация важнее самого эксплойта. Половина падений была не в примитивах записи, а в том, кто и когда блокируется.

Отчёт — это часть инженерной работы. Подробный write‑up со скриншотами и таблицами занял времени не меньше, чем сам PoC. Без него результат невоспроизводим, а значит — бесполезен для команды.

Полный отчёт, код и патчи ядра доступны в моём репозитории на GitHub.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.