Android: скрываемся от троянов и готовимся к Android Q

Сегодня в выпуске: скрываем приложение от троянов, боремся с утечками памяти, создаем неубиваемый сервис, работаем с сенсорами температуры, отлаживаем приложение прямо на устройстве и готовим свое приложение к ограничениям Android Q. А также: подборка первоклассных инструментов пентестера и библиотек для разработчиков.

Почитать

Как скрыть приложение от троянов

Mobile Malware Analysis: Overlay and How to Counter it (partly) — небольшая статья о том, как скрыть имя пакета приложения от трояна.

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

  1. Читают список процессов, чтобы узнать, какое приложение в данный момент находится на экране.
  2. Если имя пакета текущего приложения совпадает с именем пакета целевого приложения (банковский клиент, например), троян показывает оверлей поверх окна приложения с поддельными полями ввода данных (логин:пароль, номер банковской карты и так далее).

Инженеры Google знают об этой проблеме, поэтому начиная с Android 7 получить доступ к списку запущенных приложений можно только с помощью API UsageStats или сервиса Accessibility. И тот и другой требуют от пользователя перейти в настройки, активировать переключатель напротив нужного приложения и согласиться с предупреждающим сообщением. Однако на версиях Android ниже 7 (API < 24) все гораздо проще — достаточно вызвать одну функцию или прочитать синтетический файл:

  • <= 19: runningTask.get(0).topActivity
  • 20, 21: getRunningAppProcesses().get(0).processName
  • 22, 23: файлы /proc/pid/cmdline и /proc/pid/stat

Автор задался вопросом, можно ли защитить пользователей Android 6 и ниже от фишинга, и в итоге пришел к следующему решению:

try { 
    Method setter = android.os.Process.class.getMethod("setArgV0", String.class); 
    setter.invoke(android.os.Process.class, text); } 
    catch (NoSuchMethodException e) { e.printStackTrace(); } 
    catch (IllegalAccessException e) { e.printStackTrace(); }
    catch (InvocationTargetException e) { e.printStackTrace(); }
}

Данный код использует рефлексию для доступа к методу Process.setArgv0(), который меняет первый переданный процессу аргумент (в UNIX-системах он равен имени приложения, а в Android имя приложения равно имени его пакета). Способ работает на всех версиях Android до Q, где данный метод был внесен в черный список.

Разработчику

Боремся с утечками памяти

Everything you need to know about Memory Leaks in Android — большая статья об утечках памяти и о том, как их избежать. Автор приводит три типичных примера утечек.

Пример 1. Потоки

public class ThreadActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task);
        new DownloadTask().start();
    }

    private class DownloadTask extends Thread {
        @Override
        public void run() {
           SystemClock.sleep(2000 * 10);
        }
    }
}

Данный код создает поток, который спит 20 секунд (в реальном приложении это может быть какая-либо длительная работа). Проблема здесь в том, что в Java объекты вложенных классов (в данном случае DownloadTask) хранят ссылку на внешний объект (Activity), поэтому, даже если пользователь закроет активность, сборщик мусора не сможет освободить занятую ей память до тех пор, пока метод run не закончит свою работу и занятая объектом класса DownloadTask память не будет освобождена.

Пример 2. Синглтоны

public class SingletonManager {
    private static SingletonManager singleton;
    private Context context;

    private SingletonManager(Context context) {
        this.context = context;
    }

    public synchronized static SingletonManager getInstance(Context context) {
        if (singleton == null) {
            singleton = new SingletonManager(context);
        }
        return singleton;
    }
}

В данном случае проблема кроется не в самом коде, а в способе его использования:

public class LoginActivity extends Activity {
      @Override
    protected void onCreate(Bundle savedInstanceState) {
        //...
        SingletonManager.getInstance(this);
    }
}

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

SingletonManager.getInstance(getApplicationContext());

Пример 3. Листенеры

public class LoginActivity extends Activity implements LocationListener {
    @Override
    public void onLocationUpdated(Location location){
        // Do something
    }

    @Override
    protected void onStart(){
        LocationManager.getInstance().register(this);
    }

    @Override
    protected void onStop(){
        LocationManager.getInstance().unregister(this);
    }
}

Данный код регистрирует листенер для получения информации о текущем местоположении при старте активности и отключает его при корректном завершении. Однако, если система экстренно завершит работу активности, метод onStop не будет вызван и LocationManager продолжит хранить ссылку на активность.

Решить эту проблему можно так:

protected void onDestroy() {
    LocationManager.getInstance().unregister(this);
    super.onDestroy;
}

Автор статьи создал проект avoid-memory-leak-android с демонстрацией этих и других типов утечек памяти, а также исправленным кодом. Для поиска утечек в своем приложении можно использовать LeakCanary.

Сервис, который никогда не умирает

Building an Android service that never stops running — статья о создании сервиса, который никогда не умирает. Автор приводит пример, когда необходимо заставить приложение выполнять какую-то работу в фоне с жестко заданной периодичностью.

Разработчики Android настоятельно рекомендуют использовать для этого JobScheduler (или более современный WorkManager), но его проблема в том, что энергосберегающие механизмы Android (Doze и App Standby) могут откладывать выполнение работы на неопределенный срок. Классические сервисы (Service) тоже не подходят для этой задачи, так как, кроме энергосберегающих механизмов, на их работу влияют ограничения Android 8 — сервис будет завершен вскоре после ухода приложения в фон.

Решение проблемы состоит в том, чтобы использовать Foreground Service, который будет получать частичный (partial) wakelock, чтобы не быть остановленным энергосберегающими механизмами. Полный код сервиса есть в оригинальной статье, а здесь приведем только самую мякотку — код, ответственный за получение вейклока и выполнение задачи. Запускать его следует из метода startCommand сервиса:

private fun startService() {
    if (isServiceStarted) return
    log("Starting the foreground service task")
    Toast.makeText(this, "Service starting its task", Toast.LENGTH_SHORT).show()
    isServiceStarted = true
    setServiceState(this, ServiceState.STARTED)

    // We need this lock, so our service gets not affected by Doze Mode
    wakeLock =
        (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
            newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply {
                acquire()
            }
        }

    // We’re starting a loop in a coroutine
    GlobalScope.launch(Dispatchers.IO) {
        while (isServiceStarted) {
            launch(Dispatchers.IO) {
                pingFakeServer()
            }
            delay(1 * 60 * 1000)
        }
        log("End of the loop for the service")
    }
}

Работаем с сенсорами температуры

Keeping cool in Android Q with the Thermal API — краткая заметка о новом API Android Q, позволяющем получать информацию о текущем уровне нагрева устройства и о переходе в состояние тротлинга (когда система искусственно занижает производительность устройства).

Сам API крайне прост. Получить текущий уровень нагрева можно с помощью всего двух строк (Kotlin):

val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
val currentStatus = powerManager.currentThermalStatus

Значение currentStatus будет одним из следующих:

THERMAL_STATUS_NONE = 0;
THERMAL_STATUS_LIGHT = 1;
THERMAL_STATUS_MODERATE = 2;
THERMAL_STATUS_SEVERE = 3;
THERMAL_STATUS_CRITICAL = 4;
THERMAL_STATUS_EMERGENCY = 5;
THERMAL_STATUS_SHUTDOWN = 6;

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

API также позволяет следить за изменением уровня нагрева в реальном времени с помощью листенера с интерфейсом OnThermalStatusChangedListener:

powerManager.addThermalStatusListener {
    // Проверяем текущий статус
}

Отладка приложения прямо на устройстве

Android debug tools — обзор нескольких инструментов отладки, включая встроенные в Android Studio отладчик и профайлер, уже несколько раз упоминавшийся нами инструмент Facebook Stetho, сервис AppSpector и библиотеку DebugDrawer.

В контексте этого дайджеста нам наиболее интересен DebugDrawer. В отличие от других инструментов, эта библиотека позволяет отлаживать и профилировать приложение прямо на устройстве с помощью выдвигаемой с одной из сторон приложения панели (drawer).

Подключить библиотеку к проекту очень просто:

debugImplementation 'io.palaima.debugdrawer:debugdrawer:0.8.0'

Далее в метод onCreate основной активности приложения добавляем такой код:

SwitchAction switchAction = new SwitchAction("Test switch", new SwitchAction.Listener() {
    @Override
    public void onCheckedChanged(boolean value) {
        Toast.makeText(MainActivity.this, "Switch checked", Toast.LENGTH_LONG).show();
    }
});

ButtonAction buttonAction = new ButtonAction("Test button", new ButtonAction.Listener() {
    @Override
    public void onClick() {
        Toast.makeText(MainActivity.this, "Button clicked", Toast.LENGTH_LONG).show();
    }
});

SpinnerAction < String > spinnerAction = new SpinnerAction < > (
    Arrays.asList("First", "Second", "Third"),
    new SpinnerAction.OnItemSelectedListener < String > () {
        @Override public void onItemSelected(String value) {
            Toast.makeText(MainActivity.this, "Spinner item selected - " + value, Toast.LENGTH_LONG).show();
        }
    }
);

debugDrawer = new DebugDrawer.Builder(this).modules(
    new ActionsModule(switchAction, buttonAction, spinnerAction),
    new FpsModule(Takt.stock(getApplication())),
    new LocationModule(this),
    new ScalpelModule(this),
    new TimberModule(),
    new OkHttp3Module(okHttpClient),
    new PicassoModule(picasso),
    new GlideModule(Glide.get(getContext())),
    new DeviceModule(this),
    new BuildModule(this),
    new NetworkModule(this),
    new SettingsModule(this)
).build();

Возможности DebugDrawer хорошо видны в коде. Модули можно подключать и отключать независимо друг от друга. Геолокационный модуль умеет спуфить местоположение. Сетевой модуль позволяет вылавливать отдельные сетевые запросы с помощью библиотеки Chuck. Недостаток: нет модуля для инспекции баз данных.

DebugDrawer

Возвращаем два значения из функции

Pair and Triple in Kotlin — статья о специальных классах Kotlin, позволяющих возвращать два или три значения из функции. Это может пригодиться, например, для возврата кода ошибки или в случаях, когда необходимо вернуть несколько значений разных типов.

Декларация Pair (два значения) выглядит так:

Pair ("Hello", "Kotlin")
Pair ("Kotlin", 1)
Pair (2, 20)

Получить значения первого и второго элемента можно следующим образом:

println(variableName.first)
println(variableName.second)

Для создания Pair можно использовать инфиксную функцию to:

fun getWebsite() : Pair<String, String> {
    return "www.mindorks.com" to "the Website is"
}

А для получения значений — деструктивный оператор:

val (url: String, website: String) = getWebsite()

Pair можно превратить в строку с помощью метода toString():

val variableName = Pair (variable1, variable2)
print(variableName.toString())

Или в список из двух элементов с помощью метода toList():

val variableName = Pair (variable1, variable2)
val list = variableName.toList()

Класс Triple полностью аналогичен Pair за исключением того, что он может хранить три значения вместо двух:

val variable1 = "string1"
val variable2 = 1
val variable3 = "string2"

val variableName = Triple (variable1, variable2, variable3)

println(variableName.first)
println(variableName.second)
println(variableName.third)

Ограничения Android Q

Preparing your app for Android Q — статья об изменениях в Android Q, к которым необходимо быть готовым.

  1. Фоновый запуск активностей. Начиная с Android Q приложения больше не могут запускать активности, находясь в фоне. Исключения сделаны для bound-сервисов, таких как Accessibility или сервисы автозаполнения. Приложения, использующие разрешение SYSTEM_ALERT_WINDOW, и приложения, получающие имя активности в системном PendingIntent, также могут запускать активности в фоне. В качестве альтернативы можно использовать так называемые полноэкранные уведомления (метод setFullScreenIntent() в Notification.Builder).
  2. Идентификаторы устройства. Доступ к IMEI и серийному номеру устройства теперь запрещен. Если TargetSdk приложения равен Q, будет выброшено исключение, в противном случае возвращается null. MAC-адрес рандомизируется.
  3. Местоположение в фоне. Android Q позволяет пользователю выбрать, в каких случаях предоставлять приложению доступ к местоположению: всегда или только пока приложение находится на экране. Если TargetSdk приложения равен Q, приложение должно сделать явный запрос на доступ к местоположению в фоне, в противном случае оно получит разрешение автоматически при наличии обычного разрешения на доступ к местоположению. Однако система может вывести уведомление, позволяющее пользователю отозвать это разрешение.
  4. Жесты. В Android Q система навигации на основе жестов получила дальнейшее развитие. Теперь в качестве кнопок «Назад», «Домой» и «Последние приложения» можно использовать свайпы с левой, нижней и правой сторон экрана. Эти жесты могут конфликтовать с жестами, используемыми в самом приложении. Чтобы добавить некоторые регионы экрана в исключения системных жестов, можно использовать метод View.setSystemGestureExclusionRects().
  5. Темная тема. В Android Q появилась темная тема, которую пользователь может выбрать в системных настройках. Чтобы приложение автоматически переключалось на темную тему, его тема должна наследоваться от системной темы DayNight: <style name="AppTheme" parent="Theme.AppCompat.DayNight">. Также необходимо создать отдельные файлы ресурсов для ночной темы, в частности для цветов: values-night/colors.xml.
Запрос разрешения на определение местоположения в Android Q

Инструменты

  • QCSuper — приложение для перехвата фреймов 2G/3G/4G с устройств, использующих модемы Qualcomm;
  • Kali NetHunter App Store — альтернативный магазин приложений со свободным софтом для аудита безопасности;
  • FridaLoader — простая утилита для быстрой установки и запуска сервера Frida в эмуляторе Genymotion;
  • jebscripts — набор скриптов декомпилятора JEB для реверс-инжиниринга обфусцированного кода;
  • Dwarf — графический отладчик на базе Frida;
  • Frida-Android-unpack — Frida-скрипт для дампа dex-файлов приложений, защищенных с помощью упаковщиков;
  • DroidLysis — скрипт для извлечения различных значений из кода и ресурсов приложений;
  • MagiskTrustUserCerts — модуль Magisk, автоматически добавляющий установленные пользователем сертификаты в системное хранилище сертификатов;
  • ipasim — iOS-эмулятор для Windows.
QCSuper

Библиотеки

  • Room-Runtime — модифицированная версия библиотеки AndroidX Room с поддержкой обфускации с помощью ProGuard;
  • Knot — реактивный контейнер состояния для Android и Kotlin;
  • WatchTower — библиотека для отладки OkHTTP-запросов с помощью браузера;
  • Komprehensions — набор Kotlin-функций для разворачивания излишне вложенных функций;
  • sekret — библиотека для автоматического скрытия важных данных из логов;
  • Only — библиотека для запуска функций нужное количество раз в определенной последовательности (например, единовременный запуск экрана приветствия, после чего запуск основной активности);
  • android-components — набор библиотек для создания браузеров от компании Mozilla;
  • clikt — библиотека для работы с командной строкой;
  • PrimeDatePicker — очередной диалог выбора даты с возможностью выбрать отрезок времени;
  • RVcompose — Kotlin DSL для построения динамического интерфейса с помощью RecyclerView;
  • AndroidUtilCode — огромное количество подсобных функций, которые могут пригодиться при разработке для Android.
Шпаргалка по командам ADB
Евгений Зобнин: @ezobnin Редактор рубрики X-Mobile. По совместительству сисадмин. Большой фанат Linux, Plan 9, гаджетов и древних видеоигр.

Комментарии (1)