Developers: It’s super easy to bypass Android’s hidden API restrictions — детальный рассказ, как обойти защиту на доступ к скрытым методам в Android 9 и выше.
Как и любая другая ОС, Android предоставляет разработчикам доступ к обширному API, который позволяет вызывать те или иные функции ОС. Этот API включает в себя ряд скрытых, но иногда очень полезных функций, например возможность развернуть строку состояния. Вызвать эти функции напрямую не получится, так как их просто нет в SDK. Но можно использовать модифицированный SDK (сложно) или рефлексию (очень просто).
Рефлексия позволяет дотянуться до любых методов и полей классов, что, конечно же, можно использовать для не совсем легальной деятельности. Поэтому, начиная с Android 9, Google создала черный список методов и полей, недоступных для вызова с помощью рефлексии. Если приложение попробует их вызвать, то либо будет принудительно остановлено, либо получит предупреждение (в случае с методами из серого списка).
Но есть в этой защите одна проблема — она основана на проверке имени вызывающего процесса. А это значит, что, если мы не будем вызывать метод напрямую, а попросим саму систему сделать это за нас, проверка даст добро (не может же она запрещать саму себя).
Итак, стандартный способ вызова скрытого метода с помощью рефлексии (не работает, приложение завершается):
val someHiddenClass = Class.forName("android.some.hidden.Class") val someHiddenMethod = someHiddenClass.getMethod("someHiddenMethod", String::class.java) someHiddenMethod.invoke(null, "some important string")
Новый способ вызова скрытого метода с помощью двойной рефлексии (работает, потому что вызывает метод не наше приложение, а сама система):
val forName = Class::class.java.getMethod("forName", String::class.java) val getMethod = Class::class.java.getMethod("getMethod", String::class.java, arrayOf<Class<*>>()::class.java) val someHiddenClass = forName.invoke(null, "android.some.hidden.Class") as Class<*> val someHiddenMethod = getMethod.invoke(someHiddenClass, "someHiddenMethod", String::class.java) someHiddenMethod.invoke(null, "some important string")
Но и это еще не все: с помощью этого трюка мы можем вызывать очень интересный скрытый метод setHiddenApiExemptions
, который позволяет (бам!) добавить нужные нам методы в исключения и вызывать их с помощью простой рефлексии.
Следующий код прикажет системе добавить в исключения вообще все скрытые методы:
val forName = Class::class.java.getDeclaredMethod("forName", String::class.java) val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String::class.java, arrayOf<Class<*>>()::class.java) val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") as Class<*> val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) as Method val vmRuntime = getRuntime.invoke(null) setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))
Стоит отметить, что Google в курсе этой проблемы. Они отклонили баг-репорт о возможности вызова скрытых методов под предлогом, что это защита от дураков, а не security-фича.
Detecting Magisk Hide — статья о том, как обнаружить присутствие Magisk (и, как следствие, прав root) на устройстве.
Magisk — известный, а в последнее время единственный инструмент systemless-рутинга устройств. Он позволяет получить права root без изменения системного раздела, а также применить различные системные твики. Одна из широко используемых возможностей Magisk — функция Magisk Hide, которая позволяет полностью скрыть сам Magisk и наличие прав root на устройстве от выбранных приложений.
Принцип работы Magisk основан на подключении поверх файловой системы системного раздела другой файловой системы (оверлея), содержащей бинарный файл su (необходимый для получения прав root) и нужные для его работы компоненты. Подключение происходит на ранних этапах загрузки, но, если активирован Magisk Hide, он отключает оверлей для выбранных приложений. Другими словами, обычные приложения будут видеть содержимое оверлея, а те, что указаны в настройках Magisk Hide, — нет. С их точки зрения смартфон будет не рутован.
Но есть в Magisk Hide один изъян. Дело в том, что, если приложение, которое находится в списке для скрытия root, запустит сервис в изолированном процессе, Magisk также отключит для него оверлей, но в списке подключенных файловых систем (/proc/<pid>/mounts
) этот оверлей останется. Соответственно, чтобы обнаружить Magisk, необходимо запустить сервис в изолированном процессе и проверить список подключенных файловых систем.
Автор утверждает, что метод работает для последней версии Magisk на Android 8.0–10.0. Proof of concept можно найти на GitHub.
Why Does Google Think My App Is Harmful? — выступление Алека Гертина (Alec Guertin) на Android Dev Summit’19, посвященное облачному антивирусу Google Play Protect.
В своей работе Google Play Protect полагается на свыше чем 30 тысяч серверов, которые проводят статический анализ и постоянно запускают в эмуляторах и на реальных устройствах приложения, опубликованные в Google Play и за его пределами. Система обращает внимание на следующее поведение приложений:
Отдельно автор доклада остановился на том, как рядовому разработчику не попасть под подозрение. Google Play Protect может посчитать твое приложение не вызывающим доверия в следующих случаях:
Performance Myth Busters — презентация разработчиков компилятора ART, используемого в Android для JIT/AOT-компиляции приложений, о мифах повышения производительности приложений. Итак, мифы.
foo.bar
против foo.getBar()
), надеясь повысить производительность. Однако это ничего не дает, потому что компилятор ART умеет инлайнить геттеры, превращая их в обычные обращения к полям.Securing Network and Inter-App Communications on Android — вводная статья о том, как обезопасить коммуникацию приложения с сетевым сервером и с другими приложениями.
Одна из основных проблем сетевой коммуникации заключается в возможности перехвата трафика, поэтому первое, что необходимо сделать при разработке приложения, — добавить файл сетевой конфигурации, который запретит использовать незашифрованные подключения (начиная с Android 7.0). Сам файл может иметь произвольное имя, но должен располагаться в каталоге res/xml
. Также на него нужно сослаться из манифеста:
<?xml version="1.0" encoding="utf-8"?> <manifest ... > <application android:networkSecurityConfig="@xml/config" ...> ... </application> </manifest>
Далее приведен файл конфигурации, который запрещает незашифрованный трафик для всех доменов за исключением localhost, а также позволяет использовать для debug-сборок собственный центр сертификации. Необязательно включать в конфиг все эти опции.
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <!-- Запретить незашифрованный трафик для всех доменов, кроме перечисленных в исключениях --> <base-config cleartextTrafficPermitted="false" /> <!-- Домены, с которыми разрешен обмен незашифрованным трафиком --> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">localhost</domain> <trust-anchors> <!-- Кроме системных, также доверяем сертификату из файла debug_certificate --> <certificates src="system" /> <certificates src="@raw/debug_certificate" /> </trust-anchors> </domain-config> <!-- Debug-версия приложения будет доверять также нашему собственному CA --> <debug-overrides> <trust-anchors> <certificates src="@raw/my_ca"/> </trust-anchors> </debug-overrides> </network-security-config>
Конфигурационный файл также можно использовать для Certificate Pinning. Это нужно для того, чтобы приложение могло удостовериться, что удаленный сервер действительно использует настоящий сертификат безопасности. Для этого надо получить хеш SHA-256 этого сертификата и прописать его в нужное поле (его можно узнать с помощью браузера):
<domain-config> <domain includeSubdomains="true">website.net</domain> <pin-set expiration="2020-04-16"> <pin digest="SHA256">хеш</pin> <pin digest="SHA-256">хеш</pin> </pin-set> </domain-config>
Обмен данными между приложениями тоже необходимо защищать. Стандартный механизм обмена сообщениями в Android — это интенты (intent). Они позволяют отправить другому приложению (или группе приложений) сообщение или вызвать ту или иную функцию. Интенты могут быть направлены одному приложению или быть широковещательными (я отправляю сообщение системе, и она пусть разбирается, кому сообщение предназначено, — например, вызовет стандартный браузер, чтобы открыть указанную веб-страницу). Проблема с широковещательными интентами в том, что их могут получить приложения, с которыми не стоит обмениваться информацией. Чтобы решить проблему, можно вызвать диалог выбора, и тогда пользователь сможет сам указать приложение, которое должно получить интент:
val intent = Intent(Intent.ACTION_SEND) val activityList = packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL) when { activityList.size > 1 -> { val chooser = Intent.createChooser(intent, "Choose an App") startActivity(chooser) } intent.resolveActivity(packageManager) != null -> startActivity(intent) else -> Toast.makeText(this, "No App to launch with", Toast.LENGTH_LONG).show() }
Но что, если ты хочешь защитить приложение от получения любых интентов, кроме отправленных другими твоими приложениями (например, ты реализовал систему плагинов)? Для этого можно создать новое полномочие и автоматически предоставить его приложениям, подписанным тем же ключом.
Сначала объявляем разрешение:
<permission android:name="packageName.HelloWorldPermission" android:protectionLevel="signature" />
Затем защищаем с его помощью нужный компонент приложения:
<provider android:name="android.support.v4.content.FileProvider" ... android:permission="packageName.HelloWorldPermission"/>
В данном случае мы защитили ContentProvider, но также его можно сделать совсем невидимым для других приложений с помощью атрибута android:exported="false"
.
Kotlin extension functions: more than sugar — краткая статья о пользе функций-расширений Kotlin. Тех самых, что позволяют добавить метод к любому классу (своему или чужому) на лету.
string.emojify()
выглядит явно лучше, чем emojify(string)
, и тем более лучше, чем StringUtils.emojify(string)
.Kotlin Delegates in Android: Utilizing the power of Delegated Properties in Android development — статья о делегированных свойствах в Kotlin и о том, как их можно применять при разработке для Android.
Делегированные свойства — это поля класса (или глобальные переменные), обращение к которым вызовет код специального класса-делегата. Простейший пример использования делегированных свойств будет выглядеть так:
class TrimDelegate : ReadWriteProperty<Any?, String> { private var trimmedValue: String = "" override fun getValue( thisRef: Any?, property: KProperty<*> ): String { return trimmedValue } override fun setValue( thisRef: Any?, property: KProperty<*>, value: String ) { trimmedValue = value.trim() } }
Все, что делает этот класс-делегат, — триммит строку (отбрасывает начальные и конечные пробелы), записанную в переменную. Далее, если объявить переменную, используя этот класс-делегат, записанные в нее строки будут автоматически триммиться:
var param: String by TrimDelegate() param = " string "
В Android делегированные свойства очень удобно использовать для обращения к опциям с помощью SharedPreferences. Просто создай следующую функцию-расширение для класса SharedPreferences:
fun SharedPreferences.string( defaultValue: String = "", key: (KProperty<*>) -> String = KProperty<*>::name ): ReadWriteProperty<Any, String> = object : ReadWriteProperty<Any, String> { override fun getValue( thisRef: Any, property: KProperty<*> ) = getString(key(property), defaultValue) override fun setValue( thisRef: Any, property: KProperty<*>, value: String ) = edit().putString(key(property), value).apply() }
Объяви переменную, которая будет привязана к нужной опции, и просто записывай/читай значения. Они будут автоматически сохранены в файл настроек:
var option3 by prefs.string( key = { "option3" }, defaultValue = "default" ) option3 = "new_value"
Чтобы взломать сеть Wi-Fi с помощью Kali Linux, вам нужна беспроводная карта, поддерживающая режим мониторинга…
Работа с консолью считается более эффективной, чем работа с графическим интерфейсом по нескольким причинам.Во-первых, ввод…
Конечно, вы также можете приобрести подписку на соответствующую услугу, но наличие SSH-доступа к компьютеру с…
С тех пор как ChatGPT вышел на арену, возросла потребность в поддержке чата на базе…
Если вы когда-нибудь окажетесь в ситуации, когда вам нужно взглянуть на спектр беспроводной связи, будь…
Elastic Security стремится превзойти противников в инновациях и обеспечить защиту от новейших технологий злоумышленников. В…