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


Channel's geo and language: Russia, Russian
Category: Darknet


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

Related channels

Channel's geo and language
Russia, Russian
Category
Darknet
Statistics
Posts filter


Годный контент

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

Две лучшие прочитанные мной книги по программированию называются: "Эффективная работа с унаследованным кодом" и "Высоконагруженные приложения: программирование, масштабирование, поддержка". Они принесли мне больше знаний и понимания профессии программиста, чем все книги по алгоритмам и чистому коду вместе взятые. И все это при том, что я почти не работал с legacy-кодом и не занимался программированием высоконагруженных систем.


Три черты современного пользователя

1. Знает слово "баг". Багом может быть все, что угодно, от функции, назначения которой он не понимает, до элемента поведения приложения, который ему не нравится. По мнению пользователя, баг, существующий на его устройстве, обязательно существует на всех остальных устройствах, а разработчик о нем знает, но слишком ленив, чтобы его исправлять.

2. Очень беспокоится за собственную конфиденциальность. Ожидает, что любые данные, к которым хочет получить доступ приложение, будут использованы в недобрых целях против него. При этом пользуется привязанным к аккаунту Google или Apple смартфоном, который каждую минуту передает на сервера в Колифорнии все, что только может передать.

3. В слове "техподдержка" видит только слово "поддержка". Ведь задача человека на другом конце не решать проблемы, а уверить пользователя в том, что проблема будет решена и сказать ему максимальное количество приятных слов: "Спасибо", "Ваше сообщение очень важно для нас", "Вы очень нам помогли". Честные ответы и не желание держать пользователя за идиота расцениваются как оскорбление.


The content is hidden


The content is hidden


Все уже слышали о запуске Windows 11 на Pixel 6? Тут Mishaal Rahman рассказывает как это работает и как проделать почти то же самое на своем смартфоне. "Почти", потому, что вместо Windows 11 он показывает как запустить Linux в режиме полной виртуализации.

Вообще, возможность все это сделать появилась благодаря порту гипервизора KVM в Android 13. Того самого KVM, с помощью которого в Linux запускают другие линуксы и Windows. В данный момент гипервизор доступен только в Android 13, и только для смартфона Pixel 6 (Pro). Кроме того, потребуется разблокировать загрузчик, а затем активировать KVM с помощью fastboot. Далее можно запускать любой дистрибутив Linux (с поддержкой AArch64) с помощью менеджера виртуальных машин crosvm. Последний распространяется в модуле APEX и уже должен быть предустановлен на смартфон.


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

Мы все знаем до каких крайностей могут дойти разработчики мобильного Сбербанк Онлайн в своем стремлении создать самый суперский суперапп среди супераппов. Но не только этим знаменит Сбербанк. Также им не чужды нечестные приемы.

В случае со Сбербанк Онлайн и его уведомлениями эта нечестность проявляется в следующем. В Android у каждого уведомления есть две важные характеристики:

1. Канал, к которому относится уведомление (начиная с Android 8.0);
2. Категория уведомления.

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

Категории - это тоже своего рода способ группировки, но более глобальный. Вы наверняка замечали, что Android в шторке всегда показывает сообщения из мессенджеров, письма, и прочую переписку, выше всех остальных уведомлений. Он это делает читая поле category уведомления. Если приложение указало там "msg" (CATEGORY_MESSAGE), то уведомление считается пришедшим от мессенджера и сдвигается наверх.

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

Выводы: ну... на самом деле так делают и другие приложения, но видеть такое в исполнении "главного банка страны" не очень то приятно.


Обычно я не пишу в канале ничего личного и просто делюсь тем, что сам где-то прочитал. В итоге многие подписчики не знают ни меня, ни того, чем я занимаюсь. Так вот, меня зовут Евгений, я разработчик приложения AIO Launcher и недавно я столкнулся с довольно забавной проблемой.

AIO умеет отображать на рабочем столе уведомления Android в простой и лаконичной форме. Уведомление можно "открыть" тапом по нему, а можно смахнуть, также как в шторке. В целом все это прекрасно работает, за исключением одного приложения: Oculus (оно предназначено для управления VR-гарнитурами Facebook/Meta). Уведомление этого приложения успешно открывались, но ни в какую не хотели смахиваться (хотя через стандартную шторку смахивались без проблем).

Исследование выявило следующую проблему. Все уведомления в Android адресуются с помощью текстового ключа. В исходниках системы он выглядит так:

String sbnKey = user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;

Здесь первый элемент - id пользователя, далее идут пакет приложения, уникальный для приложения id уведомления, тег, который разработчик может использовать для пометки уведомления, и uid пользователя - владельца песочницы приложения. Обычно этот ключ выглядит примерно так:

0|com.example.app|15|null|10453

Все понятно и логично. Но! Оказалось, что Android не следит за форматом поля tag и никак не ограничивает его длину (фактически ограничение накладывается только на размер Parcelable, так что если засунуть туда строку длиной 1 Мб, то приложение будет прибито с исключением TransactionTooLargeException). В итоге туда можно засунуть текст, длиной в несколько экранов, и Android это проглотит. А теперь самое главное: как вы думаете, что помещает туда приложение Oculus?

Правильно. Длиннюший JSON с мета-данными уведомления, большинство из которых дублируют метаданные, которые закрепляет за уведомлением сам Android. Помимо кучи ключей и значений этот JSON в том числе содержит пробелы, которые ломали парзер AIO Launcher и не давали нормально обработать ключ.

Конечно, я сам себе злой буратино и допустил банальнейшую ошибку, но разработчикам Oculus тоже нужно отдать должное. Круче их изобретения только протокол обмена данными, работающий поверх DNS.


@Za_Raczke/how-android-updates-work-a-peek-behind-the-curtains-from-an-insider-1d8e1a48ec0b' rel='nofollow'>How Android updates work: A peek behind the curtains from an insider - хорошая статья о том, как работают обновления Android и почему плохая ситуация с обновлениями в мире Android - это следствие не раздолбайства Google, а следствие самой сути Android как операционной системы с открытым кодом.

Из интересного:

* Android X - это не всегда Android X. Не многие знают, но после ежегодного осеннего релиза каждой новой версии Android, Google выпускает еще три ежеквартальных обновления. Такие обновления называются QPR (Quarterly Platform Release) и обычно они включают в себя исправления багов, появившихся в новой версии Android (по факту, грядущая в марте версия Android 12L - это QPR2). Пользователи смартфонов Pixel получают эти обновления вместе с так называемыми Pixel Feature Drops, включающими в себя помимо обновления Android еще и набор эксклюзивных для этих смартфонов функций. А вот другие производители зачастую просто пропускают QPR.

* Android - не всегда Android. Android - это ОС с открытым исходным кодом и почти каждый производитель вносит в него собственные изменения (то, что пользователи часто ошибочно называют словом "оболочка"). Но это еще не все. Даже если Android на твоем устройстве выглядит как чистый Android - это не значит, что это и есть тот самый AOSP. Например, есть форк Android под названием Codeaurora (CAF). Выглядит он как частый Android, работает также, и используется как база для многих кастомных прошивок. Но это не чистый Android, это вариант Android от компании Qualcomm со множеством изменений, улучшений и доработок.

* Производители смартфонов получают доступ к исходным текстам новых версий Android и патчам безопасности раньше, чем простые люди. Уже в марте производители смартфонов и железа могут запросить доступ исходному коду новой разрабатываемой версии Android. Такая возможность открыта для всех производителей, но взамен они должны взять на себя определенные обязательства по обновлению устройств и подписать специальное соглашение, именуемое "keystone". В итоге производители получают достаточно времени чтобы выпустить обновление для своих смартфонов в день релиза новой версии Android (как это делала компания Essential).

* Чтобы получить возможность устанавливать на свои устройства сервисы Google, производитель должен обеспечить полную совместимость своей сборки Android с "эталонным Android". Для этого производитель отдает системный образ прошивки в Google, и Google запускает на ней множество тестов, в том числе тесты на зловредные приложения и тесты на наличие всех вышедших патчей безопасности (напомню, что Huawei очень любила пропускать патчи безопасности). Также производители могут выполнять свое собственное тестирование. Например, многие сотрудники Google используют смартфоны Pixel, в которых установлена так называемая dogfood-сборка Android, автоматически собирающая информацию о производительности и корректности работы смартфона.

* Большая часть проблем с обновлениями смартфонов связана с работой производителей чипсетов. К примеру, если смартфон построен на чипсете Qualcomm, то производитель смартфона сначала должен дождаться публикации новой версии Android именно производителем чипсета (та самая CAF-версия Android), а затем накатить на нее собственные изменения. Производителям смартфонов очень удобно сваливать все проблемы с обновлениями на тот же Qualcomm, но факт в том, что, например, чипсет Snapdragon 730, вышедший в 2019 году, поддерживается до сих пор, и Qualcomm планирует портировать на него Android 13. А чипсет Snapdragon 660 поддерживался с 2017 года и получил 6 (щесть!) обновлений версии Android.

* На сегодняшний день у производителей смартфонов есть все, чтобы вовремя выпускать обновления: поддержка производителей чипсетов, ранний доступ к исходному коду, Project Treble, сильно упрощающий процесс переноса новой версии Android на старое устройство. Настоящая проблема с обновлениями Android заключается вовсе не в их сложности, а в том, что компании экономят на обновлении смартфонов и предпочитают продавать новые модели вместо поддержки старых.


Сообщают, что Google наконец-то выкатила опцию для отключения 2G на смартфонах, выпущенных с Android 12 на борту. Опция располагается примерно здесь: Настройки -> Сеть и интернет -> SIM-карты -> Разрешить 2G.

Всем пользователям Android 12 я бы рекомендовал проверить наличие опции и отключить ее если она доступна. Причина: 2G - старый стандарт мобильный сетей, не способный обеспечить достаточную защиту абонента. Типичный вектор атаки на смартфон жертвы - с помощью поддельной базовой станции LTE заставить смартфон переключиться на 2G-стандарт, а затем использовать уязвимости 2G для перехвата СМС и разговоров.


Google’s Android 12 update has been the rockiest in years - The Verge опубликовал статью о том как Android 12 из самого важного обновления системы и триумфа, превратился в самый проблемный релиз Google.

Жесткие строки подготовки релизов, вкупе с большим количеством изменений, привели к выпуску на рынок превосходного во всех аспектах смартфона Pixel 6, который работал на забагованной ОС.

Даже несмотря на отложенный на месяц выпуск системы, Android страдал от множества проблем, которые еще более усугубились после выпуска декабрьского обновления (которое в итоге было отменено и перенесено на конец января).

С теми же проблемами столкнулись компании Samsung и OpenPlus. Последняя на фоне общей нестабильности Android 12 предприняла еще и попытку слияния кодовых баз OxygenOS и ColorOS, что само по себе незаурядная процедура.

Вывод здесь достаточно очевидный: Android слишком сложен, а сроки релизов слишком коротки.


How Telegram Messenger circumvents Google Translate's API - занятная заметка о том, как реализована функция перевода в новой версии Telegram для Android.

Благодаря выложенным исходникам клиента выяснилось, что Telegram использует API Google Translate. Однако это совсем не тот платный API, который Google предлагает своим клиентам, а скрытый API, который внутренне использует Google Chrome для встроенной функции перевода.

Но это еще не все. В коде Telegram путь к эндпоинту API представлен не в чистом виде, а разбитом на случайные куски, примерно также, как это делает зловредное ПО для скрытия строк:

uri = "https://translate.goo";
uri += "gleapis.com/transl";
uri += "ate_a";
uri += "/singl";
uri += "e?client=gtx&sl=" + Uri.encode(fromLanguage) + "&tl=" + Uri.encode(toLanguage) + "&dt=t" + "&ie=UTF-8&oe=UTF-8&otf=1&ssel=0&tsel=0&kc=7&dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&q=";
uri += Uri.encode(text.toString());

Чтобы запрос выглядел еще более легитимным, Telegram подделывает User-agent, выдавая себя за разные версии Chrome.

В итоге Telegram удается использовать сервис Google-перевода, который иначе бы стоил немалых денег, и при этом не быть забаненым на стороне севера. Проблема только в том, что этот трюк был раскрыт уже на следующий день после выхода новой версии приложения.


Fun with GSIs: A retrospective on Project Treble’s impact on custom ROMs - статья Mishaal Rahman, бывшего журналиста XDA Developers, о текущей ситуации с Project Treble в Android.

Напомним, что Project Treble - это инициатива по разделению Android на две независимые части, одна из которых представляет собой операционную систему, а другая - слой HAL, нужный для предоставления операционной системе доступа к оборудованию устройства. Идея такого разделения в том, чтобы позволить производителям смартфонов быстрее обновлять свои устройства на новую версию Android не заставляя их ждать, пока поставщики компонентов смартфона обновят драйвера. Наработки Project Treble впервые появились в Android 8.0, а позже Google выпустила универсальную сборку "чистого" Android (GSI), которую в теории можно прошить на любой Treble-совместимый смартфон, и она "просто заработает".

Статья проясняет несколько важных деталей касательно Project Treble:

1. Вопреки расхожему мнению, Treble никогда не задумывалась как прослойка, позволяющая "просто взять и запустить" новую версию Android на уже существующих устройствах. Да, в большинстве случаев образ GSI с новой версией Android действительно можно прошить на смартфон и он будет работать. Но Google вовсе не гарантирует его полную работоспособность, гарантируется лишь корректная загрузка. Истинное назначение Project Treble в том, чтобы позволить разработчикам прошивок начать работу над новой версией Android как можно раньше, не дожидаясь, пока разработчики железных компонентов смартфона, выпустят драйвера.

2. Позиционирование Treble как инструмента для разработчиков, не остановила энтузиастов от попыток его использования для портирования прошивок. Так появилось коммунити разработчиков Treble-прошивок на XDA и знаменитый образ GSI от phhusson, содержащий массу улучшений, повышающих совместимость образа с различными устройствами.

3. Благодаря Treble и усилиям phhuson, появилось огромное количество универсальных прошивок, которые можно установить на практически любой Treble-совместимый смартфон (читай: любой, выпущенный с Android 9 и выше на борту). Однако стоит иметь ввиду, что есть высокая вероятность, что какой-то не совсем стандартный компонент устройства не заработает. В основном речь идет о дополнительных камерах или экзотических датчиках.


Cancellation in Kotlin Coroutines - большая статья о том, как в Kotlin устроено завершение корутин.

Итак, начнем с того, что интерфейс Job, представляющий корутину, имеет метод cancel(), предназначенный для завершения корутины. Его вызов приводит к следующему:

* Корутина завершается на первой точке остановки, то есть когда происходит вызов какой-то стандартной suspend-функции из стандартной библиотеки корутин (в случае с примером ниже эта точка - метод delay());
* Если Job имеет потомков, то они тоже будут завершены;
* Когда Job будет завершена она больше не может быть использована для запуска новых корутин (состояние Cancelled).

fun main() = runBlocking {
val job = launch {
repeat(1_000) { i ->
delay(200)
println("Printing $i")
}
}

delay(1100)
job.cancel()
job.join()
println("Cancelled successfully")
}
// Printing 0
// Printing 1
// Printing 2
// Printing 3
// Printing 4
// Cancelled successfully

Как и в этом примере зачастую после job.cancel() следует использовать job.join() чтобы дождаться фактического завершения. Это настолько частая потребность, что библиотека поддержки корутин включает в себя функцию-расширение cancelAndJoin().

Когда Job получает сигнал завершения, она меняет свое состояние на Cancelling. Затем, при переходе в следующую точку остановки, она выбрасывает исключение CancellationException. Это исключение можно перехватить, но, чтобы избежать трудноуловимых багов, его лучше сразу выбросить снова:

suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
try {
repeat(1_000) { i ->
delay(200)
println("Printing $i")
}
} catch (e: CancellationException) {
println(e)
throw e
}
}
delay(1100)
job.cancelAndJoin()
println("Cancelled successfully")
delay(1000)
}
// Printing 0
// Printing 1
// Printing 2
// Printing 3
// Printing 4
// JobCancellationException...
// Cancelled successfully

Благодаря тому, что завершение корутины приводит к выбросу исключения, мы можем использовать блок finally чтобы корректно закрыть все используемые корутиной ресурсы (базы данных, файлы, сетевые соединения). Однако мы уже не сможем в этом блоке запустить другие корутины или вызывать suspend-функции. Эти действия будут запрещены после перехода корутины в состояние Cancelling. Единственный выход их этой ситуации - это использовать блок withContext(NonCancellable), который позволит вызвать suspend-функции, но не будет реагировать на сигнал завершения:

launch(job) {
try {
delay(200)
println("Coroutine finished")
} finally {
println("Finally")
withContext(NonCancellable) {
delay(1000L)
println("Cleanup done")
}
}
}

Еще один способ выполнить код после завершения корутины - это метод invokeOnCompletion(), который будет вызван независимо от того как была завершена корутина принудительно или она просто отработала свое время.

suspend fun main(): Unit = coroutineScope {
val job = launch {
delay(1000)
}
job.invokeOnCompletion { exception: Throwable? ->
println("Finished")
}
delay(400)
job.cancelAndJoin()
}
// Finished

Завершение корутины происходит в точках остановки. Но что делать, если в коде корутины нет точек остановки (нет вызовов suspend-функций)?

Один из вариантов - использовать функцию yield(). Эта suspend-функция приостановит корутину и тут же ее возобновит, но по пути обработает сигнал завершения. Второй вариант: Boolean-поле isActive, достаточно просто проверить его значение и, если оно равно false, закончить работу внутри корутины. Еще один вариант - вызывать функцию ensureActive(). Она выбросит исключение CancellationException если корутина уже получила сигнал завершения.


Variables point to objects - очевидная для многих и в то же время не очевидная для некоторых даже прожженных программистов заметка о том, как происходит работа с переменными и объектами.

Возьмем следующий пример:

var a = 10
var b = a
a = 20
println(b)

Очевидно, что этот код напечатает 10. Мы присваиваем переменной b ссылку на объект a, который имеет значение 10 (в Kotlin примитивные типы тоже представлены объектами), а затем присваиваем переменной a ссылку на объект со значением 20. Соответственно, a теперь ссылает на объект 20, а b продолжает ссылаться на объект 10. Все логично.

Картина становится немного сложнее когда мы начинаем работать с изменяемыми объектами:

val user1 = object {
var name: String = "Rafał"
}
val user2 = user1
user1.name = "Bartek"
println(user2.name)

Этот код напечатает Bartek, потому что, в отличие от предыдущего примера, переменные user1 и user2 продолжают ссылаться на один и тот же объект. А вот свойства этого объекта меняются, и изменение распространяется на обе переменные.

Все становится совсем запутанным когда мы переходим к изменяемым и неизмененяемым коллекциям. Вот два примера:

Первый:

var list1 = listOf(1,2,3)
var list2 = list1
list1 += 4
println(list2)

Второй:

var list1 = mutableListOf(1,2,3)
var list2 = list1
list1 += 4
println(list2)

Первый печатает [1, 2, 3], а второй - [1, 2, 3, 4]. Так происходит потому, что в первом случае мы работаем с неизменяемой коллекций, в итоге код list1 += 4 превращается list1 = list1 + 4, что буквально означает "создать новый объект-список, содержимым которого будут все элементы первого списка плюс элемент 4".

Во втором примере тот же самый код превратится уже в другую конструкцию: list1.plusAssign(4), которая означает list1.add(4), то есть "добавить элемент 4 к _уже существующему_ объекту-коллекции.

И, наконец, самый самый интересный пример. Kotlin позволяет делегировать поля отображению. Например:

class Population(var cities: Map) {
val sanFrancisco by cities
val tallinn by cities
val kotlin by cities
}

val population = Population(mapOf(
"sanFrancisco" to 864_816,
"tallinn" to 413_782,
"kotlin" to 43_005)
)

println(population.sanFrancisco) // 864816
println(population.tallinn) // 413782
println(population.kotlin) // 43005

Функция весьма удобная. Но что если после создания объекта мы присвоим полю cities значение emptyMap():

population.cities = emptyMap()

Какие значения в этом случае будут у полей sanFrancisco, tallinn and kotlin? Ответ: те же самые. Просто потому, что объект population продолжает ссылаться на предыдущую версию map со всеми ее элементами.


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.


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;


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;


@kvkvkapilvij/kotlin-interview-cheat-sheet-c62e7850ba73' rel='nofollow'>Kotlin Interview Cheat Sheet и @mohitdholakia4/interview-questions-for-android-developer-3a18e41ebcda' rel='nofollow'>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.


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() активности.


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 для разработки консольных приложений.

20 last posts shown.

314

subscribers
Channel statistics