Русский
Русский
English
Статистика
Реклама

Мама, я хакер пробуем вскрыть приложение наFlutter

RFC 1983

Hacker - человек, наслаждающийся доскональным пониманием внутреннего устройства систем,компьютеров и компьютерных сетей, в частности.

Привет! Предлагаю заглянуть под капот фреймворка и разобрать процесс компиляции и, заодно, выявить потенциальные проблемы при реверс-инжиниринге приложенияFlutterна платформеAndroid.

Мама! Ну сколько раз тебе говорить,яненахер,я-ХАКЕР!

В достаточно далёком, по меркамIT-технологий, 1989 году, я стал счастливым обладателемсвоего первого персонального компьютераАссистент, с неплохими характеристиками для домашнего ПК на тот момент Intelсовместимым процессором 8086 на 5МГц и памятью 128Кбайт. А еще был железный (в прямом смысле слова) матричный принтер фирмыRobotron, который, зараза, неправильно печатал одну из букв в русской раскладке. В общем, волею судьбы, первым моим опытом реверс-инжиниринга стал разбор кода работыBIOSпри выводе на печать и создание на ассемблере небольшого перехватчика прерывания, который подменял букву на нужную. В результате, в памяти хорошо запомнился восторг того юного начинающего хакера-пионера, и я безразмерно благодарен своим родителям, которые, отказавшись от своих хотелок тогда, приобрели мне не самый дешёвый компьютер по тем временам, поддержав мой интерес к вычислительной технике.

С тех пор, периодически занимался этой темой, правда, в основном, не профессионально. Это были или лично интересные для меня задачи, например исследования вирусов, защит ПО от взлома, или нечастые смежные задачи по работе. Занимался реверс-инжинирингом приложений подDOS/RT-11/Windows/Linux, в том числе приложений для .NETFramework, серверных приложений наJava, прошивок встраиваемых систем с различными процессорными архитектурами. В настоящее время, реверсом практически не занимаюсь, но, посколькуIT-фортуна свела меня сFlutter, решил вспомнить молодость и попробовать на зубок мобильные приложения, созданные с помощью этого замечательного фреймворка.

В общем, достаточно лирики. Итак, кому интересна магия превращения исходного кода наDartв приложениеAndroidи насколько сложно взломать его, с точки зрения хакера-любителя, добро пожаловать под кат.

Объект исследования

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

Сейчас мы отступим от кодекса честного хакера и перейдем на темную сторону взломщиковcrackers (по RFC 1983). Для примера используем хорошо известный приём взлома, это замена условного оператора сравнения на противоположный по смыслу, т. е. если изначально в условии стоит не равно подменяем его на равно. Во многих системах это осуществляется просто заменой одного байта машинного кода или промежуточного кода (например, дляIL.NETFramework) в скомпилированном исполняемом файле, который запускает пользователь. При этом одна из самых главных задач найти это место в скомпилированном файле.

Поскольку у меня был некоторый опыт именно с исследованием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), это представление может сохраняться в специализированном формате какKernelBinary(как правило это файлы с расширениемdill).

Именно это представление передается в мобильное устройство приJITкомпиляции в режимеDebugи используется как промежуточный шаг при переводе в машинные коды в режимеAOT. Далее это представление преобразуется вграф управления и переходов (CFG), содержащий инструкции промежуточного кодаIL, эти инструкции затем переводятся непосредственно в машинный код, который понимает процессор мобильного устройства.

По поводу различия в скорости работыJITиAOT:JITхотя и использует преобразование в машинный код непосредственно на устройстве пользователя и затрачивает на это некоторое время, результирующий машинный код может быть быстрее в некоторых случаях, так как может на лету анализировать типы объектов по месту использования (да, да тот самый полиморфизм) и генерировать оптимальный код исходя из текущего контекста исполнения, но поскольку вiOSподходJITне разрешен, дляreleaseрежима сейчас используется толькоAOT.

Собираем Debug

Для начала попробуем собрать наш проект в режимеdebug. Выполняем в терминале командуflutterbuildapk--debug

так как собраный файл APK это, по сути,zipархив, распаковываем и смотрим что там внутри

Содержимое app-debug.apk
Содержимое app-debug.apkСодержимое 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, вот здесь и храниться наш код в форматеkernelbinary, а также весь код фреймворка написанный наDart. Причем этоblobфайл, т. е. там может храниться различная мета информация о коде кроме самогоkernelbinary, в случаеdebugсборки там также храниться весь исходный код нашего приложения включая комментарии.На самом деле модификацияdebugсборки не представляет интерес, так как там практически храниться все в открытом виде. Поэтому, для целей нашей статьи, перейдем к исследованию сборки в режимеrelease

Собираем Release

Выполняем командуflutterbuildapk

Для получения полного лога о процессе сборки используется флаг --verbose, он покажет гораздо больше информации, что так же может быть полезным при диагностике проблем.В логе можно увидеть, что процесс компиляции нашего проекта проходит этап преобразования вASTпредставление, при этом генерируется файлapp.dill, затем применяется инструментgen_snapshot, он создает изapp.dillскомпилированный в машинный код файл библиотекиlibapp.so, который уже помещается вAPKфайл.

Содержимое app-release.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и граф переходов, затем вkernelbinaryи, наконец, вDart. Но, данная работа выходит далеко за рамки объема времени, который я предполагал выделить на эту статью.

Место модификации

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

Сравнив 2 файла, было видно, что они отличаются только в одном байте, что не может радовать хакера-любителя, так как это позволяет изменить просто один байт, не затрагивая размер и композицию файла. Соответственно, чтобы изменить поведение приложения на противоположное надо заменить только один байт.

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

Само преобразованиеILкода в машинный код находится вDartsdk, в файле отвечающим за соответствующую архитектуру. Например дляassemblerARM64 она находитсяздесь. Если проследить цепочку вызовов можно выйти и на компиляторflowgraphиIL. Здесь мы видим, что большинство кода компиляции используется как дляJIT, так и дляAOTрежимов.

Для тех, кому интересна компиляции и процессы преобразования Dart SDK предоставляет такую возможность.

Пример консольного приложения main.dart
const _secret = 'secret';void main(List<String> args) {  if (args.isNotEmpty) {    String value = args[0];    if (value == _secret) {      print("You are in!!");    } else {      print("Please, enter again");    }  }}

Команда отображающая при запуске информацию оIL,CFGи коде ассемблера
dart--print-flow-graph--print-flow-graph-filter=main--disassemblemain.dart

Пример участка скомпилированного кода, выполняющий операцию сравнения
;; t0 <- LoadLocal(value @-1)0x10ac21e87    ff75e0                 push [rbp-0x20]        ;; t1 <- Constant(#secret)0x10ac21e8a    4d8b5f47               movq r11,[pp+0x47]0x10ac21e8e    4153                   push r11        ;; t0 <- InstanceCall:24( ==<0>, t0, t1)0x10ac21e90    488b542408             movq rdx,[rsp+0x8]0x10ac21e95    498b5f4f               movq rbx,[pp+0x4f]0x10ac21e99    4d8b6757               movq r12,[pp+0x57]0x10ac21e9d    41ff54240f             call [r12+0xf]0x10ac21ea2    59                     pop rcx0x10ac21ea3    59                     pop rcx0x10ac21ea4    50                     push rax        ;; t1 <- LoadLocal(:t0 @-2)        ;; AssertBoolean:26(t1)0x10ac21ea5    488b45d8               movq rax,[rbp-0x28]0x10ac21ea9    493b86d0000000         cmpq rax,[thr+0xd0]   null0x10ac21eb0    0f8509000000           jnz 0x000000010ac21ebf0x10ac21eb6    4d8b672f               movq r12,[pp+0x2f]0x10ac21eba    41ff542407             call [r12+0x7]        ;; t1 <- Constant(#true)0x10ac21ebf    41ffb6d8000000         push [thr+0xd8]        ;; Branch if StrictCompare:28(===, t0, t1) goto (4, 5)0x10ac21ec6    415b                   pop r110x10ac21ec8    58                     pop rax0x10ac21ec9    493b86d8000000         cmpq rax,[thr+0xd8]   true0x10ac21ed0    0f8522000000           jnz 0x000000010ac21ef8

Акт последний - модификация APK

Закончим наш эксперимент заменой соответствующего байта условного оператора в скомпилированномapkфайле. Это операция состоит из нескольких этапов:

  • Разборкаapk файла

  • Модификацияlibapp.soс заменой байта

  • Сборкамодифицированного apk

  • Подпись 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

Устанавливаем на устройство adbinstallapp-release.apk

Запускаем наше приложение, и... теперь подходит любой пароль! Мы изменили поведение приложения без использования исходных файлов.

Резюме

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

Что касается именноFlutter, хранение чувствительной информации на устройстве желательно избегать, но в случае необходимости можно использовать пакетflutter_secure_storageили использоватьобфускациюпри сборке, которая может установить дополнительную преграду при изучении и осмыслении скомпилированного кода, используя имена классов и функций.

Источники информации используемые при подготовке статьи

Выражаю благодарность всем разработчикам Dart SDK за понятный и говорящий за себя код, а также отдельно Вячеславу Егорову за описание и принцип работы виртуальной машины
https://github.com/dart-lang/sdk/blob/master/runtime/docs/index.md

Источник: habr.com
К списку статей
Опубликовано: 11.01.2021 00:10:33
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании лига ставок

Dart

Flutter

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru