Инструмент на основе FRIDA для отслеживания использования JNI API в приложениях Android.
Нативные библиотеки, содержащиеся в приложениях Android, часто используют API JNI для использования времени выполнения Android. Отслеживание этих вызовов с помощью ручного обратного инженера может быть медленным и болезненным процессом. jnitrace работает как инструмент трассировки динамического анализа, аналогичный Frida-Trace или Strace, но для JNI.
Самый простой способ работать с jnitrace - это установить с помощью PIP:
pip install jnitrace
После установки PIP легко запустить jnitrace :
jnitrace -l libnative-lib.so com.example.myapplication
jnitrace требует как минимум два параметра для запуска трассировки:
-l libnative-lib.so -используется для указания библиотек для отслеживания. Этот аргумент может использоваться несколько раз или * может использоваться для отслеживания всех библиотек. Например, -l libnative-lib.so -l libanother-lib.so или -l * .com.example.myapplication - это пакет Android для отслеживания. Этот пакет уже должен быть установлен на устройстве.Необязательные аргументы перечислены ниже:
-R <host>:<port> - используется для указания сетевого местоположения сервера удаленного FRIDA. Если A: не указано, Localhost: 27042 используется Deault.-m <spawn|attach> - используется для указания механизма прикрепления Frida для использования. Это может быть либо порождено, либо прикрепить. Spawn - это по умолчанию и рекомендуемый вариант.-b <fuzzy|accurate|none> - используется для управления выводом Backtrace. По умолчанию jnitrace будет запускать Backtracer в accurate режиме. Эта опция может быть изменена на fuzzy режим или используется для остановки Backtrace, используя опцию none . Смотрите документы Фриды для объяснения различий.-i <regex> - используется для указания имен методов, которые следует отслеживать. Это может быть полезно для уменьшения шума в особенно крупных приложениях JNI. Вариант может быть предоставлен несколько раз. Например, -i Get -i RegisterNatives будут включать только методы JNI, которые содержат GET или регистрации на их имя.-e <regex> - используется для указания имен методов, которые следует игнорировать в трассировке. Это может быть полезно для уменьшения шума в особенно крупных приложениях JNI. Вариант может быть предоставлен несколько раз. Например, -e ^Find -e GetEnv исключит из результатов все имена методов JNI, которые начинают находить или содержать getenv.-I <string> - используется для указания экспорта из библиотеки, которую следует отслеживать. Это полезно для библиотек, где вы хотите проследить небольшое количество методов. Функции, которые, как считает, что экспортируется, представляют собой любые функции, которые непосредственно выводятся со стороны Java, как таковые, которые включают методы, связанные с использованием регистративных средств. Вариант может быть предоставлен несколько раз. Например, -I stringFromJNI -I nativeMethod([B)V может быть использован для включения экспорта из библиотеки под названием Java_com_nativetest_MainActivity_stringFromJNI и метод, связанный с использованием имен регистров с подписью nativeMethod([B)V .-E <string> используется для указания экспорта из библиотеки, которую не следует отслеживать. Это полезно для библиотек, где у вас есть группа занятых местных звонков, которые вы хотите игнорировать. Функции, которые, как считает, что экспортируется, представляют собой любые функции, которые непосредственно выводятся со стороны Java, как таковые, которые включают методы, связанные с использованием регистративных средств. Вариант может быть предоставлен несколько раз. Например, -E JNI_OnLoad -E nativeMethod исключит из трассировки функции JNI_OnLoad и любые методы с именем nativeMethod .-o path/output.json - используется для указания выходного пути, в котором jnitrace будет хранить все отслеживаемые данные. Информация хранится в формате JSON, чтобы позволить более позднюю постобработку данных трассировки.-p path/to/script.js - предоставляемый путь используется для загрузки сценария FRIDA в целевой процесс до загрузки сценария jnitrace . Это может быть использовано для победы над анти-фридами или антиразмерным кодом до начала jnitrace .-a path/to/script.js - предоставляемый путь используется для загрузки сценария FRIDA в целевой процесс после загрузки jnitrace .--hide-data -используется для уменьшения количества выхода, отображаемого в консоли. Эта опция будет скрывать дополнительные данные, которые отображаются в виде шестигранников или в виде резервных ссылок.--ignore-env -Использование этой опции скрывает все вызовы, которые приложение делает с помощью jnienv struct.--ignore-vm -Использование этой опции скрывает все вызовы, которые приложение делает с помощью javavm struct.--aux <name=(string|bool|int)value> -используется для передачи пользовательских параметров при появлении приложения. Например --aux='uid=(int)10' вызовет приложение для пользователя 10 вместо пользователя по умолчанию 0.Примечание
Помните, что Фрида-Сервер должен работать перед запуском jnitrace . Если были соблюдены инструкции по умолчанию для установки FRIDA, следующая команда запустит сервер, готовую к jnitrace :
adb shell /data/local/tmp/frida-server
Двигатель, который питает Jnitrace, доступен в качестве отдельного проекта. Этот проект позволяет импортировать Jnitrace для отслеживания отдельных вызовов JNI API JNI, методом, знакомого использования Frida Interceptor для подключения к функциям и адресам.
import { JNIInterceptor } from "jnitrace-engine" ;
JNIInterceptor . attach ( "FindClass" , {
onEnter ( args ) {
console . log ( "FindClass method called" ) ;
this . className = Memory . readCString ( args [ 1 ] ) ;
} ,
onLeave ( retval ) {
console . log ( "tLoading Class:" , this . className ) ;
console . log ( "tClass ID:" , retval . get ( ) ) ;
}
} ) ;Дополнительная информация: https://github.com/chame1eon/jnitrace-engine
Создание jnitrace от источника требует, чтобы node был сначала установлен. После установки node необходимо выполнить следующие команды:
npm installnpm run watch npm run watch будет запускать frida-compile в фоновом режиме, компилирующая источник в выходной файл, build/jnitrace.js . jnitrace.py загружает из build/jnitrace.js по умолчанию, поэтому для запуска обновлений не требуется никаких других изменений.
Как и Frida-Trace, вывод окрашен в зависимости от потока вызова API.
Непосредственно под идентификатором потока на дисплее находится имя метода JNI API. Имена методов точно совпадают с теми, которые можно увидеть в заголовке jni.h
Последующие строки содержат список аргументов, обозначенных |- . После |- символов- тип аргумента, за которым следует значение аргумента. Для JMethods, Jfields и Jclasses тип Java будет отображаться в кудрявых скобках. Это зависит от jnitrace , который видел исходный метод, поле или поиск класса. Для любых методов, проходящих буферы, jnitrace извлекает буферы из аргументов и отобразит его как шестигранник ниже значения аргумента.
Возвратные значения отображаются в нижней части списка как |= и не будут присутствовать для пустого методов.
Если Backtrace включена, ниже вызов метода отобразится Frida Backtrace. Имейте в виду, что, согласно документам Frida, нечеткая обратная транспорта не всегда является точной, а точная обратная трансляция может дать ограниченные результаты.
Цель этого проекта состояла в том, чтобы создать инструмент, который мог бы эффективно отслеживать вызовы JNI API для большинства приложений Android.
К сожалению, самый простой подход к прикреплению ко всем указателям функций в структуре Jnienv перегружает приложение. Это вызывает сбой на основе огромного количества функциональных вызовов, выполненных другими не связанными библиотеками, также используя те же функции в libart.so .
Чтобы справиться с этим барьером производительности, jnitrace создает тень Jnienv, который он может предоставить библиотекам, которые он хочет отслеживать. Этот jnienv содержит серию функциональных батутов, которые отскакивают вызовы JNI API через некоторые пользовательские Frida NativeCallbacks для отслеживания ввода и вывода этих функций.
Общий API Frida отлично справляется с предоставлением платформы для создания этих функциональных батутов с минимальными усилиями. Однако этот простой подход не работает для всех API Jnienv. Ключевой проблемой с отслеживанием всех методов является использование вариационных аргументов в API. Невозможно создать NativeCallback для этих функций заранее, так как он заранее не известен все различные комбинации методов Java, которые будут вызваны.
Решение состоит в том, чтобы контролировать процесс для вызовов для GetMethodID или GetStaticMethodID , используемого для поиска идентификаторов метода из времени выполнения. Как только jnitrace видит поиск jmethodID , он имеет известное отображение ID с подписью метода. Позже, когда проводится вызов метода JNI Java, для извлечения идентификатора метода в вызове используется начальный NativeCallback. Эта подпись метода затем анализируется для извлечения аргументов метода. После того, как jnitrace извлек аргументы в методе, он может динамически создать NativeCallback для этого метода. Этот новый NativeCallback возвращается, и немного архитектуры, конкретных ShellCode, посвящена настройке стека и регистрам, чтобы позволить этому вызову успешно работать. Эти NativeCallbacks для конкретных методов кэшируются, чтобы позволить обратному обращению работать более эффективно, если метод, если вы называете несколько раз.
Другое место, где простой NativeCallback недостаточно для извлечения аргументов из вызова метода, для вызовов с использованием указателя VA_ARGS в качестве окончательного аргумента. В этом случае jnitrace использует какой -то код для извлечения аргументов из предоставленного указателя. Опять же, это специфично.
Все данные, отслеживаемые в этих функциональных вызовах, отправляются в приложение консоли Python, которое форматирует и отображает его пользователю.
Большая часть тестирования этого инструмента была проведена на эмуляторе Android X86_64, использующего зефир. Любые проблемы, испытывающие работу на другом устройстве, пожалуйста, подайте проблему, но, если возможно, рекомендуется попробовать запустить аналогичный эмулятор.
Для любых проблем с опытом работы jnitrace , пожалуйста, создайте проблему на GitHub. Пожалуйста, включите следующую информацию в представленном выпуске: