Распечатанная схема архитектуры Android Binder IPC на кремовой бумаге, прижатой латунными грузами к тёмному столу. Слои от dev/binder до ActivityManagerService соединены стрелками от руки, мягкий д...


На Pixel 9 с Android 16 eBPF-трассировщик перехватил на 33% больше системных вызовов, чем штатный ftrace. Overhead - не выше 3.6% по Geekbench. И параллельно - декодировал имена методов и типизированные аргументы из сырых Binder-транзакций. Без патчей к ядру, без пересборки AOSP, без инъекций в процесс приложения.

Это WOOTdroid - исследовательский проект TU Darmstadt и Athens University of Economics and Business. Он решает проблему, над которой мобильный forensics бьётся десять лет: семантический разрыв между тем, что видит ядро Linux, и тем, что реально делает Android-приложение.

В русскоязычном пространстве Binder IPC покрыт архитектурными обзорами 2014–2020 годов - о практической трассировке Binder-транзакций для обнаружения вредоносного ПО и runtime-мониторинга публикаций нет. Закрываем пробел.

Почему ядро Android не видит поведение приложений​

Sandbox-архитектура Android построена просто: каждому приложению - уникальный UID/GID, каждое работает в изолированном процессе. Прямого доступа к железу, системным данным и функциям других процессов нет. Когда приложению нужно отправить SMS, получить GPS-координаты или запросить список контактов - оно обращается к системному сервису через Binder IPC.

Binder - kernel module в drivers/android/binder.c, посредник между клиентским процессом и системным сервисом. Клиент вызывает ioctl() на символьном устройстве /dev/binder (DAC-права 0666, но реальный доступ контролирует SELinux-политика binder_use/binder_call - без нужного домена SELinux блокирует ioctl даже при открытом fd) с командой BINDER_WRITE_READ, передавая структуру:
C:
struct binder_write_read {
    signed long write_size;
    signed long write_consumed;
    unsigned long write_buffer;
    signed long read_size;
    signed long read_consumed;
    unsigned long read_buffer;
};
Внутри write_buffer лежит сериализованный Parcel - бинарный контейнер с командой BC_TRANSACTION, целевым handle сервиса и аргументами вызова. Ядро копирует байты в адресное пространство целевого процесса. Binder использует single-copy IPC через mmap-буфер между ядром и процессом-получателем. С Android 8 scatter-gather оптимизация дополнительно устранила промежуточную сериализацию для составных структур внутри Parcel (документация AOSP). Системный сервис десериализует Parcel, выполняет операцию, формирует ответный Parcel и возвращает через BR_REPLY.

Вот тут и начинается семантический разрыв. Ядро в момент транзакции видит ровно три вещи: PID/UID вызывающего процесса, целевой Binder handle (32-битный токен от драйвера) и сырые байты Parcel. Ядро понятия не имеет, что handle 7 - это ISms.sendTextMessage, а аргументы содержат номер телефона и текст. Для ядра отправка SMS и запрос версии ОС - один и тот же ioctl(fd, BINDER_WRITE_READ, &bwr).

Принципиальное отличие от десктопного Linux. На сервере приложение для отправки данных вызывает sendto() - аудитор через auditd видит syscall с аргументами. На Android аналогичное действие проходит через цепочку app → libbinder.so → ioctl → binder driver → system_server → native API, и на уровне мониторинга системных вызовов всё выглядит одинаково. Как сформулировано в WOOTdroid: Binder parcels carry no method names or typed arguments - любая система аудита Android, ограниченная syscall-трассировкой, слепа к подавляющему большинству поведения приложений.

Существующие подходы и их архитектурные ограничения​

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

Модификация ОС (ClearScope и аналоги). ClearScope решает семантический разрыв в лоб: аудит-хуки внедряются в Binder driver и framework-слой через модификацию AOSP. По данным WOOTdroid, ClearScope показал высокую эффективность в задачах intrusion detection. Проблема - каждое обновление Android требует ручного переноса патчей, каждая модель устройства - отдельной сборки. Для лаборатории с парой устройств это терпимо. Для деплоя в организации или аудита актуальных версий - архитектурный тупик. Подход стареет быстрее, чем выходят новые версии Android.

User-space инструментация (BPFroid, binder-trace). Перехват вызовов Android Framework API в user-space - до сериализации данных в Parcel. Семантика доступна: метод и аргументы видны на уровне Java/Kotlin-кода. Критический недостаток - обход. Приложение с native-кодом (JNI/NDK) может дёрнуть ioctl() на /dev/binder напрямую, минуя Framework API и все user-space хуки. WOOTdroid демонстрирует конкретный пример: приложение отправляет SMS через прямой ioctl(), формируя Parcel вручную на native-уровне - user-space трассировщик пропускает транзакцию полностью.

Frida-подход. Та же уязвимость: хуки устанавливаются в адресное пространство целевого процесса и обнаруживаемы. OWASP MASVS-RESILIENCE описывает техники anti-debug и anti-tamper, противодействующие Frida-подобной инструментации. Малварь использует те же техники для обхода user-space мониторинга. Плюс Frida требует root-доступа или инъекции frida-gadget в APK - а это модификация целевого приложения и изменение его поведения.

Общий паттерн: решения в ядре (ClearScope) устойчивы к обходу, но требуют модификации ОС. Решения в user-space (BPFroid, Frida) не требуют модификации, но обходимы. WOOTdroid разрывает эту дихотомию.

Архитектура WOOTdroid: eBPF без модификации ОС​

WOOTdroid - два независимых модуля, оба работают через extended Berkeley Packet Filter (eBPF). Это механизм загрузки sandbox-программ в ядро Linux без пересборки и без kernel modules. eBPF-программы прикрепляются к tracepoints - стабильным hook-points, гарантированно присутствующим в новых версиях ядра. Tracepoints не меняются между версиями, в отличие от внутренних структур - и это критично для адаптации к обновлениям Android.

WDSys: трассировка syscalls на stock Android​

WDSys - адаптация eAudit-подхода (эффективного аудита syscalls на Linux-хостах) для мобильной платформы. Проблема, которую решает модуль: стандартный ftrace и tracepoint-механизм Android теряет события под нагрузкой. Ring-буфер переполняется, новые записи затирают старые быстрее, чем user-space reader успевает вычитать. На desktop-Linux это лечится увеличением буферов и мощностью CPU. На мобильном устройстве с ограниченной памятью и батареей ресурсы лимитированы - Linux Audit System (auditd) с его аппетитами к диску и CPU неприменим.

WDSys использует eBPF-программы на tracepoints системных вызовов (tracepoint/syscalls/sys_enter_*). Отличие от прямого ftrace - in-kernel фильтрация: eBPF-программа отбрасывает нерелевантные события до попадания в ring buffer, снижая объём данных для user-space reader. При нагрузке top-100 приложений из Google Play на Pixel 9 (Android 16) WDSys перехватывает на 33% больше syscalls, чем ftrace, с нулевой потерей событий. Максимальный overhead - 3.6% по Geekbench.

Исследователи явно отмечают: портирование eAudit на Android потребовало решения Android-специфичных eBPF-ограничений. Android накладывает дополнительные constraints по сравнению со стандартным Linux - ограничения на размер программ, доступные helper-функции и типы хуков. WDSys обходит их, оставаясь в рамках stock Android без модификаций.

WDBind: захват и декодирование Binder-транзакций в ядре​

WDBind - центральный компонент WOOTdroid, решающий семантический разрыв при аудите Android. Архитектура двухуровневая.

Kernel-level захват. eBPF-программа подключается к tracepoint на входе в syscall ioctl() и перехватывает буфер binder_write_read из user-space через bpf_probe_read_user(). На этом этапе WDBind получает сырой Parcel - бинарный блоб с BC_TRANSACTION, handle целевого сервиса и сериализованными аргументами.

Концептуальный пример eBPF-программы для перехвата Binder-транзакций:
C:
// пример для демонстрации концепции
SEC("tracepoint/syscalls/sys_enter_ioctl")
int trace_binder(struct trace_event_raw_sys_enter *ctx) {
    unsigned int cmd = (unsigned int)ctx->args[1];
    if (cmd != BINDER_WRITE_READ) return 0;
    struct binder_write_read bwr = {};
    bpf_probe_read_user(&bwr, sizeof(bwr), (void*)ctx->args[2]);
    // NB: это только верхний уровень - далее требуется
    // bpf_probe_read_user() по адресу bwr.write_buffer для
    // извлечения BC_TRANSACTION и Parcel-данных;
    // полная цепочка опущена для краткости.
    bpf_ringbuf_output(&events, &bwr, sizeof(bwr), 0);
    return 0;
}
Вся фильтрация - по коду команды BINDER_WRITE_READ - выполняется в ядре. До user-space доходят только Binder-транзакции, а не весь поток ioctl().

User-space декодирование. Сырой Parcel передаётся в отдельный user-space процесс для семантической реконструкции. WDBind использует framework signature table - таблицу маппинга {interface descriptor + transaction code} → {method name + типы аргументов}. Таблица извлекается заранее через Java reflection по всем AIDL-интерфейсам в classpath Android Framework.

Как это работает: interface descriptor (например, com.android.internal.telephony.ISms) идентифицирует целевой сервис, transaction code - конкретный метод в интерфейсе. Зная типы аргументов из signature table, декодер последовательно десериализует Parcel: String, int, PendingIntent и другие типы читаются в правильном порядке с правильными размерами.

Архитектурное следствие: захват происходит в ядре (любой ioctl() проходит через tracepoint - обойти невозможно), а декодирование - вне процесса приложения (не нужна инъекция, нет риска обнаружения или изменения поведения). Это точка перехвата, недоступная ни одному из предыдущих подходов.

Реконструкция семантики: от сырого Parcel к имени метода​

Конкретный пример из case study WOOTdroid. Приложение вызывает отправку SMS через прямой ioctl() к Binder, минуя SmsManager API. На уровне ядра - обычный ioctl(fd, BINDER_WRITE_READ, &bwr).

WDBind перехватывает момент, когда Parcel полностью сформирован в user-space (точка входа в ioctl()), но ещё не передан целевому процессу. Из сырого буфера поэтапно вытаскивается:
  1. Из write_buffer - команда BC_TRANSACTION с целевым handle
  2. Из заголовка транзакции - interface descriptor: com.android.internal.telephony.ISms
  3. Transaction code - числовое значение, соответствующее sendTextMessage в AIDL-интерфейсе
  4. Через signature table transaction code превращается в имя метода и список типов аргументов
  5. Аргументы десериализуются из Parcel: номер телефона (String), текст SMS (String), PendingIntent (Binder object reference)
Для аудитора результат - вместо записи ioctl(7, 0xc0306201, 0x7fc3a12000) лог содержит ISms.sendTextMessage("+7XXXXXXXXXX", "text", ...). Семантический разрыв закрыт.

Исследователи продемонстрировали end-to-end реконструкцию десяти security-relevant Binder-транзакций, включая эту SMS-отправку. User-space трассировщики (BPFroid, Frida-хуки) пропускают её полностью - приложение обходит Framework API.

Binder как поверхность атаки: маппинг на MITRE ATT&CK​

Binder - единственный штатный канал IPC в Android. Через него проходят Intents, Content Provider-запросы, вызовы системных сервисов. Это делает Binder ключевой поверхностью атаки, покрывающей несколько тактик MITRE ATT&CK.

Execution. Малварь использует Binder для выполнения действий чужими руками - через системные сервисы. Отправка SMS, звонки, доступ к камере: всё это - обращения через Inter-Process Communication (T1559). При обходе Framework API через прямой ioctl() к /dev/binder малварь спускается на уровень Native API (T1106) - ядерный интерфейс вместо Java-абстракций.

Discovery. Обращение к ServiceManager (handle 0, единственный сервис с известным токеном) с командой LIST_SERVICES_TRANSACTION перечисляет все зарегистрированные сервисы - System Service Discovery (T1007). Запросы к TelephonyManager, Build раскрывают информацию об устройстве - System Information Discovery (T1082). Перечисление активных процессов через ActivityManagerService - Process Discovery (T1057).

Defense Evasion и Privilege Escalation. "Man in the Binder" (Nitay Artenstein, Idan Revivo, Black Hat Europe 2014) продемонстрировал rootkit-имплант, перехватывающий IPC-транзакции для манипуляции сетевым трафиком - Rootkit (T1014). Уязвимости в Binder driver - use-after-free и race condition в обработке binder_thread - исторически использовались для Exploitation for Privilege Escalation (T1068).

WOOTdroid наблюдает каждую из этих техник: WDSys фиксирует ioctl() к /dev/binder на уровне syscall, WDBind реконструирует семантику - конкретный сервис, метод, аргументы. OWASP MASVS-PLATFORM явно включает IPC-риски (intents, deep links, WebView-мосты) в категорию platform-specific threats - Binder тот самый нижний слой, через который все эти механизмы работают.

Чем WOOTdroid отличается от аналогов​

КритерийClearScopeBPFroid / binder-traceFrida-хукиWOOTdroid
Модификация ОСДа, пересборка AOSPНетНетНет
Инструментация приложенияНетДа, user-spaceДа, user-spaceНет
Обход через native ioctlНевозможенВозможенВозможенНевозможен
Адаптация к новым версиям ОСРучная пересборкаОбновление хуковОбновление скриптовОбновление signature table
Семантическая реконструкцияНативная (внутри ОС)ДаДаДа, out-of-process
Overhead (Geekbench)Нет публичных данныхНет публичных данныхВарьируетсяНе более 3.6%
Полнота syscall-трассировкиНет публичных данныхНе применимоНе применимо+33% vs ftrace
Протестировано наНет данных в источникахНет данных в источникахШирокий спектрPixel 9, Android 16

Архитектурная суть: ClearScope работает в ядре (устойчив к обходу, но требует модификации ОС). BPFroid и Frida работают в user-space (не требуют модификации, но обходимы). WOOTdroid перехватывает в ядре через eBPF (обход невозможен) и восстанавливает семантику вне процесса приложения через signature table (модификация ОС не нужна). Компромисс между инвазивностью и полнотой, ранее не реализованный.

Ограничения и слепые зоны​

Было бы нечестно не сказать о граблях.

Зависимость от signature table. Таблица маппинга извлекается через Java reflection из конкретной версии Android Framework. Обновление ОС - перегенерация. Для чистого AOSP это автоматизируемо. Для вендорских расширений (Samsung Knox, Huawei HMS) - нужен отдельный анализ каждого вендорского интерфейса. На практике это означает: получил дамп framework JAR-файлов с устройства, прогнал reflection, получил таблицу. Муторно, но решаемо.

Шифрование Parcel. Если малварь шифрует содержимое Parcel при формировании через native код, WDBind увидит транзакцию (факт обращения к сервису и transaction code), но аргументы не декодирует. Семантика вызова сохраняется, семантика данных - теряется. На практике такое встречается редко: шифрование Parcel ломает стандартную десериализацию на стороне системного сервиса, так что малвари приходится изобретать отдельный канал, а это уже другой вектор.

Overhead на бюджетных устройствах. 3.6% Geekbench - результат на Pixel 9 (Tensor G4). На бюджетных SoC overhead может быть заметно выше. Исследование ограничено одной моделью - и это честно обозначено авторами.

Scope трассировки. WDSys и WDBind покрывают syscall-уровень и Binder IPC. Взаимодействие через shared memory (ashmem), Unix domain sockets (Zygote) или прямые файловые операции - за пределами scope. Для полного динамического анализа Android нужны дополнительные eBPF-хуки на соответствующие подсистемы.

eBPF на production-устройствах. Загрузка произвольных eBPF-программ на stock Android ограничена - требуется root/unlocked bootloader или ADB-доступ. WOOTdroid пригоден для forensics-лаборатории и исследовательского стенда, но не для прозрачного мониторинга на устройстве конечного пользователя без модификаций. Пока что.

Развёртывание аудита Binder IPC на стенде​

Требования к окружению​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Большинство дискуссий об аудите Android сводится к двум крайностям: статический анализ APK до установки (Google Play Protect, sandboxing) или post-mortem forensics с физическим образом устройства. Между ними - практически пустое пространство runtime monitoring, Android runtime monitoring в реальном времени. WOOTdroid занимает именно эту нишу.

По опыту работы с AOSP-исходниками и кастомными LSM-хуками, у eBPF-подхода есть одно принципиальное преимущество перед модификацией ядра: совместимость с Verified Boot и dm-verity. Образ системы не меняется - механизмы целостности не срабатывают. Это открывает архитектурный путь к деплою на устройствах с locked bootloader, если Google или вендоры добавят API для загрузки аудит-eBPF-программ. Аналогия - Android Enterprise security logging, но с Binder-семантикой.

Пока этого нет. Но WOOTdroid закладывает правильный паттерн: ядро перехватывает, user-space интерпретирует, приложение не знает. Через два-три release cycle Android я ожидаю появление встроенных eBPF audit hooks в Binder driver - если не от Google, то от Samsung Knox или GrapheneOS. А до тех пор единственный способ увидеть, что приложение реально делает на устройстве (а не что декларирует в манифесте) - собирать стенд и трассировать руками. На курсе WAPT разбирают цепочку от перехвата IPC до реконструкции семантики - с лабами на реальных устройствах.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab