Публикация приложения
После создания приложения, его тестирования и отладки мы можем приступить к его публикации. Суть публикации заключается в создании файла с расширением .apk, которое будет представлять приложение, и его последующее размещение в Google Play Market или на других внешних сайтах. По умолчанию в процессе отладки и создания приложения файл apk уже создается, и мы можем его найти в папке проекта по пути Название_проекта\app\build\outputs\apk. По умолчанию файл называется app-debug.apk и представляет debug-версию.
Но для полноценно публикации данного файла может оказаться недостаточно. И нам еще дополнительно надо произвести некоторую подготовку проекта к релизу. Для это следует указать в файле манифеста у элемента <manifest> установлены атрибуты android:versionCode и android:versionName . Также в файле манифеста элемент <application> не должен содержать атрибута android:debuggable
Кроме того, на этом этапе можно установить иконку для приложения, которая будет отображаться на рабочем экране гаджета, название приложения (атрибут android:label у элемента), а также можно задать лицензионное соглашение.
В файле манифеста также следует определить название пакета (атрибут package элемента <manifest> ), которое будет использоваться для приложения в дальнейшем. По умолчанию при разработке в Android Studio пакеты приложений начинаются с com.example. Не стоит оставлять данное название, так как название пакета будет служить уникальным идентификатором вашего приложения. Например, ниже в моем случае названием пакета служит «com.maverics.eugene.telephonelist»:
При этом если в файлах кода java название пакета в начале файла также должно соответствовать пакету приложения.
Установка требований
На этапе подготовки к релизу также можно установить требования к API. Например, наше приложение имеет определеную минимальную версию ОС Android, поэтому мы можем установить в файле манифеста соответствующие атрибуты у элемента <uses-sdk>
android:minSdkVersion — минимальная версия Android
android:targetSdkVersion — оптимальная версия API
android:maxSdkVersion — максимальная версия системы
Например, пусть минимальная версия Jelly Beans 4.1.2, а оптимальная KitKat 4.4.4:
Подпись приложения
Когда все уже готово, приложение для Android должно быть подписано сертификатом, благодаря которому можно идентифицировать автора приложения. Когда мы тестируем приложение, устанавливая его через Android Studio на устройство, то оно подписывается автоматически. Но для создания релиз-версии нам надо произвести дополнительно ряд действий.
При создании сертификата следует помнить, что при обновлении приложения система будет сравнивать сертификаты старой и новой версии. И обновление будет происходить, если сертификаты обоих версий совпадут. Но если новая версия будет подписана новым сертификатом, то приложение будет расцениваться как совершенно новое, никак не связанное со старой версией и представляющее совершенно другое приложение. В этом случае чтобы его установить, новая версия должна будет иметь другой название пакета, нежели старая.
Во-первых, в Android Studio выберем в меню пункт Build -> Generate Signed APK . После этого нам откроется окно мастера:

Нажмем на кнопку Create new. . После этого нам откроется окно создания ключа:

Введем в поле Key store path путь к файлу сетификата, который будет создан. Если указанной папки не существует, то ее надо создать или определить существующую папку.
В поле Password/Confirm указываем пароль.
В поле Alias указываем псевдоним. Можно поставить произвольное название.
В поле First and Last Name вписываем имя и фамилию. И далее пишим подразделение, организацию, город, страну и код страны.
В конце нажимаем OK.
После этого автоматически обновится первое окошко:

Далее нажмем на кнопку Next:

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

Мы можем переименовать файл, сохранив его расширение и выложить в Play Market или на любой сайт или сразу загрузить на мобильное устройство. После загрузки на телефон/планшет достоточно нажать на него, и с помощью стандартного установщика пакетов приложение будет установлено. Правда, здесь также надо учитывать, что если мы устанавливаем приложение не из Play Market, то в настройках надо разрешить установку из других источниках — Безопасность->Неизвестные источники (Разрешить установку приложений из других источников)
Difference between app-debug.apk and app-debug-unaligned.apk
![]()
The unaligned apk is just an intermediate apk. First, the unaligned apk is generated. Then, the unaligned apk gets aligned and produces the aligned apk which is the app-debug.apk. You can read more about it over here.
![]()
Short Answer:
app-debug-unaligned.apk = Unaligned Signed APK
app-debug.apk = Aligned Signed APK (RAM optimized using zipalign)
Long Answer
To understand the difference we need to know the following points:
App signing process
- generate a private key (keytool)
- compile to get the unsigned APK -> unaligned unsigned APK
- Sign app in debug/release mode using private key (jarsigner) -> unaligned signed APK
- align the APK (zipalign) -> aligned signed APK
The entire signing process is explained here.
Why do we need the intermediate app-debug-unaligned.apk at all?
Caution: zipalign must only be performed after the .apk file has been signed with your private key. If you perform zipalign before signing, then the signing procedure will undo the alignment.
What is the advantage? zipalign?
The advantage is that aligned APKs are optimized for RAM usage so they will consume less RAM in the devices. From the docs:
zipalign is an archive alignment tool that provides important optimization to Android application (.apk) files. . The benefit is a reduction in the amount of RAM consumed when running the application.
Мама, я хакер: пробуем вскрыть приложение на Flutter
Hacker — человек, наслаждающийся доскональным пониманием внутреннего устройства систем, компьютеров и компьютерных сетей, в частности.
Привет! Предлагаю заглянуть под капот фреймворка и разобрать процесс компиляции и, заодно, выявить потенциальные проблемы при реверс-инжиниринге приложения Flutter на платформе Android.

Мама! Ну сколько раз тебе говорить, я не нахер, я — ХАКЕР!
В достаточно далёком, по меркам IT-технологий, 1989 году, я стал счастливым обладателем своего первого персонального компьютера «Ассистент», с неплохими характеристиками для домашнего ПК на тот момент – Intel совместимым процессором 8086 на 5МГц и памятью 128Кбайт. А еще был железный (в прямом смысле слова) матричный принтер фирмы Robotron, который, зараза, неправильно печатал одну из букв в русской раскладке. В общем, волею судьбы, первым моим опытом реверс-инжиниринга стал разбор кода работы BIOS при выводе на печать и создание на ассемблере небольшого перехватчика прерывания, который подменял букву на нужную. В результате, в памяти хорошо запомнился восторг того юного начинающего хакера-пионера, и я безразмерно благодарен своим родителям, которые, отказавшись от своих «хотелок» тогда, приобрели мне не самый дешёвый компьютер по тем временам, поддержав мой интерес к вычислительной технике.
С тех пор, периодически занимался этой темой, правда, в основном, не профессионально. Это были или лично интересные для меня задачи, например исследования вирусов, защит ПО от взлома, или нечастые смежные задачи по работе. Занимался реверс-инжинирингом приложений под DOS/RT-11/Windows/Linux, в том числе приложений для .NET Framework, серверных приложений на Java, прошивок встраиваемых систем с различными процессорными архитектурами. В настоящее время, реверсом практически не занимаюсь, но, поскольку IT-фортуна свела меня с Flutter, решил вспомнить «молодость» и попробовать «на зубок» мобильные приложения, созданные с помощью этого замечательного фреймворка.
В общем, достаточно лирики. Итак, кому интересна магия превращения исходного кода на Dart в приложение Android и насколько сложно «взломать» его, с точки зрения хакера-любителя, добро пожаловать под кат.

Объект исследования
В качестве объекта для эксперимента, был написан небольшой пример, задача которого банальна — проверить корректность ввода пароля. Сам пароль хранится в исходном коде как константа (конечно, никогда так не делайте). При вводе пароля производится сравнение и выводится приветствие или, в противном случае, соответствующее предупреждение. Исходники этого нано-приложения расположены здесь.
Сейчас мы отступим от кодекса честного хакера и перейдем на темную сторону взломщиков crackers (по RFC 1983). Для примера используем хорошо известный приём «взлома», это замена условного оператора сравнения на противоположный по смыслу, т. е. если изначально в условии стоит «не равно» подменяем его на «равно». Во многих системах это осуществляется просто заменой одного байта машинного кода или промежуточного кода (например, для IL .NET Framework) в скомпилированном исполняемом файле, который запускает пользователь. При этом одна из самых главных задач – найти это место в скомпилированном файле.
Поскольку у меня был некоторый опыт именно с исследованием Android приложений, в качестве примера возьмем эту платформу, конечно, большинство шагов преобразования исходного кода и сам принцип компиляции также применим и к iOS приложениям.
Итак, наша цель — без модификации исходного кода, имея на руках только скомпилированный apk файл приложения Android, понять, как работает код, найти место сравнения и заменить условный оператор, после этой манипуляции подойдет любой пароль, который вводит пользователь (конечно, кроме корректного).

Перепонтовался
Перед началом работы над статьей, изначально ставил цель разобраться именно с реверс-инжинирингом кода, т. е. без исходных кодов, разобраться с принципом работы исследуемого «объекта» и внести соответствующие коррекции для изменения поведения.
Не знаю хорошо это или плохо, в общем — у меня это не получилось. Поэтому, если у кого был вопрос — сложнее ли взломать «обычное» Android приложение или Flutter? — При прочих равных условиях и исходя из своего скромного опыта, Flutter сегодня существенно труднее поддается реверс-инжинирингу. И если для обычных приложений на Android, накоплен достаточный опыт, и мы имеем различные инструменты – например, можем получить Java код из dex/jar файлов приложения, или Smali код, то для Flutter все «грустно», ввиду отсутствия инструментов и практик. Пока фреймворк не так популярен, хотя, в случае роста популярности, возможно появятся и инструменты, и более конкретные кейсы. Поэтому, простите меня профессиональные хакеры, далее речь пойдет только о процессе компиляции и замене машинного кода приложения в заранее известном месте.

Инструменты
Прежде чем мы приступим к исследованию файла Android приложения на Flutter, опишу инструменты, которые использовались в процессе.
apktool – популярный инструмент для реверс-инжиниринга приложений под платформу Android
keytool – утилита управления сертификатами и ключами
jarsigner – утилита подписи Java архивов
adb – стандартный инструмент для работы с подключенными к компьютеру Android устройствами
hopper – дизассемблер для MacOS и Linux
расширение для VSCode позволяющие редактировать файлы в шестнадцатеричном режиме
Все действия производились на MacOS.
Debug&Release
Во Flutter существует несколько режимов сборки приложения. Для целей статьи мы рассмотрим основные – это debug и release режимы.
Debug в этом режиме, кроме того, что там по умолчанию включены различные дополнения, помогающие разработчику при отладке, используется так называемая JIT (Just-In-Time) компиляция, в вольном переводе «прямо-во-время», по сути преобразование в машинные коды, которые понимает процессор мобильного устройства, происходит непосредственно во время работы самого приложения.
Release режим использует уже AOT (Ahead-Of-Time) буквально «заранее», компиляция приложения из исходных кодов в машинные коды мобильного устройства производится на машине разработчика, и на устройство пользователя устанавливается уже скомпилированное приложение, никаких преобразований во время выполнения не производится.
У этих 2-х режимов есть одно общее – это AST представление. При компиляции исходных кодов они предварительно переводятся в промежуточное представление AST (Abstract-Syntax-Tree), это представление может сохраняться в специализированном формате как Kernel Binary (как правило это файлы с расширением dill).
Именно это представление передается в мобильное устройство при JIT компиляции в режиме Debug и используется как промежуточный шаг при переводе в машинные коды в режиме AOT. Далее это представление преобразуется в граф управления и переходов (CFG), содержащий инструкции промежуточного кода IL, эти инструкции затем переводятся непосредственно в машинный код, который понимает процессор мобильного устройства.

По поводу различия в скорости работы JIT и AOT: JIT хотя и использует преобразование в машинный код непосредственно на устройстве пользователя и затрачивает на это некоторое время, результирующий машинный код может быть быстрее в некоторых случаях, так как может на лету анализировать типы объектов по месту использования (да, да – тот самый полиморфизм) и генерировать оптимальный код исходя из текущего контекста исполнения, но поскольку в iOS подход JIT не разрешен, для release режима сейчас используется только AOT.
Собираем Debug
Для начала попробуем собрать наш проект в режиме debug. Выполняем в терминале команду flutter build apk —debug
так как собраный файл APK это, по сути, zip архив, распаковываем и смотрим что там внутри

Содержимое app-debug.apk
Из всех компонентов можно выделить файлы, предназначенные для интеграции Flutter с платформой Android:
classes.dex – файл классов для виртуальной машины Dalvik, в обычных приложениях как раз в этом файле находится скомпилированный Java/Kotlin байт-код приложения. В случае с Flutter там находится код связи Flutter приложения с Android API, например — главный FlutterActivity, работа с объектами поверхностей рисования на виртуальном дисплее, код для связи с платформенными каналами
libflutter.so – менее платформозависимая библиотека движка Flutter написанная в основном на C/C++. В библиотеке находится runtime для работы Flutter, код OpenGL, SKIA и runtime виртуальной машины Dart. Более подробней c составом этих файлов можно ознакомиться в скрипте сборки GN
Что касается именно нашего кода проекта, он расположился в следующих файлах:
isolate_snapshot_data описывает объекты/граф объектов и их создание, это помогает быстро разворачивать, при старте приложения, структуры данных используемые в программе
vm_snapshot_data общие объекты виртуальной машины Dart используемые изолятами
kernel_blob.bin, вот здесь и хранится наш код в формате kernel binary, а также весь код фреймворка написанный на Dart. Причем это blob файл, т. е. там может храниться различная мета информация о коде кроме самого kernel binary, в случае debug сборки там также хранится весь исходный код нашего приложения включая комментарии.
На самом деле модификация debug сборки не представляет интерес, так как там практически хранится все в «открытом» виде. Поэтому, для целей нашей статьи, перейдем к исследованию сборки в режиме release.
Собираем Release
Выполняем команду flutter build apk
Для получения полного лога о процессе сборки используется флаг —verbose , он покажет гораздо больше информации, что так же может быть полезным при диагностике проблем. В логе можно увидеть, что процесс компиляции нашего проекта проходит этап преобразования в AST представление, при этом генерируется файл app.dill, затем применяется инструмент gen_snapshot, он создает из app.dill скомпилированный в машинный код файл библиотеки libapp.so, который уже помещается в APK файл.
Содержимое app-release.apk

Содержимое app-release.apk
В сборке release находятся те же файлы classes.dex и libflutter.so, что и в сборке debug, правда они уже не такие «жирные», так как исключены многие компоненты, используемые для отладки. По сравнению с debug сборкой видим отсутствие файлов isolate_snapshot_data, vm_snapshot_data, но, если заглянуть в libapp.so, увидим, что теперь эти компоненты разместились здесь, а kernel_blob.bin был преобразован в машинный код и размещен в секциях _kDartIsolateSnapshotInstructions и _kDartVmSnapshotInstructions библиотеки.

Ещё можно увидеть, что все ресурсы (assets) программы хранятся в папке flutter_assets, соответственно их также можно свободно извлечь и модифицировать при необходимости.
На этом мои изыскания затормозились, ибо декодирование файла libapp.so, что не удивительно, отображает только ассемблерный код. Инструментов декомпиляции Dart кода хотя бы в промежуточный IL, не говоря уже о AST или Dart я не нашел. С учётом процесса компиляции, конечно, можно представить и обратный процесс – это поиск точек входа, использование описания объектов из секции информации об объектах (_kDartIsolateSnapshotData), преобразование в несколько проходов из ассемблера в IL и граф переходов, затем в kernel binary и, наконец, в Dart. Но, данная работа выходит далеко за рамки объема времени, который я предполагал выделить на эту статью.
Место модификации
Не разобравшись полноценно с реверс-инжинирингом приложения, решил отложить этот вопрос и просто сравнить два скомпилированных файла, чтобы понять в каком месте происходят изменения при изменении операнда условия в исходном коде.
Сравнив 2 файла, было видно, что они отличаются только в одном байте, что не может радовать хакера-любителя, так как это позволяет изменить просто один байт, не затрагивая размер и композицию файла. Соответственно, чтобы изменить поведение приложения на противоположное надо заменить только один байт.

Для исследования был взят libapp.so для архитектуры arm64, которая используется в процессоре на моем телефоне. Если посмотреть на участок кода ассемблера в котором есть изменения, то можно увидеть, что они касаются команды тестирования и перехода. Соответственно заменив tbnz на tbz мы получим необходимый нам результат.

Само преобразование IL кода в машинный код находится в Dart sdk, в файле отвечающим за соответствующую архитектуру. Например для assembler ARM64 она находится здесь. Если проследить цепочку вызовов можно выйти и на компилятор flowgraph и IL. Здесь мы видим, что большинство кода компиляции используется как для JIT, так и для AOT режимов.
Для тех, кому интересна компиляция и процессы преобразования, Dart SDK предоставляет такую возможность.
Пример консольного приложения main.dart
Команда отображающая при запуске информацию о IL, CFG и коде ассемблера
dart —print-flow-graph —print-flow-graph-filter=main —disassemble main.dart
Пример участка скомпилированного кода, выполняющий операцию сравнения
Акт последний — модификация APK
Закончим наш эксперимент заменой соответствующего байта условного оператора в скомпилированном apk файле. Это операция состоит из нескольких этапов:
Разборка apk файла
Модификация libapp.so с заменой байта
Сборка модифицированного apk
Установка на смартфон и проверка
Разбираем исходный файл используя инструмент apktool
apktool d -r -s app-release.apk

Эта команда распакует в каталоге app-release компоненты нашей релизной сборки. Поскольку сейчас интересует именно архитектура arm64 возьмем libapp.so из каталога lib/arm64-v8a и с помощью шестнадцатеричного редактора заменим байт в файле.
Замена байта по смещению 0x1FFCA7 на 0х37

После модификации libapp.so переcобираем apk
apktool b app-release

Поскольку мы изменили содержимое файлов архива APK, при установке этого файла сработает защита проверки подписи. Чтобы обойти это, нам необходимо подписать файл своей подписью. Можно использовать существующий ключ или создать новое хранилище ключей командой
keytool -genkeypair -v -keystore example.keystore -alias example -keyalg RSA -keysize 2048 -validity 10000
Подписываем файл apksigner sign —ks example.keystore —ks-key-alias example app-release.apk
Устанавливаем на устройство adb install app-release.apk
Запускаем наше приложение, и. теперь подходит любой пароль! Мы изменили поведение приложения без использования исходных файлов.
Резюме
Хотя полностью провести реверс-инжиниринг мне не удалось, по результатам работы над статьей можно сделать вывод – исследование без исходных кодов внутренней работы приложения на Flutter выполнить сложнее чем «обычного» приложения под Android. Что касается именно «взлома», конечно декомпиляция является не единственным инструментом для достижения целей взломщиков – у них есть целый арсенал как прокси-серверов, инструменты для SQL-unpinning, инъекций кода, я уже не говорю про социальную инженерию. Поэтому, изначально, можно предполагать, что при необходимости и целесообразности весь код приложения может быть просмотрен и изучен, а также проведена его соответствующая модификация в различных целях. Поэтому важна целостная картина безопасности инфраструктуры и бизнес-контекста, в котором работает мобильное приложение. Но, это совершенно отдельная тема, которая пересекается с этой статьей только в части реверс-инжиниринга.
Что касается именно Flutter, хранение чувствительной информации на устройстве желательно избегать, но в случае необходимости можно использовать пакет flutter_secure_storage или использовать обфускацию при сборке, которая может установить дополнительную преграду при изучении и осмыслении скомпилированного кода, используя имена классов и функций.
How to Generate APK and Signed APK Files in Android Studio

Arooha Arif Last updated Aug 29, 2021
Android Studio allows you to create two kinds of APK files.
First are the debug APK files that are generated solely for testing purposes. They will run on your Android mobile. However, they cannot be uploaded to the Play Store or made available to the public.
Secondly, you can generate signed APK files. Signed APK files come in handy when you’ve tested your application and it is ready to be uploaded on the Play Store and released to the general public.
This tutorial will show you how to create an Android app by generating APK files using Android Studio.
First things first: open up a project file in Android Studio. If you don’t have a project file yet, simply create a New Project.




Creating an APK file
Generating a debug APK file is easy and is a matter of just a few clicks.
First, open up your project or application that you want to import into an APK file. Then, select Build > Build Bundle(s)/APK(s) > Build APK(s) from the toolbar menu.



Android Studio will take a few moments to generate an APK file.
Once the APK build is complete, you’ll receive a notification on the bottom right corner of your screen. From that notification, select Locate and you will be led to the APK file location.


If you miss the notification, you can still locate the APK file in the following path within your project folder: app/build/outputs/apk/debug. The file is named app-debug.apk by default.
Creating a Signed APK File
To generate a signed APK file, open the Build menu from the toolbar and select Generate Signed Bundle/APK.


This opens up a screen where you have to select between creating an Android App Bundle and creating an APK file. Check the APK radio button and proceed to the next window.


In the next window, you will be shown the module (your application) for which the APK file is being generated. You’ll be asked about your Key store path, Key store password, Key alias, and the Key password.


Creating a New Key Store
Assuming that this is the first time you’re creating a Signed APK file, you will have to create a new key store.
Keys are used by the developer to access their application once it has been uploaded to the Play Store. The need for the keys usually arises when you have to update your application. All of the keys are stored in the key store.
Both the key store and the keys are protected by passwords of their own. The passwords should be at least six characters in length. Also, it is a good practice to keep multiple copies of your keys since they are your only gateway to your application. If the key is lost, you will not be able to access your application or update it.
Creating your own app requires you to create a new key store. To do so, select Create new. You will find it underneath the input field where you enter the key store path.


You will then be redirected to a new window.


In the new window, enter the path for your new key store, and then enter a password to protect it.
In the same window, you will also be setting a new key for your application. Enter an identity for your key in the key alias field and then enter a password for it.
You can keep the same password as that of your key store, but it’s a good practice to give a new password to each of your keys. The same goes for the key alias.
The next field defines the validity of your application. This is the duration after which the key to your application will expire, leaving your application inaccessible. The default validity for a key is 25 years.
For each key that you generate, you’re given a certificate that contains all the information about you and your company. You don’t necessarily have to fill in all the details—just choose the ones you think should go on your certificate. A key will still be generated, even without filling in each field of the certificate.
Finishing Up
Once you have filled in the details for the certificate, select OK. You will then be directed back to the Generate Signed Bundle or APK screen.
Here, all of the fields will now be pre-filled for you. Go through all the details to stay on the safe side. Then, select Next.


On the last screen, you will now be able to see the destination of your Signed APK file. Below that, you will see two more options: Debug and Release.
Debugging is used when the application is still in the testing phase. Since your application has passed the testing phase and is ready for deployment, select Release.
There are two more checkboxes towards the bottom of the screen. Select V2 (Full APK Signature) and click Finish.


You will be notified by Android Studio once the APK build is finished. Now, you can click on Locate from the notification to open up the file location.
The Signed APK file is named app-release.apk by default. You will find it in your project folder in the app/release directory.
Summary
These are the steps you need to follow to generate APK and Signed APK files for the purposes of testing your app and making it downloadable via Google Play respectively:
Create an APK File
- Create the project in Android Studio.
- Select Build >Build Bundle(s)/APK(s) >Build APK(s) from the toolbar menu.
You can now transfer your debug APK file to your Android mobile phone and test it for bugs. You can also test it out on your PC using the Android emulator.
Create a Signed APK File
- Create the project in Android Studio.
- Select Build >Signed Bundle/APK from the toolbar menu.
- Configure the settings for your APK file and possibly create a new key store and key.
Create a New Key Store and Key
- Select a key store path.
- Enter a password for your key store.
- Give your key an identity, validity period, and password.
- Enter any personal or organizational details you want included in the key certificate.
You can now release this signed APK file to the public by publishing it on Google Play Store.
Easy but tricky, right? Hopefully, this tutorial helped to clear up any confusion you had about generating APK and signed APK files and bettered your understanding of both file types.