На 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()), но ещё не передан целевому процессу. Из сырого буфера поэтапно вытаскивается:- Из
write_buffer- командаBC_TRANSACTIONс целевым handle - Из заголовка транзакции - interface descriptor:
com.android.internal.telephony.ISms - Transaction code - числовое значение, соответствующее
sendTextMessageв AIDL-интерфейсе - Через signature table transaction code превращается в имя метода и список типов аргументов
- Аргументы десериализуются из 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 отличается от аналогов
| Критерий | ClearScope | BPFroid / binder-trace | Frida-хуки | 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 до реконструкции семантики - с лабами на реальных устройствах.