Как одна «грязная корова» ломает Linux: получаем root через код, которого нет на диске

56170
Как одна «грязная корова» ломает Linux: получаем root через код, которого нет на диске

Почему рабочий эксплойт Dirty COW не всегда даёт root и как адаптировать PoC под BusyBox, vDSO и конкретное Linux-окружение.

image

В 2016 году в ядре Linux обнаружили CVE-2016-5195, известную как Dirty COW. Уязвимость позволяла локальному пользователю обойти механизм copy-on-write и изменять защищённые данные.

В поддерживаемых версиях Linux ошибку давно исправили. Но устройства со старыми ядрами и прошивками продолжают работать годами, поэтому возраст CVE сам по себе не гарантирует безопасность системы.

Разбор Dirty COW полезен и за пределами одной уязвимости. Публичный PoC создаётся для определённой архитектуры, набора утилит и конфигурации. В другом окружении он может успешно использовать ошибку, но не доводить атаку до результата.

Умение найти такое расхождение и адаптировать эксплойт — одна из базовых задач практического пентеста. Здесь недостаточно запустить чужой код: нужно понять устройство системы, проверить предположения автора PoC и подобрать работающую цепочку.

Разберём, как работает Dirty COW, почему результат зависит от окружения и при чём здесь код, которого нет на диске.

Проверить себя можно в задаче Strange And Dirty на бесплатном курсе «Профессия Белый Хакер». Статья даёт необходимую теорию, но не раскрывает готовое решение.

Что именно позволяет сделать Dirty COW

Название Dirty COW происходит от copy-on-write — механизма, который Linux использует при работе с памятью.

Представим, что процесс создаёт приватное отображение файла с помощью mmap() и флага MAP_PRIVATE. Пока процесс только читает данные, ядру незачем создавать отдельную копию страниц памяти. Она появляется при первой попытке записи: процесс получает собственную копию страницы, а исходный файл не меняется.

Это и есть copy-on-write — «копирование при записи».

В уязвимых версиях ядра этот механизм можно было нарушить, создав состояние гонки. Публичные эксплойты многократно выполняли два действия:

  • пытались записать данные через /proc/self/mem;
  • вызывали madvise(..., MADV_DONTNEED) для того же участка памяти.

Первое действие пыталось изменить отображённую страницу. Второе сообщало ядру, что страница больше не нужна и её можно удалить из памяти.

При определённом порядке выполнения этих операций ядро некорректно обрабатывало copy-on-write. В результате процесс мог изменить данные, хотя исходное отображение было доступно ему только для чтения.

Уязвимость не давала root напрямую. Она предоставляла примитив записи — возможность изменить защищённые данные. Для повышения привилегий требовалось найти подходящую цель и добиться выполнения изменённого кода или использования новых учётных данных.

Почему исправленная в 2016 году уязвимость всё ещё интересна

Dirty COW исправили в ядре Linux в октябре 2016 года. В актуальных поддерживаемых системах этот патч давно присутствует.

Однако Linux используется не только на обычных серверах и рабочих станциях. Его ядро работает внутри роутеров, камер, контроллеров и другого встраиваемого оборудования. Такие устройства могут годами оставаться на версии прошивки, установленной производителем.

Это не означает, что любое старое устройство уязвимо. Наличие Dirty COW нужно проверять по версии ядра, патчам производителя и фактическому поведению системы. Производитель мог перенести исправление в старую ветку, не меняя номер основной версии.

Поэтому одной строки из uname -a недостаточно для окончательного вывода. Версия помогает сформировать гипотезу, а подтверждать её следует по истории обновлений, конфигурации и результатам безопасной проверки на разрешённом стенде.

Почему готовый эксплойт может не дать root

Большинство публичных эксплойтов Dirty COW используют один из двух понятных сценариев.

Перезапись /etc/passwd

Файл /etc/passwd содержит сведения о локальных пользователях, включая их идентификаторы. Один из классических вариантов эксплуатации добавляет в него новую запись с UID 0 — идентификатором пользователя root.

Но самой записи недостаточно. В системе должен существовать способ войти под новым пользователем или переключиться на него. На минимальном устройстве может не быть подходящего login, su, SSH-сервера с парольной аутентификацией или другого нужного механизма.

Тогда файл изменён, а воспользоваться новой учётной записью невозможно.

Замена SUID-бинарника

SUID — специальный бит прав доступа. Когда пользователь запускает такой файл, процесс получает права владельца файла. Если SUID-бинарник принадлежит root, его можно попытаться заменить кодом, который запускает командную оболочку с повышенными привилегиями.

Этот вариант требует, чтобы в системе уже находился подходящий SUID-файл. Если таких файлов нет, заменять нечего.

Обычный бинарник тоже можно повредить или изменить, но он запустится с правами текущего пользователя. Сам по себе root-доступ от этого не появится.

Оба примера показывают главное: успешная запись — только один элемент цепочки. Результат зависит от того, куда записаны данные и что система сделает с ними дальше.

Что меняет BusyBox

Во встраиваемых Linux-системах вместо полного набора GNU-утилит часто используется BusyBox.

BusyBox объединяет множество команд в одном исполняемом файле. Один и тот же бинарник может работать как sh, ls, cat, mount и десятки других программ. Конкретная функция называется applet.

BusyBox определяет нужный applet по имени запуска и аргументам. Например, оболочка может быть доступна через символьную ссылку /bin/sh, ведущую на BusyBox, или через команду вида:

busybox sh

Из-за этого payload, рассчитанный на обычный /bin/sh, не всегда подходит минимальной системе. В ней может отсутствовать ожидаемый путь, нужный applet может быть отключён при сборке, а переданные аргументы могут не соответствовать способу запуска BusyBox.

В таком случае основная часть эксплойта способна сработать: состояние гонки возникает, защищённые данные меняются. Ошибка появляется на последнем этапе, когда payload пытается запустить отсутствующую или неправильно вызванную оболочку.

Почему готовый PoC Dirty COW не дал root: уязвимость срабатывает, но payload не подходит окружению BusyBox и цепочка повышения привилегий обрывается

Поэтому сообщение «эксплойт отработал без ошибок» нельзя считать доказательством успешного повышения привилегий. Нужно отдельно проверять каждый этап цепочки.

vDSO: код, которого нет в файловой системе

Если /etc/passwd и SUID-бинарники не подходят, эксплойту нужна другая цель. В отдельных вариантах атаки Dirty COW такой целью становилась vDSO — virtual dynamic shared object.

vDSO — это небольшая ELF-библиотека, которую ядро автоматически отображает в адресное пространство пользовательских процессов. Она позволяет выполнять некоторые часто используемые операции без полноценного переключения из пользовательского режима в режим ядра.

Например, программа может получать системное время через функцию, предоставленную vDSO. Это быстрее, чем каждый раз выполнять обычный системный вызов.

В карте памяти процесса vDSO обычно видна как отдельная область:

[vdso]

При этом файла vdso.so, который можно открыть в файловой системе, нет. Код vDSO собирается вместе с ядром, а затем отображается в память процессов при их запуске.

vDSO в Linux: файла vdso.so нет в файловой системе, но код vDSO отображается в память пользовательских, системных и root-процессов

Набор функций зависит от архитектуры, ABI и версии Linux. Например, на разных платформах vDSO может предоставлять clock_gettime(), gettimeofday(), time() или другой набор функций. Адрес области также не следует считать постоянным: при запуске процесса он обычно рандомизируется.

Поэтому эксплойт, рассчитанный на конкретную vDSO-функцию, нельзя без проверки переносить между архитектурами и версиями ядра.

Как vDSO связывается с Dirty COW

Некоторые публичные PoC для Dirty COW используют vDSO как цель для записи. Общая идея выглядит так:

  • эксплойт находит vDSO в памяти;
  • определяет расположение нужной функции;
  • пытается изменить её код через примитив Dirty COW;
  • ожидает, пока эту функцию вызовет процесс с повышенными правами;
  • внедрённая полезная нагрузка выполняется в контексте этого процесса.
Схема использования vDSO с Dirty COW: код изменяется в памяти и затем выполняется в контексте процесса с повышенными привилегиями

Это не универсальное свойство любой системы с Dirty COW. Возможность такого сценария зависит от версии ядра, архитектуры, устройства vDSO, способа отображения страниц и конкретной реализации эксплойта.

Также недостаточно просто записать произвольные байты в vDSO. Payload должен:

  • состоять из инструкций для нужной архитектуры;
  • соответствовать ABI целевой системы;
  • помещаться в доступное пространство;
  • не разрушать выполнение раньше времени;
  • использовать доступные системные вызовы и пути;
  • корректно работать с установленным BusyBox или другой пользовательской средой.

Если хотя бы одно предположение неверно, изменение памяти может произойти, но ожидаемого результата не будет.

Почему payload из публичного PoC приходится менять

Готовый PoC обычно создаётся и тестируется в определённом окружении. В коде могут быть зашиты:

  • архитектура процессора;
  • порядок байт;
  • адреса или способы поиска символов;
  • имя функции vDSO;
  • путь к командной оболочке;
  • аргументы для execve();
  • сетевой адрес и порт для reverse shell.

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

Например, payload пытается выполнить /bin/sh, а на устройстве доступен только BusyBox без такой символьной ссылки. Или код собран для процессора с другим набором инструкций. Или нужная vDSO-функция отсутствует в используемой версии ядра.

Вместо случайного изменения эксплойта полезно разобрать его на этапы:

  • Как PoC определяет, что ядро уязвимо?
  • Как он находит vDSO?
  • Какой символ пытается изменить?
  • Какой код записывает?
  • Как должно начаться выполнение payload?
  • Какие файлы, команды и аргументы он ожидает увидеть?
  • Как проверяется успешное повышение привилегий?

Так становится понятно, сработал ли сам примитив Dirty COW и на каком этапе ломается остальная цепочка.

Всегда ли нужен reverse shell

Reverse shell удобен, но для учебной задачи он может быть избыточен.

Чтобы открыть интерактивную оболочку, payload должен создать сетевое соединение, перенаправить потоки ввода и вывода, запустить интерпретатор и правильно сформировать аргументы. Каждый шаг добавляет зависимость от целевого окружения.

Иногда достаточно более простого результата, который подтверждает выполнение кода с нужными правами. Конкретная цель зависит от условий стенда и задания.

Это сокращает payload и уменьшает количество предположений о системе. Но даже минимальный код нужно адаптировать под архитектуру, ABI и ограничения конкретного устройства.

Что проверить перед запуском эксплойта

Ядро

  • версия и дата сборки;
  • наличие исправления Dirty COW;
  • архитектура и разрядность;
  • порядок байт;
  • включённые защитные механизмы.

Пользовательское окружение

  • доступные файлы и каталоги;
  • набор applet BusyBox;
  • наличие /bin/sh, login и su;
  • файлы с SUID-битом;
  • права на запись и исполнение;
  • доступные способы передачи бинарника.

vDSO

  • присутствует ли она в карте памяти;
  • какие символы экспортирует;
  • какая функция используется выбранным PoC;
  • соответствует ли эксплойт архитектуре и ABI;
  • какое событие должно привести к выполнению изменённого кода.

Payload

  • под какую архитектуру он собран;
  • какие системные вызовы использует;
  • какие пути и аргументы содержит;
  • зависит ли от конкретной libc;
  • можно ли проверить его отдельно от Dirty COW.

Работать с такими техниками следует только на собственных системах, учебных стендах или в рамках согласованного тестирования. Эксплуатация состояния гонки способна повредить данные или нарушить работу системы, поэтому для экспериментов нужен изолированный стенд с возможностью быстрого отката.

Отработайте знания на практике

Strange And Dirty — учебная задача, построенная вокруг CVE-2016-5195.

По условиям задания доступ к устройству уже получен, а его ядро уязвимо к Dirty COW. Однако стандартный эксплойт не приводит к повышению привилегий. Участнику нужно разобраться, почему готовый сценарий не подходит этому окружению, и довести атаку до результата.

Статья объясняет основные элементы задачи, но не содержит готового решения. Конкретные параметры стенда, архитектуру и необходимые изменения нужно определить самостоятельно.

Где регулярно тренироваться на таких задачах

Чтобы научиться работать с незнакомыми системами, нужна регулярная практика: собирать информацию, проверять гипотезы, разбирать причины неудач и связывать отдельные техники в рабочую цепочку.

Для этого в CyberED есть бесплатный курс «Профессия Белый Хакер».

Участники работают с практическими стендами на платформе Labs, смотрят воркшопы по техникам атак и решают задачи с постепенным усложнением. Онлайн-встречи можно проходить в прямом эфире или смотреть в записи.

Программа состоит из десяти уровней и постепенно охватывает основные этапы работы с инфраструктурой. Участники начинают с разведки внешней сети и получения первоначального доступа, затем переходят к закреплению, повышению привилегий в Linux, выходу за пределы DMZ, пробросу трафика, работе с Windows-инфраструктурой и противодействию обнаружению.

Практические задания по этим темам собраны в бесплатном курсе «Профессия Белый Хакер», где участники работают с учебными стендами и разбирают техники на практике.

01001
SOC
Security Vision SIEM
ЭКСПЕРТИЗА И АНАЛИТИЧЕСКИЕ ИНСТРУМЕНТЫ SOC — В ЕДИНОМ КОНТУРЕ.
ОТ КОНТРОЛЯ ДАННЫХ И ВЫЯВЛЕНИЯ АТАК ДО РАССЛЕДОВАНИЯ И РЕАГИРОВАНИЯ.
УЗНАТЬ БОЛЬШЕ
18+. Реклама. Рекламодатель ООО «Интеллектуальная безопасность», ИНН 7719435412