Pull to refresh

Реализуем тач логгер под Android с помощью CVE-2016–5195

Reading time 16 min
Views 8.6K

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


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

Я расскажу о своём пути решения этой задачи. Он был долгим, тернистым, но чертовски увлекательным! Надеюсь, вам будет интересно проследить за ним и узнать для себя что-то новое о linux, android, их безопасности и их внутренностях. Я не гуру в устройстве linux, поэтому кому-то некоторые объяснения покажутся очевидными и излишне подробными, но повторюсь, это мой путь и я подробно описываю всё, что изучил в процессе. Надеюсь, это не оттолкнёт опытных линуксоидов и немного снизит порог вхождения для всех остальных. Итак. Как же реализовать тач логгер под android?

Выбор пути


Самое простое и очевидное решение — написать отдельное приложение и собирать параметры жестов только в нём, как это сделано в [1]. Но это совсем неинтересная задача. И не только потому, что это слишком просто. Ограничив сбор данных одним приложением, есть риск упустить какие-то важные поведенческие характеристики пользовательских жестов, которые проявляются только при особых условиях. Поэтому хотелось бы собирать жесты вне зависимости от приложения, в котором работает пользователь, правда сделать это не так просто.

Мобильные ОС защищены на порядки лучше настольных. Они, в отличие от последних, разрабатывались уже в те времена, когда над безопасностью и изоляцией процессов серьёзно задумывались, и тот же Android просто не позволяет вам отслеживать пользовательские жесты за пределами view вашего приложения. Подозреваю, в других мобильных ОС такая же ситуация. Но неужели всё настолько хорошо защищено, что для получения координат касаний пришлось прибегнуть к помощи уязвимостей ядра linux?

Многие сейчас, например, вспомнили про android accessibility service — сервис универсального доступа. Удобная штука для помощи людям с ограниченными возможностями или написания различного рода троянцев под Android. Можно подписаться в приложении на события универсального доступа и получать в том числе данные о пользовательских жестах. Казалось бы! Бери и пользуйся, всё уже написано за тебя. Но увы, нужные мне параметры жестов из этих данных не извлечь.

Что ж, остаётся не так много вариантов. Попробуйте, например, включить в меню разработчика пункт «отображать касания».



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


Выделенная строчка позволяет объекту mPointerLocationView обрабатывать все события ввода, поступающие от сенсорного экрана

Если изучить иерархию классов и граф вызовов, станет ясно, что в итоге все сводится к вызову метода registerPointerEventListener у системного сервиса WindowManagerService. Последний через интерфейс WindowManager позволяет обычным приложениям вызывать свои методы удалённо. Кажется, достаточно получить в приложении доступ к WindowManager, вызвать этот метод, и приложение сможет обрабатывать координаты, приходящие из системы. Но есть подвох: в Android доступ к системным сервисам предоставляется через Binder IPC, и у WindowManager можно вызвать лишь те методы, которые находятся в соответствующем AIDL файле. К сожалению, нашего метода в AIDL нет, и удалённо вызвать его из пользовательского приложения не удастся. Но если вы вендор, и не знаете, как ещё можно следить за пользователями, можно изменить исходники android и добавить логгирование жестов прямо в прошивку устройства, что я и сделал ради интереса здесь. Правда в нашем случае такое решение не подходит. Можно прошить одно, два устройства для сбора данных, но десятки незнакомых тестовых пользователей на такой шаг не пойдут. Необходим более универсальный способ.

Что ж, вот оно, решение, которому я всеми силами пытался найти альтернативу, но не смог. Итак, Android основан на ядре linux, и архитектура системы ввода устроена так же, как и в любом linux дистрибутиве. Драйверы устройств ввода, тачскрина в том числе, передают данные в userspace через символьные устройства, расположенные в /dev/input/, откуда их можно прочитать. И уж напрямую от драйвера можно получить все данные, которые только можно пожелать: координаты, метки времени, площадь касания, тип жеста… Хватит не на одно исследование. Правда, здесь тоже есть подвох: в Android доступ к этим устройствам имеет либо root, либо пользователь, состоящий в группе input. Android приложения же запускаются от имени пользователей, которые такими правами похвастать не могут.

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

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

CVE-2016–5195 aka DirtyCOW



Собственной персоной

История обнаружения и исправления этой уязвимости сама по себе очень занимательная. Ее случайно заметили 11 лет назад, но так и не закрыли. Повторно она была обнаружена и уже исправлена только в 2016 году, после обнаружения готового эксплойта, использовавшего её. Вот так уязвимость с 11-летней историей превратилась в 0-day. Что примечательно, она присутствует на всех версиях ядра, начиная ещё с 2.6.22! То есть на всех версиях android. В бюллетенях безопасности android эта уязвимость появилась в декабре 2016, а значит, исправлена она только на тех устройствах, которые получали обновления безопасности после этой даты. Nexus 5, например, так и остался ей подвержен, как и сотни других android устройств.

Что же делает dirtyCOW? Если вкратце, она позволяет перезаписать любой файл, доступный пользователю только на чтение. Под «любой» я имею в виду ЛЮБОЙ, даже если сама файловая система смонтирована только на чтение (в android именно так обстоят дела с разделом /system, где хранятся самые вкусные системные файлы). Подробно про механизм действия уязвимости можно прочитать, например, здесь. Нам же стоит помнить лишь две вещи. Первое: изменения в read-only файловой системе исчезнут после перезагрузки, и второе: нельзя менять размер файла, то есть записывать в него больше, чем он занимает места на диске.

Что ж, властью, данной нам уязвимостью, мы можем временно перезаписать любой системный файл, доступный на чтение. Казалось бы, это развязывает нам руки, можно творить все, что угодно! Но повторюсь, android неплохо защищён, и придется постараться, чтобы обойти эту защиту и добиться поставленных целей.

Идея реализации


Наша задача — из обычного приложения запустить процесс, способный читать данные из устройств ввода. Напомню, доступ к устройству ввода имеет только процесс, запущенный от пользователя с UID=0 (root) или состоящего в группе input (в android она имеет GID=1004).

Рассмотрим, например, файл /system/bin/ping. Его можно найти практически во всех android-устройствах, и он доступен на чтение и исполнение всем желающим. Что примечательно, у этого файла выставлен SUID-бит. SUID (Set owner User ID up on execution — выставить UID владельца при исполнении) — один из атрибутов файла в Linux. Обычно процесс наследует права пользователя, запустившего его. Наличие SUID бита позволяет запустить процесс с правами владельца исполняемого файла, а так же с его UID и GID. Соответственно, поскольку владельцем ping является root, то и выполнится этот файл от имени root. Понимаете, к чему я клоню? Если мы перезапишем ping с помощью dirtyCOW и запустим его, то наш код выполнится от имени root и сможет прочитать устройства ввода! Что ж господа, поздравляю, у нас получилось, все большие молодцы, расходимся… Нет.

SELinux


Да, наш план потрясающе сработает на всех версиях android вплоть до 4.2. Но на более свежих версиях могут возникнуть, а начиная с 5.0 обязательно возникнут некоторые трудности. Виной всему SELinux (Security-Enhanced Linux) — модуль ядра, реализующий мандатную политику безопасности вдобавок к уже существующей дискреционной. Вкратце о том, как это работает. У каждого пользователя, процесса, файла, сетевого порта, символьного устройства и так далее есть так называемый контекст безопасности — некоторая метка, дополнительный атрибут. В системе существует набор политик, который описывает разрешённые для пользователей и процессов операции на основании их контекстов. Все операции, которых нет в наборе политик, запрещены и строго пресекаются системой.

Сторонние приложения запускаются в процессах с контекстом untrusted_app. Уже из названия контекста следует, что прав у таких процессов немного. У файла /system/bin/ping контекст system_file, и да, SELinux не разрешает запускать system_file на исполнение из контекста untrusted_app. Поэтому запустить ping из приложения на android 5.0+ мы не сможем, как и другие системные файлы с SUID-битом.
На самом деле, контексты в SELinux состоят из нескольких частей, например, у ping контекст имеет полный вид u:object_r:system_file:s0, а у пользователя — u:r:untrusted_app:s0. В нашем случае это не имеет значения, поэтому я буду использовать более короткую запись.

Другие способы


Итак, в системе нет файлов с SUID-битом, доступных для запуска из сторонних приложений. Но что если перезаписать какой-то файл и сделать так, чтобы система сама его запустила от имени root? В android есть такое понятие, как сервис. Не тот, что запускается из вашего приложения и что-то делает в фоне. Сервис — это демон, стартующий при запуске системы.


Примеры сервисов, описанных в файле init.rc

Примечательно то, что android перезапускает сервисы, если они падают. Что ещё интереснее, многие сервисы запущены от root. В android 6.0.1 это vold, healthd, debuggerd, installd, zygote и многие другие. И да, файл /system/bin/app_process, он же zygote, доступен на чтение всем пользователям!


Сервис zygote, описанный в отдельном файле

Понемногу вырисовываются очертания нового плана. Если перезаписать /system/bin/app_process на наш payload и уронить сервис zygote, android перезапустит его и наш код в файле app_process выполнится от имени root.

Возникает вопрос, как уронить zygote. На самом деле, это самый ненадёжный момент в реализации тач-логгера. Сейчас я полагаюсь на то, что при перезаписи /system/bin/app_process нашим payload-ом zygote упадёт “сама”. Но нет никаких гарантий, что это будет срабатывать всегда. Давайте разберёмся, что происходит при перезаписи файла app_process и почему это может привести, а может и не привести к падению.

Виртуальная память и mmap


В Linux у каждого процесса есть своё виртуальное адресное пространство. В нём хранятся все нужные процессу данные: его стэк, куча, переменные среды и многое, многое другое. Это пространство транслируется ядром в физические адреса в оперативной памяти.


Хорошая визуализация этой концепции

Однако в этом же пространстве находится и исполняемый файл самого процесса, и его интерпретатор, и библиотеки из его зависимостей, и ещё много разных файлов. Хранить всё это в оперативной памяти было бы слишком расточительно, поэтому в linux есть довольно изящный механизм загрузки файлов в память процесса без расходования оперативной памяти вообще. Называется он memory-mapped file, или файл, отражённый в память. Ядро создаёт в адресном пространстве процесса страницу с содержимым файла, однако на самом деле эта виртуальная страница транслируется ядром не в оперативную память, а сразу на диск. То есть процесс работает с содержимым файла словно с большим куском выделенной памяти, хотя на самом деле он читает или пишет байты на диске. (кстати, если вы ещё не читали о принципе работы dirtyCOW, он как раз основан на багах при многопоточном доступе к отражённому в память файлу).

Именно в таком, «отражённом» виде хранится в памяти процесса его исполняемый файл. Частично он прогружается в память, но большая часть остаётся на диске. Поэтому изменения файла сразу же появляются и в памяти процесса. Допустим, после замены исполняемого файла процесс подгружает из памяти следующую инструкцию. С большой вероятностью она может прийтись «некстати» и привести процесс к падению, либо вообще окажется мусором, с тем же финалом. Однако может случиться и так, что процесс во время замены исполняемого файла будет «висеть» в блокирующем вызове, выполняя какой-нибудь select(), read() или waitpid(). В таком случае до окончания вызова с ним ничего не случится и он продолжит существовать. Вероятно, есть и другие сценарии выживания процесса после замены исполняемого файла, но я в силу небольшого опыта в linux с ними не знаком.

Мы выяснили, что происходит при перезаписи файла и как это может вызвать (или нет) падение процесса. Более надёжного способа перезапуска я так и не придумал, поэтому оставим все как есть. Тем более, работает этот способ все же достаточно хорошо (исключение — устройства samsung, у которых своя, самсунговская магия не даёт zygote упасть после перезаписи её исполняемого файла. Как это работает, я так и не выяснил).

Вернёмся к zygote. Что это за такой несущественный сервис, что я без проблем готов заменить его на какой-то другой бинарный файл? Zygote — это процесс, порождающий все android-приложения. Когда вы нажимаете на иконку того же clash of clans, zygote создаёт копию своего процесса с помощью fork(), и в этой копии запускает нужное приложение. Сделано это для экономии ресурсов и ускорения запуска. Если убить zygote, все android-приложения упадут вслед за ним. Такое положение вещей лишает наш тач логгер всякого смысла. Зачем собирать пользовательские жесты с кирпича? К счастью, эту проблему легко обойти.

Демоны


Во-первых, почему вдруг все android-приложения должны завершиться, если упадёт zygote? Ведь при завершении родительского процесса в linux дочерний продолжает прекрасно работать, а его новым родителем становится init. А дело в том, что zygote при запуске порождает новую группу процессов, или новую сессию. В этой сессии запускаются все android-приложения, все дочерние процессы андроид-приложений, их дочерние процессы и так далее. Когда завершается породивший сессию процесс, всё ветвистое дерево его потомков в той же сессии завершается вместе с ним. Так при закрытии эмулятора терминала завершатся все процессы, запущенные в нём. Вот почему zygote при падении обязательно утянет за собой в ад все android-приложения.

Как было упомянуто выше, это порождает некоторые проблемы. Первое. Кто вернёт app_process на место после того, как будет запущен payload, а наше приложение будет убито? И второе. Как оставить наш payload запущенным после возвращения app_process?

Чтобы процесс продолжил работать после завершения сессии, его необходимо отвязать от текущей сессии… Породив новую! Делается это с помощью системного вызова setsid(). Но можно поступить ещё хитрее. За один вызов можно запустить новый процесс, отвязать его от текущей сессии и отвязать его от стандартных потоков ввода\вывода. Этот волшебный вызов называется daemon() и он сочетает в себе мощь fork() и setsid(), порождая новый процесс-демон, который будет работать даже если родитель сгинет в аду вместе со всей своей сессией, ибо демон сам есть порождение ада.

C помощью daemon() прекрасно решаются обе упомянутые выше проблемы. Сначала мы создаём демона внутри приложения. Он переживёт падение zygote, поэтому он сможет перезаписать app_process на payload, дождаться его запуска, а затем вернуть app_process обратно.


Решение первой проблемы в картинках

Вторая решается аналогично: когда система запустит наш payload, мы создадим в ней другого демона, который будет работать и после восстановления app_process. Но будет ли? Падение процесса при перезаписи его исполняемого файла никто не отменял, от этого даже демонизация не спасает. От этого спасёт разве что замена исполняемого файла на лету. Так почему бы именно это и не сделать?

Execve()



Суть execve()

Этот системный вызов подменяет текущий процесс новым, запуская бинарный файл, указанный в аргументах. Подробнее прочитать об этом можно в соответствующем man.
Кстати, практически все процессы в linux запускаются связкой fork()+execve().

В итоге, система запускает наш payload. Мы внутри него делаем daemon() и execve() для запуска другого бинаря, назовём его exec_payload. Затем мы возвращаем app_process на место, но exec_payload продолжает работать.


Решение второй проблемы в картинках

Теперь мы знаем, как из обычного приложения запустить процесс от имени root. Точнее, как, убив половину системы, заставить ее выполнить наш код от имени root и вернуть всё ”как было”. Однако на этом трудности не заканчиваются. Помните про SELinux? Так вот, он никуда не делся и по-прежнему не разрешает выполнение многих операций.

SELinux[2]


Рассмотрим, в какой ситуации окажется наш exec_payload после запуска с точки зрения SELinux контекстов. У zygote контекст одноимённый — zygote. Exec_payload при запуске его наследует. У символьных устройств в /dev/input тоже есть свой контекст: input_device. И, ожидаемо, SELinux не разрешает процессам с контекстом zygote получать доступ к файлам с контекстом input_device. Неужели мы проделали весь этот путь только для того, чтобы снова упереться в SELinux?

Не совсем. На этот раз ситуация всё же немного лучше, и прав у нас побольше. Помните, что zygote запускает все android-приложения? Так вот, если у процесса с контекстом zygote рождается потомок с контекстом untrusted_app (или platform_app для встроенных приложений, или isolated_app… Кто бы знал для чего), это значит, что у zygote есть права менять selinux-контекст. Осталось найти нужный контекст, который имеет доступ к устройствам ввода. И найти его несложно: это shell.

Shell — это пользователь (обладающий одноимённым контекстом), от имени которого выполняются все команды, если зайти на android-устройство удалённо по ADB (Android debug bridge). У него прав поменьше, чем у root с контекстом init или kernel, но значительно больше, чем у android-приложений. В том числе, пользователь shell имеет доступ к устройствам ввода на чтение и запись. Поменяв контекст с zygote на shell и добавив себя в группу input, наш exec_payload наконец-то сможет получить доступ к вожделенным устройствам ввода.

Получение контекста shell из zygote
#ifdef __aarch64__
void * selinux = dlopen("/system/lib64/libselinux.so", RTLD_LAZY);
#else
void* selinux = dlopen("/system/lib/libselinux.so", RTLD_LAZY);
#endif

if (selinux)
{
  void* getcon = dlsym(selinux, "getcon");
  const char* error = dlerror();
  if (!error)
  {
    getcon_t* getcon_p = (getcon_t*) getcon;
    char* secontext;
    int ret = (*getcon_p)(&secontext);

    void* setcon = dlsym(selinux, "setcon");
    const char* error = dlerror();
    if (!error)
    {
      setcon_t* setcon_p = (setcon_t*) setcon;
      if ((*setcon_p)("u:r:shell:s0") != 0)
      {
        LOGV("Unable to set context: %s!", strerror(errno));
      }

      (*getcon_p)(&secontext);
      LOGV("Current context: %s", secontext);
    }
  }
  dlclose(selinux);
}
else
{
  LOGV("SELinux not found.");
}


И, наконец, осталось решить последнюю задачу. Как обмениваться данными между нашим (вновь запущенным после падения половины системы) приложением и демоном, читающим данные из /dev/input/event[X]? Возможно несколько вариантов. К сожалению, самые адекватные из них (UNIX socket, FIFO) недоступны нам (коварный SELinux!). Остаётся писать данные на SD-карту одним процессом и читать их оттуда другим процессом. Не самый крутой вариант, но всё же. Помимо этого, можно отправлять данные в приложение через специальные intent с помощью встроенной утилиты am (по сути, это командный интерфейс для Activity Manager). Возможно, есть другие способы, до которых я не додумался.

«Ручной» способ сбора данных


Использовать эксплойты для достижения различных целей — очень ненадёжно. Редко удаётся реализовать универсальное решение на основе эксплойта, которое будет работать на всех устройствах с любыми версиями Android. Моя реализация тач-логгера — не исключение. Не везде упадёт zygote после перезаписи app_process. Вероятно, не на всех устройствах SELinux позволит сменить контекст с zygote на shell нашему payload. И обязательно существуют и другие подводные камни, пока мне неизвестные.

Однако наша цель — собрать события ввода, и если тестовые пользователи согласны предоставить свои устройства для этого, можно внедрить ещё одно решение, простое и на 100% универсальное. Правда, для его запуска придётся проделать несколько ручных операций. Если закинуть exec_payload по adb в директорию /data/local/tmp (куда у shell есть доступ), и оттуда запустить его — демон начнёт работать точно так же, как если бы его запустило наше приложение. Единственное, будет отличаться UID (2000 вместо 0), но на доступе к устройствам ввода это никак не отразится. Так что в третьей версии своего тач-логгера (которая сейчас доводится до ума) я собираюсь предусмотреть и такой вариант в качестве крайней меры, оставив при этом и возможность запуска на рутованных устройствах как ещё один запасной вариант.

Другие применения DirtyCOW на Android


Понятное дело, не всегда (почти никогда) эксплойты не используются в таких благородных целях, как наука. И вам, наверное, интересно, какое ещё применение можно найти DirtyCOW, используя описанный выше способ повышения привилегий. Как вы понимаете, это не полноценный root, поэтому наши возможности весьма ограничены. Тем не менее, натворить бед можно и с этими правами. Первое: можно использовать доступ к устройствам ввода для похищения паролей (в тач-логгере я сделал кнопку «паузы» чтобы эти данные не попадали в мои и потенциально в чужие руки). Второе: можно использовать встроенные утилиты вроде am и pm. Это позволяет скрытно устанавливать и удалять приложения. Am в свою очередь позволяет получить информацию об activity, запущенной в данный момент поверх остальных. Идеальный функционал для банковского трояна, который при запуске банковского приложения будет рисовать поверх собственную форму логина. В общем, dirtyCOW, как и любой инструмент, можно использовать в разных целях, как в хороших, так и в не очень.

Один последний трюк


Напоследок хочу рассказать о самом интересном: ещё об одном способе выполнить ваш код от более привилегированного пользователя. Он позволит запускать код с привилегиями mediaserver, netd, debuggerd или других сервисов, если вам это вдруг понадобится.

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

Суть трюка очень проста и изящна. Практически у любого исполняемого файла в Android есть зависимость от системной библиотеки libcutils.so. И при запуске исполняемого файла эта библиотека будет подгружена в память процесса. Особенность разделяемых библиотек в формате ELF в том, что в них есть специальные секции .init и .init_array. Функции, адреса которых будут помещены в эти секции, выполнятся при подгрузке библиотеки в процесс. Соответственно, если мы скомпилируем библиотеку с функцией в секции .init_array и перезапишем ей файл /system/lib/libcutils.so, то при его подгрузке в процесс эта функция благополучно выполнится с привилегиями процесса.

__attribute__((constructor)) void say_hello()
{
  payload_main();
}

Функция с атрибутом constructor при компиляции помещается в секцию .init_array библиотеки.

Но насколько универсально такое решение? Не хотелось бы, чтобы у процессов в разных версиях Android возникли сложности с тем, что они не смогут найти те или иные функции в этой жизненно важной библиотеке. Действительно, перезаписывать сам libcutils довольно опасно. Но у него, в свою очередь, тоже есть зависимости, от других библиотек. Правда, и другие библиотеки довольно важны, и трогать их не хотелось бы. Здесь на помощь приходит ещё один, чуть менее простой, но не менее изящный хак.

DT_SONAME → DT_NEEDED


У каждой библиотеки в формате ELF есть заголовок, в котором находится вся нужная линковщику информация о библиотеке. Нас интересует информация о её зависимостях и о её имени. Узнать всё это можно, например, с помощью команды objdump -p:

Динамический раздел:
[.....]
  STRTAB               0x00001660
  STRSZ                0x000014ec
  GNU_HASH             0x00002b4c
  NEEDED               liblog.so
  NEEDED               libc++.so
  NEEDED               libdl.so
  NEEDED               libc.so
  NEEDED               libm.so
  SONAME               libcutils.so
  FINI_ARRAY           0x0000fbf0
[.....]

Так выглядит фрагмент заголовка libcutils.so. Поля NEEDED содержат зависимости, которые будут подгружены линковщиком в тот же процесс, что и сам libcutils. Обратите внимание на поле SONAME, содержащее имя библиотеки. Примечательно, что оно не нужно линковщику для нормальной работы, он ищет библиотеки по именам их файлов. Так почему бы не разобрать ELF-заголовок и не вставить вместо этого ненужного поля ещё одну зависимость? От какой-нибудь менее важной системной библиотеки? Здесь нужно быть осторожным: длина имени новой зависимости не должна превышать длину имени самой библиотеки (поэтому я выбрал libcutils, а не, например, libc). К счастью, на примете есть отличный кандидат: libmtp.so. Она большого размера (несколько десятков килобайт), с коротким именем, и нужна только при подключении к компьютеру по USB для передачи файлов по протоколу MTP. То есть 2 минуты без неё прожить можно. Остальное — дело техники. Парсим ELF файл, находим поле с именем библиотеки, меняем тип поля с DT_SONAME (0xE) на DT_NEEDED (0x1), и само имя меняем на libmtp.so. Готово! У libcutils.so появляется новая зависимость:

Динамический раздел:
[.....]
  STRTAB               0x00001660
  STRSZ                0x000014ec
  GNU_HASH             0x00002b4c
  NEEDED               liblog.so
  NEEDED               libc++.so
  NEEDED               libdl.so
  NEEDED               libc.so
  NEEDED               libm.so
  NEEDED               libmtp.so
  FINI_ARRAY           0x0000fbf0
[.....]

Теперь дело за малым: перезаписать libmtp.so по нашему усмотрению и перезапустить процесс, от имени которого мы хотим выполнить код. Сделать это можно, опять же, уронив zygote, так как вслед за ней падают не только android-приложения, но и многие системные сервисы.
Стоит сказать спасибо авторам вируса Android.Loki.28.origin, у которых я почерпнул эту замечательную идею с внедрением зависимости в библиотеку.

Вместо заключения


Прошли времена, когда эксплойты под Android использовали вопиющие уязвимости в безопасности ОС, что позволяло безнаказанно творить в системе всё, что угодно. Сегодня Android хорошо защищён, и немалый вклад в защиту внёс SELinux, появившийся в версии 4.3, и включённый по умолчанию в 5.0. Тем не менее, лазейки в защите всё ещё встречаются, и они всё ещё повышают привилегии настолько, чтобы их можно было использовать на практике, как в хороших целях, так и во зло. Надеюсь, вам было интересно узнать о том, как одному из самых, казалось бы, бесполезных эксплойтов в Android удалось найти полезное применение. И надеюсь, что новая версия тач-логгера поможет мне собрать вагоны ценнейших пользовательских данных и продвинет исследование поведенческих признаков жестов на сенсорном экране вперёд.
Tags:
Hubs:
+28
Comments 13
Comments Comments 13

Articles