Получи случайную криптовалюту за регистрацию!

Записки гиканутого

Логотип телеграм канала @channel_n5 — Записки гиканутого З
Логотип телеграм канала @channel_n5 — Записки гиканутого
Адрес канала: @channel_n5
Категории: Технологии
Язык: Русский
Страна: Россия
Количество подписчиков: 306
Описание канала:

Android: хакинг, кодинг и прочая жесть @ezobnin

Рейтинги и Отзывы

4.00

2 отзыва

Оценить канал channel_n5 и оставить отзыв — могут только зарегестрированные пользователи. Все отзывы проходят модерацию.

5 звезд

1

4 звезд

0

3 звезд

1

2 звезд

0

1 звезд

0


Последние сообщения

2021-12-13 11:14:55 Coroutines and Java Synchronization Don't Mix - небольшая заметка о неочевидной для некоторых программистов особенности взаимодействия корутин Kotlin и блокировок.

Всем мы знаем про аннотацию @Synchronized (или блок synchronized), которая говорит о том, что код функции может выполняться только в одном потоке одновременно:

repeat(2) {
thread { criticalSection() }
}

@Synchronized
fun criticalSection() {
println("Starting!")
Thread.sleep(10)
println("Ending!")
}

Два потока в этом примере выполнят код функции последовательно, один за другим:

Starting!
Ending!
Starting!
Ending!

Однако если мы заменим потоки на корутины, то все изменится:

val scope = CoroutineScope(Job())

repeat(2) {
scope.launch { criticalSectionSuspending() }
}

@Synchronized
suspend fun criticalSectionSuspending() {
println("Starting!")
delay(10)
println("Ending!")
}

Вывод будет таким:

Starting!
Starting!
Ending!
Ending!

Другими словами аннотация @Synchronized буд-то бы не работает в случае корутин.

На самом деле объяснение в том, что обе корутины в данном примере работают в одном потоке. А это влечет за собой два следствия:

1. Компилятор, поняв, что в synchronized-блок входит только один поток, может полностью удалить синхронизацию;
2. Блок synchronized обладает свойством reentrace, когда один и тот же поток может заходить в synchronized-блок, не снимая блокировку.

Другими словами, @Synchronized просто не имеет смысла для корутин, работающих в одном потоке. И вместо нее следует использовать класс Mutex.
68 views08:14
Открыть/Комментировать
2021-12-08 15:31:33 Are iPhones Really Better for Privacy? Comparative Study of iOS and Android Apps - исследование использования трекинговых библиотек в различных приложениях для Android и iOS. Авторы работы взяли 12 000 приложений для каждой платформы, проанализировали их код и сетевых подключения, и пришли к следующим выводам:

* В среднем приложения используют около трех различных трекинговых библиотек;

* 3.73% приложений для Android и 3.13% приложений для iOS используют больше 10 трекинговых библиотек;

* 88.73% приложений для Android и 79.35% приложений для iOS содержат хотя бы одну трекинговую библиотеку;

* Самая популярные трекинговые библиотеки на Android: Google Play Services (87.3%), Google AdMob (61.7%), Google Firebase (57.6%);

* Самая популярные трекинговые библиотеки на iOS: SKAdNetwork (69.6%), Google Firebase (53.9%), Facebook (25.5%);

* Самые используемые разрешения в приложениях для Android (исключая те, что даются без запроса пользователя): доступ к карте памяти, местоположению и камере;

* Самые используемые разрешения в приложениях для iOS: хранилище фотографий, камера, местоположение;

* Самые популярные домены для отправки статистики: googleads.g.doubleclick.net (Android) и app-measurement.com (iOS);

* Большая часть трекинговых компаний принадлежит Alphabet (Google) и Facebook;
104 views12:31
Открыть/Комментировать
2021-12-01 10:25:19 Re-route Your Intent for Privilege Escalation - презентация с BlackHat Europe 2021, посвященная уязвимостям, связанным с использованием так называемых PendingIntent в приложениях для Android.

Интенты (Intent) - одна из ключевых идей Android. Интенты позволяют пересылать сообщения между различными компнентами приложений и системных сервисов с целью вызывать определенное действие. Запуск приложений, открытие ссылок, инициации звонков и многое другое в Android выполняется с помощью интентов.

Поверх интентов построен другой механизм - отложенные интенты (Peding intent). Он позволяет передать интент другому приложению или компоненту системы, чтобы то могло отправить интент от имени передавшего его приложения.

Проблема отложенных интентов только в том, что записанный в них интент в ряде случаев можно изменить (это можно сделать если не установлен флаг FLAG_IMMUTABLE) и в итоге выполнить произвольное действие от имени передавшего pending intent приложения.

Отложенные интенты чаще всего используются в следующих компонентах:

1. Уведомления. Чтобы перехватить их мы можем создать NotificationListenerService, который будет слушать все уведомления приложений и извлекать из них отложенные интенты.
2. SliceProvider. Этот механизм используется для встраивания частей приложения в другие приложения, например, встраивания переключателя быстрых настроек в окно ассистента. Мы можем получить слайс с помощью класса SliceViewManager или ContentResolver и затем получить peding intent всех слайсов.
3. MediaBrawserService. Механизм, позволяющий приложению дать доступ к своей медиатеке с возможностью включить проигрывание медиафайлов. Получить pending intent можно подключившись к приложению с помощью класса MediaBrowser.
4. Виджеты рабочего стола используют отложенные интенты в качестве действий при нажатии на свои элементы. Класс AppWidgetHost позволяет приложению прикинуться лаунчером и получить доступ к виджетам приложений. Далее pending intent можно извлечь из самого виджета.

В качестве примера уязвимости в одном из этих компонентов приведем CVE-2020-0188. Это уязвимость в SliceProvider'е стандартных настроек Android. Благодаря тому, что peinding intent был открыт для изменения, его можно было вытащить с помощью `ContentResolver`а затем изменить так, чтобы прочитать приватные файлы приложения "Настройки":

Intent hijackIntent = new Intent();
hijackIntent.setPackage(getPackageName());
hijackIntent.setDataAndType(Uri.parse("content://com.android.settings.files/my_cache/NOTICE.html"), "txt/html");
hijackIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
pi.send(getApplicationContext(), 0, hijackIntent, null, null);

По сути этот код заставляет приложение "Настройки" передать ссылку на свой внутренний файл с правами на чтение и запись приложению-эксплойту. Остается только принять интент и можно делать с файлом все, что угодно.

Рекомендации разработчикам:

* По возможности указывай флаг FLAG_IMMUTABLE при создании pending intent;
* При необходимости использовать модифицируемый pending intent, используй явный интент и заполняй поле ComponetName. В этом случае злоумышленник не сможет перенаправить интент;
* Используй утилиту PendingIntentScan для сканирования приложения на наличие модифицируемых pending intent;
142 views07:25
Открыть/Комментировать
2021-11-26 10:31:51 Kotlin Interview Cheat Sheet и Interview Questions for Android Developer - две статьи о частых вопросах на собеседованиях на должность разработчика приложений для Android. Пройдем мимо совсем тривиальных вопросов вроде разницы между val и var и рассмотрим наиболее интересные:

1. Разница между методами setValue() и postValue() в MutableLiveData? Первый используется из основого потока, второй - из фонового.

2. Как проверить, что lateinit-свойство было инициализировано? С помощью метода isInitialized().

3. Разница между object и companion object? Первый инициализируется при первом доступе, второй во время загрузки класса, к которому он привязан.

4. Разница между операторами == и ===? Первый используется для проверки значений объектов, второй - ссылок.

5. В чем преимущество оператора when в Kotlin перед switch в Java? When намного мощнее, его можно использовать как выражение, а внутри выполнять сложные сравнения.

6. В чем разница между основным и вторичным конструктором класса в Kotlin? Первичный конструктор объявляется в самом объявлении класса, сразу после имени класса; он не может содержать кода инициализации (его следует выносить в блок init). Для объявления вторичного конструктора используется блок constructor, в котором можно не только инициализировать поля, но и выполнять код.

7. Можно ли использовать несколько блоков init? Да, их можно использовать чтобы инициализировать различные по назначению и смыслу компоненты класса.

8. Что такое suspend-функция? Это функция, исполнение которой может быть остановлено и возобновлено позже. Такие функции обычно используются для последовательного выполнения асинхронного кода.

9. Основная разница между onPause() и onClose()? onPause вызывается даже когда на экране появляется диалог, например диалог подтверждения разрешения. Метод onClose будет вызван только когда текущая активность сменится другой полноэкранной активностью.

10. Чем отличается AndroidViewModel от ViewModel? Первая включает в себя контекст приложения.

11. Зачем нужна Jetpack Paging Library? Эта библиотека позволяет загружать и отображать данные небольшими порциями.

12. Зачем нужна Jetpack Navigation Component? Он значительно упрощает понимание и управление навигацией внутри приложения.

13. В каком потоке работает сервис? В основном потоке приложения, UI-потоке. Но при желании поток можно изменить.

14. В чем разница между сервисом и Intent Service? Классический сервис предназначен для выполнения долгих фоновых операций, Intent Service предназначен для небольших задач, которые можно отдать на исполнение и забыть.

15. Зачем использовать ProGuard? ProGuard (в настоящее время он заменен на внутреннюю реализацию от Google) позволяет уменьшить размер APK, удалить из него неиспользуемые классы и затрудняет реверс-инжиниринг.

16. Что такое Corutine Dispatcher? Он определяет, в каком потоке будет выполняться корутина.

17. Что такое Couroutine Scope? Он определяет жизненный цикл корутины. Корутины, принадлежащие одному Scope, будут завершены когда закончится жизненный цикл Coroutine Scope.
137 views07:31
Открыть/Комментировать
2021-11-25 10:58:51 Compose UI and the death of androidx.lifecycle.ViewModel - заметка о том, почему разработчики Android не рекомендуют использовать androidx.lifecycle.ViewModel совместно с Jetpack Compose и почему появление Compose фактически означает смерть ViewModel, даже если вы используете паттерн MVVM.

Ответ на этот вопрос довольно простой: androidx.lifecycle.ViewModel был создан для того, чтобы привязать ViewModel к жизненному циклу активности или фрагмента. В приложениях, написанных с использованием Jetpack Compose, рекомендуется использовать только одну активность и не использовать фрагменты вообще. Другими словами - проблема, решением которой был androidx.lifecycle.ViewModel, просто перестала существовать и поэтому он стал не нужен.

В приложениях на Jetpack Compose, ViewModel должна быть обычным классом, жизненный цикл которого будет равен жизненному циклу всего приложения. Если же необходимо привязать ViewModel к жизненному циклу активности, то достаточно инициализировать его в методе onCreate() активности.
140 views07:58
Открыть/Комментировать
2021-11-23 09:59:51 7 things you should know before using Jetpack Compose - небольшой FAQ по Jetpack Compose для тех, кто раздумывает, стоит ли использовать новый UI-фреймворк.

* Jetpack Compose уже стабилен? Да, по словам Google версия 1.0.0 уже полностью готова для продакшена;

* Можно ли использовать Jetpack Compose с Java? Нет, Compose завязан на многие возможности Kotlin, такие как suspend-функции и плагины компилятора;

* Какие минимальные требования? Android 5.0 и Android Studio Arctic Fox (2020.3.1);

* Как Compose влияет на производительность и время сборки? Согласно замерам Compose способен сделать приложение быстрее, а размер APK - меньше, но только при условии использования исключительно Compose, без примешивания традиционных элементов интерфейса; в последнем случае время сборки и размер APK могут незначительно возрасти;

* Можно ли использовать Compose в существующем проекте? Да, Compose может использоваться параллельно и даже совместно с традиционной системой UI Android; более того, миграцию на Compose рекомендуется выполнять постепенно, а не сразу для всего приложения;

* Можно ли использовать Compose совместно с другими популярными библиотеками? Да, Compose - часть набора библиотек Jetpack и отлично с ними взаимодействует, также его можно использовать совместно с Glide, Coil, Dagger/Hilt, Flow, Retrofit, Ktor, Lottie и многими другими библиотеками;

* Можно ли использовать Compose для мультиплатформенной разработки? В настоящий момент находятся в разработке браузерная и десктопная версии Compose, почти готова версия для умных часов, существует даже версия Compose для разработки консольных приложений.
118 views06:59
Открыть/Комментировать
2021-11-17 10:44:53 Effective Kotlin Item 47: Avoid unnecessary object creation - статья о том, как сделать приложение чуточку быстрее, избавившись от ненужного создания дополнительных объектов.

Основная идея: объекты - дорогое удовольствие. Они занимают память, а на их создание уходит хоть и совсем незначительное по современным меркам, но время. Поэтому даже виртуальная машина Java старается минимизировать создание дополнительных объектов. Например, можно было бы подумать, что две следующих строки должны быть разными объектами, но это не так (оператор === сравнивает ссылки на объект):

val str1 = "Lorem ipsum dolor sit amet"
val str2 = "Lorem ipsum dolor sit amet"
print(str1 == str2) // true
print(str1 === str2) // true

Виртуальная машина, видя два одинаковых объекта типа String, объединяет их в один объект. То же самое происходит с типами Int и Long, но только для чисел от -128 до 127:

val i1: Int? = 1
val i2: Int? = 1
print(i1 == i2) // true
print(i1 === i2) // true

Для хранения каждого объекта используется заголовок из 12 байт, который на 64-битных системах выравнен по 8 байтам. Так что в целом заголовок занимает 16 байт, плюс сама ссылка на объект. Это не так много, но когда имеешь дело с больших количеством одинаковых небольших объектов это играет свою роль. Например, Array будет занимать в пять раз больше места, чем IntArray, просто потому что в первом случае каждое число обернуто в объект.

На создание объектов также уходит время. Оно совсем небольшое при создании одиночных объектов, но может иметь серьезные негативные последствия когда объектов много. Например:

class A

private val a = A()

// Benchmark result: 2.698 ns/op
fun accessA(blackhole: Blackhole) {
blackhole.consume(a)
}

// Benchmark result: 3.814 ns/op
fun createA(blackhole: Blackhole) {
blackhole.consume(A())
}

// Benchmark result: 3828.540 ns/op
fun createListAccessA(blackhole: Blackhole) {
blackhole.consume(List(1000) { a })
}

// Benchmark result: 5322.857 ns/op
fun createListCreateA(blackhole: Blackhole) {
blackhole.consume(List(1000) { A() })
}

Есть несколько способов избежать создания объектов. Один из них: использовать синглтоны. Например, в следующей реализации связного списка объект класса Empty создается при каждом создании списка, хотя он всегда один и тот же:

sealed class LinkedList

class Node(
val head: T,
val tail: LinkedList
) : LinkedList()

class Empty : LinkedList()

// Usage
val list: LinkedList =
Node(1, Node(2, Node(3, Empty())))
val list2: LinkedList =
Node("A", Node("B", Empty()))

Просто заменим его на синглтон:

object Empty : LinkedList()

Еще один способ: использовать пул объектов. Так делает, например, библиотека поддержки корутин. Вместо создания потока для каждой фоновой задачи она поддерживает пул (по сути массив) потоков и запускает код на уже подготовленном потоке. Простейшая реализация пула может выглядеть примерно так:

private val connections: MutableMap =
mutableMapOf()

fun getConnection(host: String) =
connections.getOrPut(host) { createConnection(host) }

Объекты можно кешировать. Например, если какая либо функция при вызове создает объекты, то эти объекты можно где-то сохранить чтобы использовать при следующем вызове функции. Да, такой кеш будет занимать память, и поэтому придется выбирать между скоростью и потребление памяти.

Еще один хороший подход: вынести тяжелые объекты "за скобки". Взгляни на следующие две реализации функции вычисления количества максимальных элементов в коллекции:

fun > Iterable.countMax(): Int =
count { it == this.max() }

fun > Iterable.countMax(): Int {
val max = this.max()
return count { it == max }
}

Вторая реализация будет намного быстрее, потому что она находит максимальный элемент только один раз.

Ну и, наверное, самый простой способ: откладывать создание тяжелых объектов. То есть вместо такого кода:

val a = A()

Писать такой:

val a by lazy { A() }
144 views07:44
Открыть/Комментировать
2021-11-15 10:35:39 How Android Wallpaper Images Can Threaten Your Privacy - занимательная статья о том, как идентифицировать любое устройство на Android по установленным на экране обоям.

Небольшое предисловие. Еще в Android 2.0 появился класс WallpaperManager, который можно использовать для управления установленными на экране обоями. Помимо прочего класс включает метод getDrawable(), который позволяет получить текущие обои в виде изображения. По факту это была уязвимость, которую исправили только в Android 8.1, заставив приложения использовать разрешение READ_EXTERNAL_STORAGE для получения обоев.

В той же Android 8.1 у класса WallpaperManager появился другой метод - getWallpaperColors(), позволяющий извлечь три "главных" цвета из обоев (именно его использует новая система теминга Android 12). Внутри эта функция использует метод k-средних, интересная особенность которого в том, что фактически он создает уникальный "цветовой отпечаток" обоев. В итоге по такому отпечатку можно точно идентифицировать устройство.

Для демонстрации этой техники автор создал приложение и выложил его исходники на github.
136 views07:35
Открыть/Комментировать
2021-11-03 13:05:52 Android 12 Release Notes - официальный документ команды разработчиков Google о новшествах платформы Android 12. Мы сосредоточимся исключительно на новых функциях безопасности.

Ограничения на показ оверлеев. Начиная с новой версии, Android больше не предоставляет разрешение на показ оверлеев (окон поверх экрана) автоматически для предустановленных приложений. Исключение сделано только системных приложений, располагающихся в каталоге /system. ОС также налагает ограничения на оверлеи. Теперь приложения не могут использовать непрозрачные оверлеи, которые пропускают через себя нажатия (ранее они использовались чтобы заставить пользователя нажать определенные элементы интерфейса, перекрыв их безобидным окном). Кроме того, приложения теперь могут попросить систему не показывать оверлеи поверх своих окон с помощью разрешения HIDE_OVERLAY_WINDOWS.

Возможность отключить 2G. Android 12 позволяет отключать поддержку сотовых сетей стандарта 2G в настройках смартфона чтобы защититься от атак, использующих понижение до 2G.

Улучшения Wi-Fi. Android 12 поддерживает WPA3 Hash-to-Element (H2E), WPA2/WPA3-Enterprise transition mode и Transition Disable indication.

Разрешения на использование Bluetooth. Android 12 включает несколько новых разрешений для работы с Bluetooth:

* BLUETOOTH_SCAN - возможность поиска и подключения к новым устройствам;
* BLUETOOTH_ADVERTISE - разрешение на использование Bluetooth advertising;
* BLUETOOTH_CONNECT - возможность подключения к привязанным устройствам;

Эти разрешения заменяют ранее применявшиеся разрешения BLUETOOTH и BLUETOOTH_ADMIN, но только для приложений, собранных для Android 12. Старые приложения продолжат работать как и прежде.

Индикаторы камеры и микрофона. Как и iOS, начиная с 12-ой версии, Android будет показывать индикаторы использования микрофона и камеры в правом верхнем углу экрана.
203 views10:05
Открыть/Комментировать
2021-10-20 10:39:18 Effective Kotlin Item 50: Eliminate obsolete object references - статья о необходимости освобождения объектов в определенных ситуациях.

Одна из важнейших особенности Java и Kotlin - автоматическое управление памятью, когда сборщик мусора сам удаляет неиспользуемые объекты из оперативной памяти. Сборщик мусора существенно упрощает жизнь разработчикам, но он не всегда работает.

Канонический пример когда сборщик мусора не справляется - это активности Android. Рассмотрим следующий пример:

class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...

logError = {
Log.e(
this::class.simpleName,
it.message
)
}
}

//...

companion object {
var logError: ((Throwable) -> Unit)? = null
}
}

Разработчик решил упростить себе жизнь и вынес функцию logError в companion object чтобы всегда иметь к ней доступ. Проблема здесь только в том, что logError внутри себя ссылается на на MainActivity. В итоге если MainActivity будет завершена, она продолжит занимать память - сборщик мусора не сможет ее освободить по причине существования ссылки на активность в объекте-компаньоне.

Решить эту проблему можно заменив жесткую ссылку на WeakReference (хотя по-хорошему проблема решается с помощью системы внедрения зависимостей).

Еще один пример - реализация стека:

class Stack {
private var elements: Array =
arrayOfNulls(DEFAULT_INITIAL_CAPACITY)
private var size = 0

fun push(e: Any) {
ensureCapacity()
elements[size++] = e
}

fun pop(): Any? {
if (size == 0) {
throw EmptyStackException()
}
return elements[--size]
}

private fun ensureCapacity() {
if (elements.size == size) {
elements = elements.copyOf(2 * size + 1)
}
}

companion object {
private const val DEFAULT_INITIAL_CAPACITY = 16
}
}

Она абсолютно корректна, за исключением одного момента: функция pop() не удаляет элемент из массива. Это значит, что если создать стек из 1000 элементов, а затем удалить 999 из них, стек все равно будет занимать память, необходимую для хранения 1000 элементов. Исправляется код вот так:

fun pop(): Any? {
if (size == 0)
throw EmptyStackException()
val elem = elements[--size]
elements[size] = null
return elem
}

Как обнаружить такие проблемы? Стоит научиться пользоваться анализатором хипа (такой есть в стандартной поставке Android Studio). Также поможет инструмент LeakCanary. Он автоматически оповестит обо всех утечка активностей.
186 views07:39
Открыть/Комментировать