Владельцы смартфонов, приобретенных на условном «Алиэкспрессе» за условные пятьдесят долларов, исчисляются десятками тысяч. Безусловно, пользоваться такими поделками можно, только вот их производители вынуждены экономить на каждом этапе, и здесь мы говорим даже не о проблемах с железом и качеством сборки — прошивка таких телефонов может содержать большие сюрпризы: малварь, трояны и абсолютно не оптимизированные приложения, которые беспощадно жрут батарею. В этой статье мы покажем, как дать решительный бой всему этому дурно пахнущему программному мусору.
INFO: Самый прямой и брутальный способ решения проблемы зараженной прошивки — накатить чистую и проверенную. 🙂
Откуда берутся вредоносы на устройстве?
Раньше основным каналом поставки на устройство вредоносных приложений были форумы и неофициальные магазины. Немногие уже вспомнят, как vk музыка угоняла логины и пароли от нашей самой популярной социальной сети. Довольные пользователи скачивали ее с 4pda.ru, а потом некоторые из них недоумевали, просматривая свои сессии из стран, где они никогда не бывали. Мы уже писали, что сегодня не так сложно опубликовать вредонос в Google Play, тогда вопрос его распространения становится вопросом поисковой оптимизации в маркете (ASO).
Но сейчас нередко прошивку заражают нечестные посредники или сами производители устройств. В подобном случае спектр зловредной деятельности простирается от банального показа рекламы и кражи личных данных до майнинга наших любимых криптовалют прямо на всех ядрах устройства.
Как найти вредонос?
Странности в поведении устройства обычно всплывают уже в первую неделю использования. Например, одна кастомная клавиатура время от времени показывала межстраничное объявление в самых неожиданных местах. Другие «быстрые браузеры» могут без спроса устанавливать чужие приложения. Заподозрив аномальную активность, с помощью сторонних программ можно посмотреть манифест устройства, вызывающего сомнения. Например, android.permission.SYSTEM_ALERT_WINDOW
разрешает создавать окна поверх всех остальных приложений. Очень удобно для показа рекламы, как ты догадался. А для установки приложений нужен android.permission.REQUEST_INSTALL_PACKAGES
.
Серые накрутчики установок так и работают — сначала заражаем устройства своими установщиками, потом принимаем заказы от легальных или не очень разработчиков на установку. С помощью трюков с accessibility service
можно даже оставить отзыв и оценку в приложении Google Play. Отзывы, оценки и количество установок — важный фактор для ASO любого приложения, так что услуга еще долго будет востребована на рынке.
Как обезвредить вредонос?
Локализовав злокачественный пакет, можно сразу его удалить и успокоиться. Но простое удаление работает, только когда этот пакет не системный. Если пакет системный, можно его удалить с помощью рут-доступа. Возьми стороннее приложение или напиши свой собственный инструмент. Когда рут-прав нет, но вредонос есть в списке приложений, можно просто удалить его обновления и отключить его.
Если на этом мучения пользователя остановились, его можно поздравить. В самых запущенных случаях (нет рута, и вредонос засел в системе) может помочь лишь полная перепрошивка устройства, но она доступна только с разблокированным загрузчиком, а не на всех устройствах можно это сделать (кстати, и не на всех устройствах можно получить рут-доступ). Если загрузчик нельзя разблокировать, нет рут-прав и вредонос не светится в списке приложений в настройках, то лекарство остается только одно — магазин. 🙂
INFO: Перед покупкой нового устройства обязательно найди его ветку на 4pda.ru. Там может уже быть вся нужная информация о нежелательных приложениях, более чистых прошивках и способах получения рут-прав.
Как прочитать манифест установленного приложения?
Для чтения манифестов не требуется специальных разрешений в системе. Любое приложение может это делать без спроса, используя контекст чужого приложения:
Context context = createPackageContext(packageName, CONTEXT_RESTRICTED);
parser = context.getResources().getAssets().openXmlResourceParser("AndroidManifest.xml");
Если пишешь свое приложение, удобнее всего отобразить манифест в компоненте WebView
. Сначала сгенерируй красивый HTML-файл (со стилями CSS и прочим), потом загрузи его в WebView. Есть также и готовые читалки манифестов, в маркете можешь сам поискать по запросу manifest view
.
Как получить множество информации об установленном APK при помощи PackageManager?
С первой версии Android SDK у нас есть замечательный инструмент — класс PackageManager. Его метод getInstalledApplications
вернет список установленных на устройстве приложений (ApplicationInfo), как системных, так и пользовательских. Зная установленные на устройстве приложения, можно оптимизировать рекламу. Например, не показывать рекламу тех, что уже есть. Или же можно просто собирать статистику по пользователям. Есть мнение, что кто-то за эту статистику платит, но мне таких пока не встречалось.
Класс ApplicationInfo содержит много полезной информации (имя пакета, путь до APK-файла, включено или отключено приложение и так далее). Но если ее оказалось мало, то вызови метод getPackageInfo()
для нужного имени пакета приложения, и получишь еще больше данных в классе PackageInfo (версия, время установки, время последнего обновления и прочее).
Как извлечь установленный APK-файл?
Класс PackageManager позволяет нам получить не только список всех зарегистрированных пакетов в системе, но и сами APK-файлы этих пакетов. ApplicationInfo.sourceDir
содержит путь до APK-файла (например, /data/app/имя пакета/base.apk
). Мы можем скопировать файл во внутреннюю кеш-папку нашего приложения, а оттуда уже поделиться с другими приложениями с помощью FileProvider.
Использование FileProvider — рекомендованный Google способ обмена файлами между приложениями. Он, кстати, не требует прав доступа к внешнему хранилищу <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
, так что простое приложение будет иметь на один запрос прав меньше. Вот мы копируем APK в папку с кешем:
public static File copyFileToFilesDir(Context context, String fileName, String name) throws IOException {
File src = new File(fileName);
File dst = new File(context.getCacheDir(), name + ".apk");
FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel();
try {
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inChannel != null) {
inChannel.close();
}
outChannel.close();
}
return dst;
}
Вот как поделиться приложением:
public void shareApk(View view) throws IOException {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("application/xml");
Uri uri = FileProvider.getUriForFile(this, "ru.androidtools.greenbro.files",
Tools.copyFileToFilesDir(this, app.sourceDir, app.packageName));
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file)));
}
Класс PackageManager может сказать нам имя пакета приложения, установившего пакет. Для этого используется метод getPackageManager().getInstallerPackageName()
, который вернет строку. Например, Play Маркет имеет com.google.market
или com.google.market
, Amazon App Store — com.amazon.venezia
, маркет от Samsung — com.sec.android.app.samsungapps
. А вот приложения с F-Droid указывают не на него, а на com.google.android.packageinstaller
, как будто бы их установил сам пользователь.
Зная источник установки и имя пакета самого приложения, можно постучаться в маркеты по конкретным URL. Наличие программы в маркете — уже некоторый повод для самоуспокоения, все-таки их иногда чистят. А вот если приложение было из маркета удалено, то это серьезный сигнал для беспокойства (его, конечно, могли удалить из-за какой-нибудь чепухи, вроде нарушения чужих авторских прав, но, скорее всего, это было что-то реально нехорошее).
Для получения URL приложения в Play Маркет и F-Droid используй строчки
String playStoreUrl = "https://play.google.com/store/apps/details?id=" + packageName;
String fdroidStoreUrl = "https://www.f-droid.org/packages/" + packageName;
Аналогично можно проверять и остальные маркеты. Код простой проверки (запускай только в фоновых потоках, так как используется сеть):
private void checkPlayMarket(String playStoreUrl) {
try {
URL url = new URL(playStoreUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.connect();
int code = connection.getResponseCode();
if (code == 200){
// Есть в маркете
}
if (code == 404){
// Нет в маркете
}
connection.disconnect();
} catch (Exception e) {
Log.e("CheckStoreTask", e.toString());
}
}
Читаем системные ресурсы описания разрешений Android
Бывает, что нужно сделать описание разрешений, используемых приложением. Для таких случаев можно заготовить множество строчек, помноженное на количество локалей… плюс к каждой нужна своя картинка. От таких решений APK нашего конечного приложения будет пухнуть на глазах. А как ты знаешь, есть еще люди в 2017 году (за пределами Москвы их несколько миллионов), для которых размер APK-файла приложения очень важен. Места на хранилище устройства наверняка хватает всем, но очень дорогой интернет заставляет людей учитывать этот показатель при выборе приложения.
Можно зайти с другой стороны и взять описание и ресурсы из самой системы (класс PermissionInfo). Они уже сразу будут локализованы, так что переводчики пока отдохнут. Список разрешений конкретного приложения получаем вот так:
PackageInfo packageInfo = getPackageManager().getPackageInfo(app.packageName, PackageManager.GET_PERMISSIONS);
String[] requestedPermissions = packageInfo.requestedPermissions;
Информацию о разрешениях — так:
PermissionInfo pinfo = getPackageManager().getPermissionInfo(requestedPermissions[i], PackageManager.GET_META_DATA);
Как ты знаешь, разрешения в «Андроиде» разделены по группам. Получаем заголовок: getGroupTitle(pinfo.group); :
private String getGroupTitle(String perm) {
String result = getString(R.string.others_permission);
try {
PermissionGroupInfo groupInfo = getPackageManager().getPermissionGroupInfo(perm, 0);
result = groupInfo.loadLabel(getPackageManager()).toString();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return result;
}
Заголовок разрешения и картинку:
private CharSequence loadItemInfoLabel(PermissionInfo pinfo) {
CharSequence label = pinfo.loadLabel(getPackageManager());
if (label == null) {
label = pinfo.name;
}
return label;
}
private Drawable loadItemInfoIcon(String perm) {
Drawable icon;
icon = Tools.getPermissionDrawable(getPackageManager(), perm);
if (icon == null) icon = ContextCompat.getDrawable(this, R.drawable.ic_perm_device_info);
return icon;
}
Будь осторожен: в любой момент может прийти null, так что имей заглушки на этот случай. Пока!
Источник — xakep.ru
Добавить комментарий