Zygote android что это
«Холодный» запуск Android-приложения
Всем приветъ! Давно ничего не писал.
Это будет серия постов о процессе «холодного» запуска Android приложения, с момента нажатия на иконку и до создания процесса приложения.
Общая схема
Открывая «окно»…
Перед тем как запустить новый процесс приложения, system_server создает стартовое окно используя метод PhoneWindowManager.addSplashScreen():
Стартовое окно это то, что пользователь будет видеть пока запускается само приложение. Окно будет отображаться до тех пор пока не будет запущена Activity и не будет отрисован первый кадр. То есть пока не будет завершен «холодный» запуск. Пользователь может видеть данное окно длительное время, поэтому постарайтесь сделать его приятным.
Содержимое стартового окна берется из drawable-ресурсов windowSplashscreenContent и windowBackground запускаемого Activity. Банальный пример такого окна:
Если пользователь восстанавливает Activity из режима последнего экрана(Recent screen), при этом на нажимая на иконку приложения, то system_server вызывает метод TaskSnapshotSurface.create(), чтобы создать стартовое окно из уже сделанного скриншота.
Как только стартовое окно показано пользователю, system_server готов запустить процесс приложения и вызывает метод ZygoteProcess.startViaZygote():
В коде видно, что метод ZygoteProcess.zygoteSendArgsAndGetResult() отправляет аргументы запуска через сокет Zygote-процессу.
«Разделение» Zygote-ы
Каждый процесс приложения запускается с помощью форкания(разделения) от существующего Zygote-процесса…
Вкратце об этом я писал в предыдущей статье про запуск Android-а. А теперь давайте посмотрим поглубже на происходящие процессы.
Когда система загружается процесс Zygote стартует и выполняет метод ZygoteInit.main():
Как вы видите метод ZygoteInit.main() делает 2 важные вещи:
На заметку: Начиная с Android 10 есть оптимизационная фича под названием Unspecialized App Process, которая имеет пул не специализированных Zygote-процессов, для еще более быстрого запуска приложений.
Приложение запустилось!
После форка дочерний процесс запускает метод RuntimeInit.commonInit(), который устанавливает дефолтный UncaughtExceptionHandler. Далее, процесс запускает метод ActivityThread.main():
Тут происходят две интересные вещи:
Контроль над приложением
В процессе system_server метод ActivityManagerService.attachApplication() вызывает метод ActivityManagerService.attachApplicationLocked(), который завершает настройку запускаемого приложения:
Парочка ключевых выводов:
Давайте немного подробно разберем 3-ий пункт и узнаем что и как происходит при загрузке компонентов и ресурсов приложения. Порядок шагов такой:
Эпилог
Мы начали изучать «холодную» загрузку с очень обще-абстрагированного уровня:
Теперь мы знаем, что происходит «под капотом»:
Ну что же, это был длинный пост. Но это не все! В следующих постах мы продолжим глубокое погружение в процесс запуска Android-приложения. Оставайтесь с нами!
Русские Блоги
Android Zygote: глубокое понимание Zygote (android6.0)
На основе Android6.0 соответствующие файлы являются следующими:
1. Обзор
Где процесс Zygote может быть перезапущен:
Начиная с App_main (), класс вызова функции процесса запуска Zygote выглядит примерно так:
Два, процесс запуска Zygote
2.1 App_main.main
2.2 start
2.3 startVm
Основная длина метода создания виртуальной машины Java заключается в настройке параметров виртуальной машины. Ниже перечислены только некоторые часто используемые параметры в процессе отладки и оптимизации.
2.4 startReg
массив [i] ссылается на массив gRegJNI, который имеет более 100 членов. Каждый из участников прошелREG_JNIМакроопределения:
3. Войдите в слой Java
Предыдущий [Раздел 2.1] AndroidRuntime.start () выполняется до последнего вызова ZygoteInit.main () посредством отражения, см. Ниже:
3.1 ZygoteInit.main
Метод caller.run (), вызываемый после перехвата исключения, будет рассмотрен в следующей статье system_server.
3.2 registerZygoteSocket
3.3 preload
Выполните инициализацию процесса Zygote. Для загрузки классов используйте механизм отражения Class.forName () для загрузки. Для загрузки ресурсов, в основном com.android.internal.R.array.preloaded_drawables и com.android.internal.R.array.preloaded_color_state_lists, ресурсы, начинающиеся с com.android.internal.R.xxx в приложении, В этот момент Zygote загружается в память.
Все ресурсы в методе preload () загружаются в процессе zygote. Когда необходимо разветвить новый процесс, используется технология копирования при записи, как показано ниже:
3.4 startSystemServer
3.5 runSelectLoop
Zygote использует эффективный механизм мультиплексирования ввода / вывода, чтобы гарантировать, что он будет в спящем режиме, когда нет запроса на подключение клиента или обработки данных, в противном случае он ответит на запрос клиента.
3.6 runOnce
Для получения дополнительной информации см.Понять процесс создания процесса Android
Русские Блоги
Процесс zyogte при запуске системы Android-4 (C)
Основное содержание этой статьи таково:
Мы все знаем«Один штатив и три ножки»с участием«Устойчивость треугольника», Тогда какие три «ноги» поддерживают систему Android? которыйпроцесс инициализации、SystemServer процессс участиемЗиготный отросток. В этой статье мы подробно рассмотрим процесс Zygote.
1. Зачем изучать зиготу?
Во-вторых, начало процесса зиготы (слой C)
Зигота в процессе инициализацииserviceСпособ начать. Начиная с Android 5.0, в Zygote все еще есть изменения: раньше он помещался непосредственно в блок кода в init.rc, но теперь вынесен в отдельный файл, а файл импортируется через init.rc через «импорт». Как показано ниже: Код находится вinit.rc Ряд 11
Как видно из приведенного выше утверждения, init.rc напрямую не представляет фиксированный файл, а вводит разные файлы в соответствии с содержимым атрибута ro.zygote. Это связано с тем, что начиная с Android 5.0 система Android начала поддерживать 64-битную компиляцию, а сам процесс Zygote будет иметь различие между 32-битной и 64-битной версиями. Поэтому атрибут ro.zygote используется для управления запуском различных версий процесса Zyogte.
Возможные значения атрибута rozyoget следующие:
Итак, в каталоге того же уровня init.rc есть 4 файла rc, связанных с zygote:
Здесь мы смотрим на разницу между ними
(1) Разница между init.zygote32 и init.zygote64
Поскольку результаты init.zygote64 и init.zygote64_32 аналогичны результатам init.zygote32 и init.zygote64, я объясню только разницу между init.zygote32 и init.zygote64. Посмотрите на код напрямую
Содержимое файла init.zygote32.rc соответствует содержимому zygote, запущенного в init.rc до Android 5.0.
(2), функция zygote и service_start ()
Приведенная выше строка кода указывает, что параметр сокета zygote указывает, что ему нужен сокет «потока» с именем «zgyote». Когда процесс init действительно запускает службу zygote, он переходит к функции service_start (), а затем давайте посмотрим на конкретное выполнение функции service_start ().
Я разделил функцию service_start на 11 шагов, давайте рассмотрим их один за другим.
1. Шаг 1. Сброс флага в Сервисе
serviceПри запуске необходимо сбросить флаги, среди которых четыре флага SVC_DISABLED, SVC_RESTARTING, SVC_RESET и SVC_DISABLED_START относятся к процессу запуска и должны быть сначала очищены. в случаеserviceС флагом SVC_RUNNING служба уже запущена, поэтому нет необходимости перезапускать ее повторно.
2. Шаг 2-консольная проверка
Вот в основном суждениеserviceВам нужна консоль? Если вам нужна консоль, но ее еще нет, она несовместима, поэтому просто выйдите.
Здесь в основном для проверкиserviceБинарный файл существует
PS: функция restart_service_need () также будет использовать параметр dynamic_args, используемый функцией service_start (), равен NULL, поэтому этот абзац не будет выполнен
4. Шаг 4. Проверьте параметр SVC_ONESHOT.
Флаг SVC_ONESHOT указывает, что служба запускается только один раз, после выхода она больше не может быть запущена
5. Шаг 5. Задайте контекст безопасности SELinux.
Целью этого кода является получение контекста безопасности процесса обслуживания.
Не о чем говорить, это дочерний процесс fork
7. Шаг 7. Подготовьте переменные среды.
вserviceВ опциях, если есть опция setev, параметр setenv будет установлен как переменная среды процесса службы.
PS: Здесь поместите результат выполнения dup в дескриптор файла общей области «Свойства» fd в переменной среды ANDROID_PROPERTY_WORKSPACE. Этот fd используется, когда сервисный процесс не может открыть файл устройства в общей области атрибутов.
9. Шаг 9: стандартный вывод, стандартный ввод, стандартная ошибка.
10. Шаг 10: стандартный вывод, стандартный ввод, стандартная ошибка.
Быстрый код предназначен для обновления соответствующих атрибутов службы.
(3) Процесс запуска зиготы
Затем мы разберемся с процессом запуска зиготы, который выглядит примерно так:
Процесс в init.cpp уже объяснялся ранее, я больше не буду об этом говорить, давайте начнем с app_main.cpp
1. Основная функция app_main.cpp
Я разделил приведенный выше код на семь частей, давайте объясним их одну за другой ниже.
Этот код связан с механизмом безопасности системы, prctl (PR_SET_NO_NEW_PRIVS), похоже, запрещает изменение разрешений, которые являются содержимым SEAndroid. Android официально запустил механизм безопасности системы на основе SELinux на 4.4
В Linux PR_SET_NO_NEW_PRIVS устанавливается, когда процесс или дочерний процессPR_SET_NO_NEW_PRIVSАтрибут, он не может получить доступ к некоторым операциям, которые нельзя использовать совместно.Эта функция добавлена после ядра 3.5.
Эта часть в основном создает объект AppRuntime, а класс AppRuntime наследуется от AndroidRuntime. Я объясню это подробно позже.
Затем найдите параметры, связанные с виртуальной машиной, в параметрах командной строки и добавьте их в объект среды выполнения.
Затем следуют некоторыеinternal Параметры, где—zygoteУказывает на запуск в режиме зиготы
1.4, часть четвертая
Эта часть предназначена в основном для запуска класса ZygoteInit или RuntimeInit.Разница зависит от того, существует ли имя класса или нет.Режим без zyogetс участиемрежим zyoget.
Запустите класс Java, если параметр запуска имеет «—zygote». Затем выполните ZygoteInit.
В основном это делится на три ситуации.
PS: app_process может запускать процесс Zyoget, кроме.
2. Введение в AppRuntime
Выше мы упоминали, что объект AppRuntime создается в основной функции app_main.cpp. Давайте сначала посмотрим на класс AppRuntime.
Из приведенного выше кода мы знаем, что AppRuntime наследуется от класса AndroidRuntime и перегружает функции onVmCreated, onStarted, onZygoteInit и onExit. Мы обнаружили, что функция start не перегружена, но функция runtime.start находится в конце функции main () файла app_main.cpp, поэтому конкретная реализация находится в функции start класса AndroidRuntime. Затем изучим класс AndroidRuntime
3. Введение в класс AndroidRuntime
Давайте посмотрим на его конструктор. Код находится вAndroidRuntime.cpp Строка 238
Конструктор, код очень маленький, всего 4 строчки, я разделил на 3 части
kia structure diagram.png
PS: В конструкторе класса Android Android 5.0 установит графическую систему Skia раньше и установит графический формат, используемый в нижней части системы, на RGB565, 16-битный графический формат. Формат 16 изображений не так богат, как формат 24-битной графики, но он позволяет сэкономить память. После Android 5.0 системные настройки для графики skia удаляются, и сохраняется только вызовmOptions.setCapacity(20);Для настройки параметров виртуальной машины
4. Запустите виртуальную машину, функцию start () AndroidRuntime.
Ранее мы знали, что в конце функции main () для запуска вызывается функция start () класса AppRuntime. Из вышесказанного мы знаем, что функция start () класса AppRuntime на самом деле является функцией start () вызываемого AndroidRuntime. Давайте взглянемAndroidRuntimeизstart()Конкретная реализация функции выглядит следующим образом:
Ха-ха, у этого метода есть комментарии, и это здорово, сначала переведите комментарии следующим образом
В этой функции не так много контента, но она очень важна, поэтому яAndroidRuntimeизstart()Код внутри функции разделен на 7 частей. Давайте объясним одно за другим
4.1, первая часть журнала печати
Первый звонокALOGD метод, Используется для записи ежедневного контента (журнал, записываемый ALOGD, существует во время компиляции, но будет создан во время выполнения), что знаменует начало Android. Следующий цикл for используется для определения того, запущен ли systemServer (то есть, имеет ли входящий параметр startSystemServer), если он должен запустить systemServer, журнал также должен быть напечатан.
PS: systemTime (SYSTEM_TIME_MONOTONIC), чтобы получить текущее время системы
Системный каталог из переменных средыANDROID_ROOTПрочтите. Если это не удается, каталог настроек по умолчанию«/system». Если даже«/system»И процесс Zygote не завершится.
PS: Системный каталог создается в процессе инициализации
Завершите инициализацию интерфейса jni через jni_invocation.Init (NULL). Далее идет код для создания виртуальной машины, которая вызывает функцию startVm.
После Android 5.0 виртуальная машина ART будет запущена. О предыдущем коде запуска Dalvik4.3 AndroidRuntime.cppВы можете сравнить 267 самостоятельно. Пожалуйста, обратитесь к этой статье о запуске виртуальной машины ARTПятая часть, перемещение виртуальной машины
Из приведенного выше кода мы знаем, что если это запуск Zygote, onVmCreated фактически ничего не делает. Если он не в режиме Zygote, он просто получает класс на основе имени класса и просто получает класс на основе имени класса и освобождает его. Название класса
Функция startReg () регистрирует локальную функцию JNI в глобальном gRegJNI на виртуальной машине, вызывая функцию register_jni_procs (). Для анализа этой части см.3. Межпроцессное взаимодействие Android. IPC. Три вещи о «JNI»средний4. Метод поиска JNI
Сначала он получает идентификатор метода main () через функцию GetStaticMethodI. Следующим шагом является использование функции CallStaticVoidMethod для вызова метода уровня Java. Пока инициализация Zygoet будет перенесена на Java. Конечно, если Zyogte не запущен, исполняемая Java будет RunnableInit
Функция start класса AndroidRuntime фактически выполняет три функции:
На этом процесс zyogte (Часть C) закончился. Далее идет Java-глава процесса zyogte, мы будем в следующей статьеxxxЭто объясняет последующий процесс. Посмотрим выше
В-третьих, о внедрении виртуальных машин.
Когда система Android была в версии 4.4, была выпущена среда выполнения ART для замены ранее использовавшейся виртуальной машины Dalvik и для решения проблем производительности предыдущей виртуальной машины Dalvik.Принцип реализации ART я объясню отдельно позже. Пропустите это здесь. Вот краткое описание виртуальной машины. Виртуальная машина Dalvik на самом деле является виртуальной машиной Java, но она выполняет не файлы классов, а файлы dex. Следовательно, наиболее совершенное решение, разработанное для среды выполнения ART, должно быть похоже на виртуальную машину Dalvik. Фактически, будь то виртуальная машина ART или виртуальная машина Dalvik, интерфейс в основном такой же, как у виртуальной машины Java (но ее внутренний механизм отличается). Только так можно добиться бесшовного сближения. Давайте кратко рассмотрим эти три виртуальные машины (виртуальная машина Java, виртуальная машина Dalvik, среда выполнения ART), как показано ниже:
На приведенном выше рисунке показана взаимосвязь между виртуальной машиной Java, виртуальной машиной Dalvik и средой выполнения ART.
Из приведенного выше рисунка мы знаем, что и виртуальная машина Dalvik, и среда выполнения ART реализуют три абстрактных интерфейса виртуальной машины Java, а именно:
В системе Android виртуальная машина Davik реализована в libdvm.so, а среда исполнения ART теперь находится в libart.so. Другими словами, libvm.so и libart.so экспортируют три интерфейса, JNI_GetDefaultJavaVMInitArgs, JNI_CreateJavaVM и JNI_GetCreatedJavaVM, для вызова внешнего мира. Более того, система Android также предоставляет системное свойство вpersist.sys.dalvik.vm.lib.2(Android4.4 раньше былpersist.sys.dalvik.vm.lib), он либо принимает значение libdvm.so, что означает, что это виртуальная машина Dalvik, либо принимает значениеlibdvm.so, Это означает, что в настоящее время это среда выполнения ART.
3. Введение в принцип АРТ
В-четвертых, запустите виртуальную машину
Запуск виртуальной машины в основном состоит из двух частей, а именно:
Потом посмотрим на это по очереди:
(1) Анализ функции jni_invocation.Init (NULL)
Функция JniInvocation :: Init в основном открывает libart.so через функцию dlopen, а затем использует функцию dlsym для поиска и экспорта трех интерфейсов виртуальной машины Java, так что виртуальная машина может быть создана и доступна через эти три интерфейса. На этом инициализация среды JNI завершена.
Тогда почемуlibart.soВместо тогоlibdvm.so, Поскольку он вызывает функцию GetLibrary, давайте посмотрим на код вJniInvocation.cpp Ряд 60
Эта функция очень проста, это чтение свойств системы.persist.sys.dalvik.vm.lib.2Мы знаем, что это системное свойство в Android 6.0 имеет значение libart.so, а также дает значение системного свойства по умолчанию для libart.so. Итак, этот метод наконец выводит библиотеку libdvm.so
Итак, мы видим, что функция инициализации JniInvocation на самом деле основана наpersist.sys.dalvik.vm.lib.2Для инициализации среды выполнения ART. Далее мы продолжаем рассматривать реализацию функции-члена startVm класса AndroidRuntime:
(Два), startVm (JavaVM ** pJavaVM, JNIEnv ** pEnv, bool zygote) «анализ функции
Старые правила, давайте сначала переведем примечания, а именно:
Запустите виртуальную машину Dalvik.Различные параметры задаются в свойствах системы. Вектор «mOptions» обновлен.Примечание: при добавлении параметров не помещайте буфер символов во вложенный диапазон. При использовании «mOptions.add ()» при добавлении буфера не копируйте буфер. Это потому, что, если буфер превышает диапазон, параметры могут быть перезаписаны. Рекомендуется размещать буфер в верхней части функции, чтобы последняя была включена во избежание снижения вероятности ошибок. В случае успеха верните 0.
Я делю содержимое функции startVm на 11 частей, и мы объясним их одну за другой ниже:
Подводя итог: на самом деле в функции AndroidRuntime :: startVm () в основном выполняются две вещи.
(3) Анализ функции JNI_CreateJavaVM ()
Чтобы облегчить ваше понимание, я разделю описанный выше процесс на 6 шагов и объясню их по очереди:
Резюме: Подведем итоги функции JNI_CreateJavaVM (), она в основном включает в себя следующие две вещи
Пока что функция JNI_CreateJavaVM () была проанализирована. Вот более важный класс, Runtime. Давайте посмотрим на Runtime.
Пять, время выполнения
Среда выполнения представляет среду выполнения Java в ART. Процесс может создать только одну виртуальную машину ATR, а виртуальная машина ART может иметь только одну среду выполнения. О времени выполнения определяется вruntime.ccв.
ВышеJNI_CreateJavaVM()В функции вызывается функция Runtime :: Create (options, ignore_unrecognized). Давайте взглянем на функцию Runtime :: Create (options, ignore_unrecognized)
(1), Runtime :: Create (options, ignore_unrecognized) анализ функции
Этот фрагмент кода очень прост, в основном он вызывает функцию Init для инициализации экземпляра Runtime, а затем давайте посмотрим на конкретную реализацию функции init.
(2), Анализ функции Runtime :: Init (const RuntimeOptions & raw_options, bool ignore_unrecognized)
На этом этапе анализируется функция Runtime :: Init, и каждый шаг выше может быть указан как статья для дальнейшего изучения, но, в конце концов, это не специальное исследование ART, так что пока заинтересованные могут изучить сами.
Атака на Zygote: новый виток эволюции мобильных угроз
История о маленьком троянце, который смог
Главная опасность приложений, втайне от пользователя получающих root-доступ на мобильном устройстве, заключается в том, что они могут предоставлять доступ к зараженному устройству куда более продвинутым и опасным зловредам с хорошо продуманной архитектурой. Мы опасались, что троянцы, несанкционированно получающие права суперпользователя и использующие их для установки легитимных приложений и показа рекламы, начнут устанавливать вредоносные программы. Наши опасения оправдались: с помощью таких рутовальщиков стал распространяться самый сложный из известных нам мобильных троянцев.
Рутовальщики
В нашей предыдущей статье мы рассказывали о набирающих популярность Android-зловредах, которые несанкционированно получают привилегированный доступ к устройству и используют его для установки приложений и показа агрессивных рекламных сообщений. Часто через некоторое время после попадания подобного зловреда на устройство пользоваться им становится весьма затруднительно – из-за обилия назойливой рекламы и установленных приложений.
Со времени написания первой статьи (август 2015) все изменилось в худшую сторону – число семейств таких вредоносных программ выросло с 4 до 11, распространяются они еще активнее, и намного успешнее осуществляют «рутование». По нашим оценкам, троянцами, получающими права суперпользователя, во второй половине 2015 года были атакованы около 10% пользователей мобильных устройств на платформе Android. Были зарегистрированы случаи, когда такие программы были предустановлены на новых мобильных устройствах из Китая.
Впрочем, стоит отметить, что устройства с ОС Android выше версии 4.4.4 имеют куда меньше уязвимостей, с помощью которых можно получить root-доступ. Поэтому в основном целью подобных зловредов являются ОС предыдущих версий, которые все еще установлены на большинстве устройств пользователей. Диаграмма, приведенная ниже, показывает распределение пользователей нашего продукта в зависимости от версии ОС Android. Как видно на диаграмме, около 60% пользователей пользуются устройствами, на которых упомянутые троянцы могли бы заполучить привилегии суперпользователя.
Версии операционной системы Android, используемые пользователями наших продуктов
Отметим, что хозяева описанных ранее троянцев, таких как Leech, Ztorg, Gorpo (а также зловредов нового семейства Trojan.AndroidOS.Iop), действуют сообща. Зараженные такими зловредами устройства обычно образуют своеобразный «рекламный ботнет», через который рекламные троянцы распространяют друг друга и рекламируемые приложения. Уже через несколько минут после установки одного из таких троянцев на атакованном устройстве работают все остальные активные зловреды, входящие в эту «сеть». Злоумышленники наживаются на рекламе и установках легитимных приложений.
В 2015 году с помощью такого «рекламного ботнета» начали распространяться зловреды, несущие непосредственную угрозу пользователю. В том числе так распространяется один из самых сложных мобильных троянцев из всех, что попадали к нам на анализ.
Уникальный троянец
С помощью «рекламного ботнета» распространяется уникальный в своем роде троянец, который имеет следующие особенности:
Троянец устанавливается в папку, содержащую системные приложения, под именами, которые и в самом деле могли бы быть у системных приложений (например, AndroidGuardianship.apk, GoogleServerInfo.apk, USBUsageInfo.apk и т.д.).
В начале своей работы зловред собирает следующую информацию:
Собранная информация отправляется на сервер злоумышленников, адрес которого троянец получает из списка, прописанного в коде:
Или, в случае недоступности указанных выше серверов, из списка резервных командных серверов, также прописанного в коде:
В ответ приходит зашифрованный конфигурационный файл, который сохраняется как «/system/app/com.sms.server.socialgraphop.db». Конфигурация регулярно обновляется и состоит из следующих полей:
Если поле mModuleUpdate заполнено, то происходит скачивание и сохранение DEX файлов. Затем с помощью DexClassLoader.loadClass() происходит загрузка скачанных файлов в контекст зловреда. После загрузки модули удаляются с диска, т.е. они остаются только в оперативной памяти устройства, что серьезно затрудняет их обнаружение и удаление антивирусными программами.
Загруженные модули для корректного исполнения должны иметь следующие интерфейсные методы:
В зависимости от версии также может использоваться следующий набор интерфейсов:
Подобный механизм обеспечивает возможность исполнения приложением-загрузчиком модулей, реализующих различную функциональность, и осуществлять их координацию и синхронизацию.
Описанные приложения и загружаемые ими модули используют файлы » androidbin», «conbb», «configopb», «feedback» и «systemcore», хранящиеся в папке /system/bin, для выполнения в системе различных действий с привилегиями суперпользователя. Разумеется, этих файлов на чистой системе нет.
Учитывая описанную модульную архитектуру и привилегированный доступ к устройству, зловред может сотворить буквально все, что угодно. Возможности подгружаемых модулей ограничиваются только фантазией и навыками вирусописателей. Данные зловреды (приложение-загрузчик и загружаемые им модули) относятся к разным видам троянцев, но все они были занесены в наши антивирусные базы с общим наименованием Triada.
На время анализа приложение-загрузчик (детектируется нами как Backdoor.AndroidOS.Triada) скачивал и активировал следующие модули:
Использование процесса Zygote
Отличительной особенностью вредоносного приложения является использование процесса Zygote для внедрения своего кода в контекст всех приложений на устройстве. Процесс Zygote – это родительский процесс для всех Android-приложений, который содержит системные библиотеки и фреймворки, используемые практически всеми приложениями. Этот процесс является шаблоном для каждого нового приложения, и это значит, что как только троянец попадет в этот процесс, он становится частью этого шаблона и будет попадать в каждое запускаемое приложение. Мы впервые сталкиваемся с подобной техникой ITW, до этого использование Zygote реализовывалось только в виде Proof-of-Concepts.
На схеме, представленной ниже, представлена архитектура зловреда.
Рассмотрим подробнее, как происходит заражение процесса Zygote.
Подготовительный этап и тестирование
Вся магия начинается в функции crackZygoteProcess(), которая содержится в модуле Trojan-Banker. Ее код можно увидеть на скриншоте ниже.
Как видно из кода, сначала троянец подгружает библиотеку libconfigpppm.so и вызывает экспортируемую ею функцию configPPP (первая подчеркнутая красным строка на скриншоте). Если эта функция успешно отработала и вызов стандартной функции Android API System.getProperty() с нестандартным параметром «pp.pp.pp» (позже поясним зачем) вернул значение, отличное от null, то троянец запускает исполняемый ELF-файл configpppi, передавая ему на вход PID процесса zygote.
Расскажем, как все происходит, по порядку.
Внутри функции configPPP из libconfigpppm.so троянец первым делом получает адрес (внутри адресного пространства своего процесса), по которому загружен файл, который реализует стандартную функцию Android API ActivityThread.main(). Далее по полученному адресу и при помощи файла / proc/self/maps определяет путь и имя этого файла. В подавляющем большинстве случаев это будет / system/framework/framework.odex. Но данный механизм отработает и в случае, если framework.odex имеет нестандартное имя или путь.
Троянец читает данный файл с диска и сравнивает с тем, что уже подгружен в память процесса. Проверка происходит следующим образом:
Если проверка на соответствие файла в памяти с прообразом на диске завершилась неудачно, функция configPPP прекращает дальнейшую работу и возвращает соответствующий код ошибки (-103 в данном случае).
Если же проверка прошла успешно, начинается процесс патчинга образа файла framework.odex в памяти процесса троянца.
Все начинается с получения данных класса ActivityThread, определенного в framework.odex, при помощи функций dexFindClass и dexGetClassData. Данные функции авторы троянца заимствовали прямиком из открытых исходных кодов виртуальной машины Dalvik. Далее, используя полученные данные класса, троянец итерирует по списку методов, реализованных в данном классе, пока не дойдет до метода с именем main. И получает байт-код этого метода с помощью функции dexGetCode (также заимствованной из исходных кодов Dalvik), сохраняя указатель на него.
Затем троянец проверяет, не пропатчен ли уже код этого метода, сравнивая его с кодом того же метода, полученного из файла на диске. И если метод уже пропатчен, работа библиотеки завершается.
После этого троянец ищет в таблице строк файла framework.odex в памяти процесса строку нужной длины – от 32 до 62 символов, – записывает на ее место строку » /system/lib/libconfigpppl.so» и сохраняет ее ID (порядковый номер в таблице строк).
Далее ищет в таблице методов файла framework.odex в памяти процесса один из методов с именем loop, attach, setArgV0. Выбирается тот, который попадется первым, или, если данные методы не были найдены, то берется предпоследний метод из таблицы методов. Выбранный метод заменяется на стандартный метод System.load(), и его ID сохраняется. На скриншоте ниже показан псевдокод, отвечающий за эту операцию:
На этом подготовительный этап можно считать законченным, и троянец переходит к самому интересному. Он добавляет в памяти (!sic) к байт-коду оригинального метода main класса ActivityThread еще несколько инструкций. Представленный ниже байт-код помещается перед байт-кодом оригинального метода:
1A 00 [strID, 2 bytes] //const-string v0, «/system/lib/libconfigpppl.so»
71 10 [methID, 2 bytes] 00 00 //invoke-static
0E 00 //return-void
Где strID – сохраненный ID перезаписанной ранее строки, а methID – сохраненный ID перезаписанного ранее метода.
В результате совершенных модификаций при вызове функции ActivityThread.main() в контекст вызвавшего эту функцию процесса будет подгружаться библиотека /system/lib/libconfigpppl.so. Но, так как framework.odex пропатчен только в контексте процесса троянца, то библиотека будет подгружаться только в процесс троянца. Это, на первый взгляд, бессмысленное действие выполняется в целях тестирования возможности зловреда модифицировать процесс Zygote. Если описанные действия не вызвали ошибок в контексте самого приложения, то они не вызовут ошибок и в контексте системного процесса. Злоумышленники подходят с осторожностью к такому сложному действию как изменение адресного пространства Zygote, так как любое неосторожное действие в этом процессе может привести к немедленному падению системы. Именно поэтому осуществляется «тестовый прогон», позволяющий проверить работоспособность описанной методики на устройстве пользователя.
На этом работа библиотеки libconfigpppm.so почти закончена. В заключение, она записывает в файл /data/configppp/cpppimpt.db некоторые сохраненные в результате своей работы данные , которые потом будут использоваться в процессе реальной модификации Zygote. А именно:
И, наконец, троянец вызывает метод ActivityThread.main() (который уже пропатчен), подгружая таким образом библиотеку /system/lib/libconfigpppl.so в свой процесс. Посмотрим, что же происходит внутри нее, но сначала заглянем внутрь файла configpppi, который осуществляет реальную модификацию адресного пространства Zygote.
Модификация Zygote
Делает он практически то же самое, что и файл libconfigpppm.so, т. е. патчит функцию ActivityThread.main() внутри framework.odex, подгруженного в процесс. Только на этот раз это действие происходит не внутри процесса троянца, а внутри процесса, PID которого передается в качестве аргумента файлу configpppi, т. е. процесса Zygote. Запись в процесс Zygote происходит при помощи системного вызова ptrace(), позволяющего читать и писать в память другого процесса. И теперь троянец не ищет интересующие его адреса и другие данные для патчинга внутри процесса, а берет их из уже подготовленного ранее файла /data/configppp/cpppimpt.db.
Процесс Zygote – это родительский процесс для всех Android-приложений. Он содержит в себе все необходимые системные библиотеки и фреймворки, которые используются практически всеми приложениями. Этот процесс был придуман, чтобы не загружать целую кучу библиотек в память каждый раз, когда пользователь запускает очередное приложение. При запуске приложения пользователем процесс Zygote просто вызывает системный вызов fork(), который создает дочерний процесс, представляющий собой полную копию родителя, и в дочерний процесс загружаются только непосредственно данные запускаемого приложения. Такой механизм, в частности, позволяет сократить время запуска приложений.
Таким образом, при запуске нового Android-приложения в него из Zygote будет попадать измененная троянцем версия framework.odex (с подгруженной библиотекой libconfigpppl.so). Иными словами, библиотека libconfigpppl.so попадает во все новые приложения и может изменять логику их работы. Это открывает широкий спектр возможностей перед злоумышленниками.
Подмена стандартных функций Android Framework
После того как библиотека /system/lib/libconfigpppl.so оказалась загружена внутри Zygote стандартной функцией System.load(), срабатывает ее функция JNI_OnLoad.
Внутри этой функции троянец первым делом восстанавливает исходные значения перезаписанных строки и метода. Для этого используются данные, сохраненные в файле /data/configppp/cpppimpt.db.
Далее троянец подгружает DEX файл configpppl.jar, используя для этого стандартную функциональность Android API, а именно класс dalvik.system.DexClassLoader.
И, чтобы удостовериться, что DEX файл был успешно подгружен, вызывает его метод pppMain в классе PPPMain, который только выводит в Logcat строку «PPP main started».
Следующий этап – подготовка хуков для различных методов Android Framework (framework.odex). Сначала троянец по очереди проверяет наличие в configpppl.jar всех методов, которые будут использоваться в качестве хуков, с помощью функции checkPackageMethodExits (определена в configpppl.jar). Далее троянец подготавливает хуки для следующих методов:
Хуки устанавливаются при помощи вызова стандартной функции RegisterNatives(). Эта функция предназначена для связывания Java-методов с их нативной реализацией (т. е. на языке C/C++). Таким образом троянец подменяет стандартные функции Android Framework (которые перечислены выше) на функции, реализованные в libconfigpppl.so.
Рассмотрим подробнее, зачем троянцу понадобилось подменять эти функции.
Проверка успешности модификации Zygote
Функция, которая подменяет оригинальную getProperty(), сначала проверяет, какой параметр ей передается на вход. И если это строка » pp.pp.pp«, то сразу же возвращает значение true. Иначе вызывает оригинальную функцию getProperty(), передавая ей на вход полученный параметр. С помощью вызова этой функции с параметром » pp.pp.pp» из Java-кода (показан на первом скриншоте в этом разделе) троянец проверяет, удалось ли ему успешно подменить функции Android Framework. Если подмена произошла успешно, то, как и говорилось ранее, троянец запускает исполняемый ELF-файл configpppi, передавая ему на вход PID процесса Zygote.
Далее троянец «убивает» процессы интересующих его в первую очередь приложений: com.android.phone, com.android.settings, com.android.mms – это стандартные приложения «Телефон», «Настройки» и «Сообщения». Эти приложения автоматически запустятся при следующей разблокировке устройства и будут содержать в себе полученный от Zygote измененный framework.odex со всеми хуками, установленными libconfigpppl.so.
Модификация исходящих SMS
Функция, которая подменяет newApplication(), сначала вызовет оригинальную функцию, а потом еще 2 функции configpppl.jar: onModuleCreate() и onModuleInit().
Функция onModuleCreate() проверяет, в контексте какого приложения она запущена. И в зависимости от этого устанавливает значение глобальной переменной mMainAppType:
Функция onModuleInit() на основе установленного значения mMainAppType выполняет одну из инициализирующих функций:
Если троянец запущен внутри com.android.phone, то он регистрирует обработчик событий com.ops.sms.core.broadcast.request.status и com.ops.sms.core.broadcast.back.open.gprs.network. Первый из них устанавливает в качестве значения глобальной переменной mLastSmsShieldStatusTime текущее время, а второй включает передачу данных по мобильным сетям.
Если троянец запущен внутри com.android.settings или com.android.mms, то он регистрирует обработчики событий:
Первые два из них такие же, как и в предыдущем случае, а третий выполняет отправку SMS на номер и с текстом, указанными в параметрах события.
И в зависимости от результата вызывает одну из функций: PISmsCore.invokeMMMain() или PISmsCore.invokeOtherMain().
Обе эти функции вызывают PISmsCore.initInstance(). Она делает следующее:
Самое интересное из всех этих действий – это подмена системных биндеров «isms» и «isms2».
Биндеры (Binder IPC) – это механизм межпроцессного взаимодействия на ОС Android. В случае использования биндеров весь обмен данными между различными приложениями происходит через специальное псевдо-устройство – / dev/binder. Фактически же, для передачи данных используется адресное пространство памяти ядра ОС. Схема подобного взаимодействия изображена ниже.
Например, когда приложение хочет отправить SMS-сообщение, оно вызывает стандартную функцию Andoroid API – sendTextMessage (или sendMultipartTextMessage). Это приводит к вызову метода transact() системного биндера «isms» (или «isms2»), который в итоге передаст эти данные GSM-модулю телефона.
Метод transact() переопределен в реализации биндера «isms», который подменяет оригинальный. Таким образом, когда приложение отправляет SMS-сообщение, оно (сообщение) попадает под контроль троянца, проходя через вредоносный биндер.
В методе transact() троянец достает из проходящего через него SMS-сообщения номер получателя, текст сообщения и номер центра SMS-сообщений и отправляет их на один из своих C&C серверов (выбирается случайным образом):
Ответ C&C сервера, помимо прочих данных, может содержать новый номер получателя SMS-сообщения и новый текст отправляемого сообщения.
Если же интернет в момент перехвата SMS-сообщения недоступен, то троянец попытается получить новый номер получателя SMS и текст сообщения из локальных файлов конфигурации, которые он хранит в / sdcard/Android/com/register/localuseinfo/.
В итоге троянец подменяет текст сообщения и номер его получателя и пытается отправить SMS тремя различными способами одновременно:
Когда троянец отправляет SMS-сообщение одним из этих способов, он сохраняет новый текст сообщения и номер получателя в специальной переменной. И перед отправкой очередного SMS-сообщения он проверяет, не было ли оно уже отправлено. Это позволяет предотвратить бесконечные рекурсивные вызовы метода transact(), таким образом каждое отправляемое SMS-сообщение будет перенаправлено подмененным биндером только один раз.
Помимо функции PISmsCore.initInstance(), функция PISmsCore.invokeMMMain() вызывает еще одну функцию – PIMMCrack.initInstance(). Здесь троянец пытается определить версию mm.sms.purchasesdk, используемую приложением, внутри которого он работает. mm.sms.purchasesdk – это SDK китайского происхождения, которое внедряется разработчиками в их приложения для реализации функциональности покупок внутри своих приложений посредством SMS-сообщений. Троянец знает, что приложение использует данное SDK, так как предварительно он проверил, использует ли оно библиотеку libsmsiap.so, которая также является частью этого SDK.
Таким образом, механизм, описанный в этой главе, позволяет троянцу модифицировать исходящие SMS-сообщения, отправляемые другими приложениями. Мы полагаем, что злоумышленники используют эту технику для незаметного воровства финансовых средств пользователей. Например, когда пользователь покупает что-то во внутриигровом магазине какой-нибудь игры для Android (если эта игра использует SDK для оплаты внутриигровых покупок через SMS — например, mm.sms.purchasesdk), злоумышленники могут модифицировать исходящее платежное SMS-сообщение таким образом, чтобы получить деньги пользователя вместо разработчиков игры. В результате пользователь, скорее всего, не заподозрит, что его деньги были украдены злоумышленниками. Но поскольку оплаченный контент он так и не получит, то, вероятно, подумает, что это баг в игре. Еще один вариант – деньги уходят злоумышленникам, но пользователь контент получает. В таком случае троянец совершает хищение финансовых средств не столько пользователя, сколько разработчиков легитимного ПО.
Фильтрация входящих SMS
Оригинальная функция dispatchPdus(), как показано на схеме ниже, используется для рассылки PDU-данных (Protocol Data Unit, низкоуровневая сущность представления данных, используется во многих коммуникационных протоколах) входящих SMS-сообщений всем установленным приложениям. Далее все приложения, подписанные на это событие, могут получать и обрабатывать входящие SMS-сообщения, которые приходят в виде PDU-данных вместе с широковещательным событием.
Функция, которая подменяет оригинальную dispatchPdus(), вызывает функцию moduleDispatchPdus() из configpppl.jar. Эта функция сначала проверяет, в контексте какого приложения она запущена, и, если это не com.android.phone (т. е. стандартное приложение «телефон»), то просто формирует и отправляет всем приложениям событие android.provider.Telephony.SMS_RECEIVED (вместе в пришедшими PDU) – стандартное событие, означающее получение входящего SMS-сообщения, на которое подписано, например, стандартное приложение для работы с SMS («Сообщения» или Hangouts).
Если же функция запущена в контексте приложения com.android.phone, то троянец проверяет, с какого номера и с каким текстом получено SMS-сообщение. Для проверок используются несколько файлов, которые находятся в директориях: / sdcard/Android/com/register/infowaitreceive/ и /sdcard/Android/com/register/historyInfo/. Имена файлов в этих директориях имеют постфикс, содержащий дату и время последнего обновления. И если файлы были обновлены раньше, чем происходило последнее обращение к C&C серверу, то они просто удаляются и никаких проверок пришедшей SMS дальше не происходит. Иначе, все файлы, найденные в указанных директориях, расшифровываются, и из них достаются и заносятся в список номера и ключевые слова (для поиска в тексте SMS-сообщения).
Если SMS-сообщение пришло с номера из списка или его текст содержит одно (или несколько) из ключевых слов, то троянец отправляет его вместе с событием android.provider.Telephony.SMS_RECEIVEDcom.android.sms.core, которое получат только те, кто подписан на него. На «чистом» Android устройстве нет ни одного такого приложения. Иначе, отправляет его вместе со стандартным событием android.provider.Telephony.SMS_RECEIVED. Таким образом некоторые входящие SMS-сообщения могут быть отфильтрованы, и ни пользователь, ни какое-либо установленное приложение в системе его не увидят.
Сокрытие модулей троянца из списка запущенных сервисов
Данная функция используется для получения списка запущенных сервисов. Подменяя эту функцию, троянец скрывает свои модули из этого списка. А именно исключает из списка, возвращаемого оригинальной функцией getRunningServices(), следующие элементы:
Сокрытие модулей троянца из списка запущенных приложений
Данная функция используется для получения списка запущенных приложений. Подменяя эту функцию, троянец скрывает свои модули из этого списка. А именно исключает из списка, возвращаемого оригинальной функцией getRunningAppProcesses(), следующие элементы:
Сокрытие модулей троянца из списка установленных пакетов
Данная функция используется для получения списка установленных пакетов. Подменяя эту функцию, троянец скрывает свои модули из этого списка. А именно исключает из списка, возвращаемого оригинальной функцией getInstalledPackages(), следующие элементы:
Сокрытие модулей троянца из списка установленных приложений
Данная функция используется для получения списка установленных пакетов приложений. Подменяя эту функцию, троянец скрывает свои модули из этого списка. А именно исключает из списка, возвращаемого оригинальной функцией getInstalledApplications(), следующие элементы:
Заключение
Приложения, втайне от пользователя получающие root-доступ на мобильном устройстве, предоставляют доступ к зараженному устройству более сложным и опасным зловредам – в частности, мобильному троянцу Triada, самому сложному из известных нам. После попадания на устройство пользователя Triada внедряется практически во все запущенные процессы и продолжает существовать только в оперативной памяти. Кроме того, все отдельно запущенные процессы троянца скрываются от пользователя и других приложений. В итоге обнаружение и удаление троянца становится крайне затруднительным как для пользователя, так и для защитных решений антивирусных компаний.
Основная функциональность троянца направлена на перенаправление финансовых SMS-транзакций, осуществляемых в ходе оплаты пользователем различного дополнительного контента в легитимных приложениях. После оплаты деньги пользователя попадают не к разработчику ПО, а к злоумышленникам. В зависимости от того, получает ли пользователь оплаченный контент, троянец совершает хищение финансовых средств либо у пользователя (если контент пользователем не получен), либо у разработчиков легитимного ПО (если контент пользователем получен).
Очевидно, что троянец Triada разработан киберпреступниками, которые очень хорошо разбираются в атакуемой мобильной платформе. Спектр техник, использованных данным троянцем, не встречается ни в одном из известных нам мобильных зловредов. Использованные методы сокрытия и получения персистентности позволяют эффективно избегать обнаружения и удаления всех компонетов зловреда после их установки на зараженном устройстве, а модульная архитектура позволяет злоумышленникам расширять и менять функциональность, и ограничивают их только возможности операционной системы и установленные на устройстве приложения. Так как зловред проникает во все приложения, установленные на системе, киберпреступники потенциально могут модифицировать их логику, чтобы реализовать новые векторы атаки на пользователей и максимизировать свою прибыль.
По сложности Triada не уступает Windows-зловредам, что знаменует своеобразный Рубикон в эволюции угроз, направленных на платформу Android. Если раньше большинство троянцев под эту платформу были довольно примитивными, то теперь «на сцену» выходят новые угрозы – с высоким уровнем технической сложности.