Непокорный ReactNative - (не)Уникальный опыт

Непокорный ReactNative


По моему скромному мнению - ReactNative уже прошел свой пик популярности в разработке мобильных приложений и дальше будет постепенно покидать эту область. Зачем он нужен, если есть божественный Flutter? 🤪 Но такие приложения все еще встречаются и порой доставляют проблемы исследователям безопасности. Впрочем, когда нас это останавливало? 😎

/img/disobedient-react-native/rnkdpv.jpg

Простой случай

  • Android: assets/index.android.bundle
  • iOS: Payload/appname.app/main.jsbundle

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

__d(function (g, r, i, a, m, e, d) {
    Object.defineProperty(e, "__esModule", {
        value: !0
    }), e.default = void 0;
    var t = r(d[0])(r(d[1])),
        s = (Boolean(g.origin), {
            PROD_URL: "https://example.com/",
            logger: !1,
            statusDescription: {
                200: "OK",
                401: "Unauthorized"
            },
            headers: {
                "content-type": "application/json",
            },
            successStatus: [200],
            request: {
                login: {
                    url: "login/",
                    method: "post"
                },
    ...

Как бывает на самом деле

В реальности, вы открываете index.android.bundle и…

/img/disobedient-react-native/hermes.png

Это что еще за нахрен...

Добро пожаловать в реальный мир, Нео. Матрица поимела тебя и подкинула проект с Hermes

Hermes is an open-source JavaScript engine optimized for React Native. For many apps, using Hermes will result in improved start-up time, decreased memory usage, and smaller app size when compared to JavaScriptCore. Hermes is used by default by React Native and no additional configuration is required to enable it.

Но отчаиваться пока рано. Да и вообще не стоит наверное. Для начала можно попробовать вот этот совет:

/img/disobedient-react-native/tips.jpg

Это срабатывает чаще, чем может показаться. Разгадка проста - поддержку формата Hermes для iOS, завезли буквально в 2021м году, т.е. совсем недавно (относительно времени публикации статьи).

Если получилось, то исследование приложения сразу становится на 30% приятнее (по данным независимых экспертов). А вот если нет…

Добро пожаловать в Грецию

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

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

Для дизассемблирования бинарного index.android.bundle будем использовать hbctool. Все подробности есть в репозитории, но краткий флоу работы с программой я покажу.

pip install hbctool # установить
hbctool disasm index.android.bundle out # дизассемблировать и сохранить результат out

На выходе получится 3 файла:

  • insturctions.hasm - байткод
  • metadata.json - метаданные байткода
  • string.json - все строки

Ожидая увидеть что-то вразумительное в .hasm файле вы увидите вот это:

LoadConstUndefined  	Reg8:0
Call1               	Reg8:1, Reg8:2, Reg8:0
GetGlobalObject     	Reg8:0
PutById             	Reg8:0, Reg8:1, UInt8:1, UInt16:24355
; Oper[3]: String(24355) '__BUNDLE_START_TIME__'
LoadConstFalse      	Reg8:1
PutById             	Reg8:0, Reg8:1, UInt8:2, UInt16:21147
; Oper[3]: String(21147) '__DEV__'
GetById             	Reg8:1, Reg8:5, UInt8:4, UInt16:19169
; Oper[3]: String(19169) 'process'
JmpTrue             	Addr8:5, Reg8:1
NewObject           	Reg8:1
PutById             	Reg8:0, Reg8:1, UInt8:3, UInt16:19169
; Oper[3]: String(19169) 'process'
LoadConstString     	Reg8:1, UInt16:15211
; Oper[1]: String(15211) ''
PutById             	Reg8:0, Reg8:1, UInt8:4, UInt16:24366
; Oper[3]: String(24366) '__METRO_GLOBAL_PREFIX__'

И это будет файл всего лишь на 1918555 строк. Справедливости ради, скажу, что раскурив официальный мануал и помедитировав n-дцать часов над этим всем, можно начать понимать что происходит и даже построить некоторые связи, найти точку входа и худо-бедно разобрать логику. Но без специального инструмента, который сделает все рутинные операции за вас - это титанический труд. Это не jadx, не IDA Pro и даже не архивы спецслужб… Не стоит вскрывать эту тему 😁

И все же польза от этой утилиты есть. Она дает нам строки, в которых можно найти что-то полезное. Адреса отладочных контуров, ключи для API, пароль администратора… Кто знает. Формат файла со строками, тоже не слишком приятный, но жить можно:

[
    {
        "id": 4494,
        "isUTF16": false,
        "value": "twenty_five_years"
    },
    {
        "id": 4495,
        "isUTF16": false,
        "value": "https://developer.mozilla.org/docs/Web/CSS/-ms-scroll-limit-y-min"
    },
    {
        "id": 4496,
        "isUTF16": false,
        "value": "minimal"
    },
    ...
]

Но этого мало и полноценное исследование безопасности на основании одного файла string.json, не построишь. Что-то можно накопать из AndroidManifest.xml и “прилегающих территорий”. Например зарепортить android:allowBackup="true" или бездарно составленный network_security_config.xml, но опять же это все детский сад.

Остается один вариант - тестирование методом черного ящика. Мысль очевидная, но не всем исследователям безопасности мобильных приложений этот подход привычен. Так-то декомпилировал код и сиди корявую джаву кури, а в случае с черным ящиком - нужно думать. Много. А еще понимать как работают те или иные механизмы внутри приложения.

Вносите черный ящик

Начать исследование методом черного ящика лучше всего с анализа трафика. Это даст вам много информации о внутреннем устройстве моделей, методах их обработки, также может дать информацию о параметрах deeplink-ов и применяемых методах шифрования. Поэтому, трафик - наше все. Но не только трафик, а также его отсутствие дает полезную информацию. Например, если при установке пин-кода не улетает никаких запросов, значит этот механизм работает локально, а это вполне себе уязвимость, которую можно попробовать проэксплуатировать. Следствием из предыдущей уязвимости может быть отсутствие какого-либо шифрования токена для доступа к API. Если приложение делает запросы к серверу еще до перехода на главный экран, то есть вероятность, что токен хранится как попало (а точнее в базе данных RKStorage в директории приложения) и это тоже можно и нужно репортить. Определенную пользу может принести подмена ответов от сервера чтобы посмотреть как приложение реагирует на те или иные данные, что тоже может вскрыть какие-то аномалии приводящие к уязвимостям. Вариантов вагон, главное не унывать.

Но не все любят черный ящик, и поэтому, если вас прямо тошнит от самой мысли о таком типе тестирования то вон из профессии могу предложить один вариант, как это ящик немного обелить. Следите за руками. Если есть приложение и есть API, то велика вероятность, что есть сайт. А сайт это JS, а JS это жопа возможность посмотреть на логику работы разных частей системы. Может статься так, что приложение писали те же самые фронтендеры, что красили кнопки на сайте. Но даже если это не так, то накопать полезного посмотрев код фронтенда все равно можно. И вот blackbox уже начинает выглядеть не таким страшным. Приведу пример. В API могут быть методы, до которых вы никогда не дойдете тыкая интерфейс мобильного приложения. Или никогда не увидите какие-то параметры методов, потому что они или не используются в мобильном приложении или используются, но только для учеток c определенной ролью. Которой у вас нет. Поэтому, найти полную реализацию API со всеми параметрами было бы неплохо. Как это сделать? Смотреть в браузере JS сейчас практически бесполезно. Чаще всего там будет обфусцированный бандл размером 50к строк, который не приведет в порядок даже js beautifier. И тут в дело вступает человеческий фактор.

Разработчики не пишут код без ошибок (внезапно!) и ошибки это нужно отправлять в какой-то трекер, потом разбирать, а значит нужен какой-то map-файл, который бы помогал по обфусцированному коду получать нормальный. Вы наверняка знакомы с map-файлами Proguard-а. То же самое есть в мире фронтенда. Что еще есть в мире фронтенда? То, что эти файлы часто лежат рядом с JS бандлами и ждут, пока вы их скачаете и получите оригинальный код. Как ни странно, среди фронтенд-разработчиков есть целая каста людей, которые выступают за обязательное размещение map-файлов рядом с приложением. Спасибо им за это 🤣

Естественно сумрачные гении давно придумали как автоматизировать выкачивание кода и есть несколько инструментов, которым достаточно скормить url, а они заботливо все скачают и положат в указанный каталог. И чаще всего это будет не JavaScript! А самый что ни на есть TypeScript. С оригинальными комментариями разрабочтиков, TODO-шками и прочими прелестями. Рекомендую. Порой такое смешное там пишут…

/img/disobedient-react-native/comment.png

Софт для скачивания исходников с фронта:

По ряду причин, я использую свою реализацию, но раньше пользовался каким-то из форков unwebpack-sourcemap. Впрочем, форк тоже пришлось запатчить, потому что он глючил на некоторых сайтах…

Финальные мысли

Как видите, на любого хитрого грека найдется свой Харон ;) Главное не унывать, пробовать зайти с разных сторон и думать, думать, думать.

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

Удачи, и пусть вам никогда не попадется приложение на ReactNative 😅

Ссылки


Смотрите также

comments powered by Disqus