Frida, это динамический фреймворк для взаимодействия с бинарными приложениями. Сначала инструмент просто показался мне интересным, чуть позже я понял насколько это веселая вещь. Помните режим бога в играх? Frida позволяет делать тоже самое, только уже с нативными приложениями. В этом посте я расскажу о использовании Frida для игры с Android приложениями. И раз уж вы здесь, мы дополнительно решим несложный Android CrackMe во второй части.
Что такое динамическая бинарная инструментация
Динамическая бинарная инструментация (Dynamic binary instrumentation) позволяет внедрять сторонний код в уже запущенное приложение, для того чтобы оно делало то, что не делало раньше. Это не эксплуатация инъекции кода через ранее найденные уязвимости. Это не отладка приложения, ведь на самом деле вы не подключаетесь отладчиком к приложению, но это позволяет делать в общем-то похожие вещи. Какие возможности открывает динамическая бинарная инструментация:
Frida "позволяет внедрять JavaScript сниппеты или вашу собственную библиотеку в нативное приложение на Windows, MacOS, Linux, iOS, Android или QNX". Первоначально инструмент работал на движке Google V8 JavaScript Runtime, но начиная с 9 версии Frida использует Ducktape, при этом у вас есть возможность перейти обратно к V8, если вам это понадобиться. Frida определяет несколько режимов работы для взаимодействия с приложениями (включая возможность использования инструментария даже на устройстве без прав супер-пользователя), но сейчас мы рассмотрим самые частые примеры использования, не особо затрагивая методов их работы.
Для начала вам понадобиться:
Если вы планируете повторить решение OWASP Unbreakable Crackme Level 1, который решается во второй части статьи, вам так же следует загрузить:
Frida предоставляет несколько APIs и способов для начала анализа. Вы можете использовать командную строку или инструмент наподобие frida-trace для отслеживания низкоуровневых функций (таких как вызов "open" в libc.so) и максимально быстрого начала. Вы можете использовать биндинги на C, NodeJS или Python для нетривиальных задач. Под капотом Frida работает на JavaScript и в большинстве случаев вам потребуется писать код на этом языке. Поэтому, если вы как и я немного недолюбливаете JavaScript (помимо его возможностей XSS), Frida еще одна причина что бы разобраться с ним получше.
Установите Frida, если еще не сделали этого (посмотрите README для других способов установки):
Запустите ваш эмулятор или присоединитесь к вашему устройству, убедитесь, что устройство доступно в adb:
После, установите frida-server. Распакуйте архив и разместите исполняемый файл на устройстве:
С помощью adb откройте shell на устройстве, перейдите в root и запустите frida:
(Примечание 1: Если frida-server не стартовал убедитесь, что у вас есть root права и файл находиться на устройстве. Я наблюдал несколько странных ошибок с поврежденным файлом при перемещении. Примечание 2: Если вы хотите запустить frida-server в фоновом режиме выполните: ./frida-server &)
В другом терминале, на хостовой системе проверьте, что Frida запустилась и вы можете посмотреть список процессов на Android:
аргумент -U означает использование USB и позволяет Frida проверить подключенные USB-устройства, но это также работает с эмулятором. Вы должны получить примерно следующий список процессов:
Вы можете увидеть ID процесса (PID) и сам запущенный процесс (Name). С помощью Frida вы можете внедрится в любой из этих процессов и начать взаимодействие.
Например, вы можете отслеживать нужные вам вызовы функций в Chrome (запустите Chrome на эмуляторе, если он еще не запущен):
Вы увидите следующее:
Команда frida-trace генерирует небольшой файл JavaScript, который внедряется в процесс и отслеживает вызовы функций. Посмотрим, как устроен скрипт open.js в __handlers__/libc.so/open.js. В нем происходит перехват вызовов функции open из libc.so и вывод ее аргументов. Собственно, сам код для Frida:
Обратите внимание как Frida предоставляет доступ к аргументам функции open во время ее вызова (args[0], args[1] и т.д.) внутри приложения Chrome. Давайте немного изменим скрипт. Было бы неплохо, если бы мы получали действительный путь до открытого файла, вместо адреса памяти где храниться данный путь. К счастью, мы можем напрямую обращаться к памяти с помощью Frida. Посмотрите на Frida API и объект Memory. Мы можем изменить наш скрипт, что бы он выводил содержимое по адресу памяти, где размещена UTF8 строка и мы получали более понятный вывод. После изменения код будет выглядеть примерно так:
(мы просто добавили функцию Memory.readUtf8String) и преобразили вывод:
Frida выводит пути до файлов. Легко, не правда ли?
Еще одна вещь о которой стоит рассказать, вы можете либо запустить приложение перед внедрением Frida, либо передать аргумент -f для Frida, чтобы приложение было запущено автоматически.
Теперь используем интерфейс командной строки Frida, frida-cli:
Этой командой вы запустите приложение Chrome. Однако это не запускает основной процесс приложения. Это нужно для того что бы вы могли внедрить код во Frida, еще перед началом исполнения приложения. К сожалению, в моем случае приложение всегда закрывалось по истечению двух секунд. Это не то, что нам нужно. За эти две секунды вы можете набрать %resume%, как предлагает это cli и приложение сможет запустить свой основной процесс. Или вы можете запускать приложения без его остановки, для этого передайте аргумент --no-pause, в этом случае приложение все еще будет запускается посредством Frida.
В обоих случаях вы получаете shell (который не закрывается автоматически), куда вы можете вводить команды Frida следуя Frida JavaScript API. Нажмите TAB для просмотра доступных команд. Оболочка так же поддерживает автодополнение команд.
Большинство возможностей хорошо документированы. Для Android особенно внимательно посмотрите на секцию Java в JavaScript API (Я буду говорить о "Java API", хотя технически это просто обертка на JavaScript для доступа к Java объектам). Мы сосредоточимся на Java API, поскольку это наиболее удобный способ взаимодействия с Android приложениями. Вместо перехвата функций из libc мы будем работать напрямую с функциями и объектами из Java. (Примечание: Если вы заинтересованы, что вы можете делать с помощью Frida за пределами Java API, перехватывая более низкоуровневые C функции на Android, как мы делали с помощью frida-trace, посмотрите раздел документации посвященный функциям. Я не рассматриваю это здесь.)
Начиная работу с Java API просто получим версию Android на устройстве:
Или список загруженных классов (Предупреждение: Команда может иметь довольно длинный вывод. Дальше я объясню выполняемый код.)
Мы ввели достаточно длинную команду, с некоторой вложенной функцией. Прежде всего обратим внимание, что код обернут в Java.perform(function(){ ... }), что является требованием Java API.
Это тело функции, находящейся внутри обертки Java.perform:
Код достаточно прост: Мы перечисляем все загруженные классы используя Java.enumerateLoadedClasses из Frida API, и выводим каждый элемент в консоль с помощью console.log. Такой тип Callback является шаблоном часто встречающимся во Frida. Это некоторое представление Callback объекта:
onMatch вызывается с одним и более аргументом, когда Frida находит совпадение с вашим запросом, а onComlete, когда Frida заканчивает итерирование возможных совпадений.
Сейчас мы углубимся в магию Frida и перезапишем функцию. Кроме того мы будем загрузим код из внешнего скрипта, вместо того что бы набирать его в консоли, это намного удобнее. Сохраните следующий код, например, как chrome.js:
Этот код перезаписывает функцию onResume в классе android.app.Activity. Происходит вызов Java.use для получения доступа к реализации функции и последующего переопределения функции onResume в классе. В новой реализации функция вызывает оригинальную функцию, через this.onResume(), так что приложение продолжит работать нормально.
Открой ваш эмулятор, откройте Chrome и выполните внедрение кода передав -l.
Теперь, когда происходит вызов onResume, например, перейдите в другое приложение и вернитесь обратно в Chrome, вы увидите:
Приятно, не так ли? Мы фактически перезаписали функцию из приложения. Это дает нам много возможностей для контроля поведения нашего выбранного приложения. Но мы можем больше: мы также можем искать объекты в куче с помощью Java.choose.
Прежде чем мы продолжим, предостережение: Когда наша эмуляция достаточно медленная Frida может останавливается по тайм-ауту. Для предотвращения этого оберните свои скрипты в функцию setImmediate или экспортируйте их как RPC. По умолчанию RPC во Frida не имеют тайм-аутов. setImmediateавтоматически перезапускает ваши скрипты во Frida, после того как вы изменили исходный файл, поэтому это еще и удобно. Также это запускает ваши скрипты в фоновом режиме. Это означает, что сразу получите cli, хотя Frida все еще обрабатывает ваши скрипты. Просто подождите и не покидайте cli, пока Frida не показала вам вывод ваших скриптов. Еще раз отредактируем chrome.js:
Запустим это через команду frida -U -l chrome.js com.android.chrome, получив следующий вывод:
Итак, мы обнаружили 4 экземпляра класса android.view.View в куче. Давайте посмотрим, что мы сможем с ними сделать. Может быть мы можем вызвать их методы. Добавим instance.toString() в наш вывод console.log (поскольку мы использовали setImmediate мы можем изменить скрипт и он перезагрузится во Frida автоматически)
Это вернет:
Frida просто вызывает метод toString экземпляра класса android.view.View. Здорово. Таким образом с помощью Frida мы можем читать память процесса, изменять функции, находить инстансы классов в куче и все это в несколько строчек кода.
Предостережения
Когда вы экспериментируете с Frida вы заметите, что есть некоторые нестабильности. Во-первых внедрение стороннего когда в другой процесс может вызвать сбои, потому приложение будет работать не так, как планировалось. Во-вторых, сама Frida является экспериментальной. Она работает, но часто вы должны попробовать несколько способов для получения желаемого результата. Например, Frida всегда крашится, когда я пытаюсь загрузить скрипт и запустить процесс в одну команду. Вместо этого я сначала запускаю процесс, а затем внедряю скрипт. Поэтому я и показывал вам разные способы избежания тайм-аута. Возможно, вам будет нужно какой из них работает в вашем случае.
Python bindings
Если вы хотите автоматизировать вашу работу с Frida еще больше, вы должны посмотреть на биндинги для Python, C и NodeJS, их очень просто использовать как только вы разобрались как работать с Frida. Например, инъекция вашего скрипта chrome.js из Python, вы можете использовать Python bindings и создать
Если вы хотите хотите завершить сессию Frida и скрипты внедренные в этой сессии вызовите session.detach().
Для других примеров, как обычно, смотрите документацию Frida.
Продолжение следует!
Frida Framework vs Android p.2
Frida Framework vs Android p.3
Что такое динамическая бинарная инструментация
Динамическая бинарная инструментация (Dynamic binary instrumentation) позволяет внедрять сторонний код в уже запущенное приложение, для того чтобы оно делало то, что не делало раньше. Это не эксплуатация инъекции кода через ранее найденные уязвимости. Это не отладка приложения, ведь на самом деле вы не подключаетесь отладчиком к приложению, но это позволяет делать в общем-то похожие вещи. Какие возможности открывает динамическая бинарная инструментация:
- Доступ к памяти процесса
- Переопределение функций во время исполнения приложения
- Вызов функций из импортированных классов
- Поиск экземпляров объектов в куче и взаимодействие с ними
- Перехват и трассировка функций
Frida "позволяет внедрять JavaScript сниппеты или вашу собственную библиотеку в нативное приложение на Windows, MacOS, Linux, iOS, Android или QNX". Первоначально инструмент работал на движке Google V8 JavaScript Runtime, но начиная с 9 версии Frida использует Ducktape, при этом у вас есть возможность перейти обратно к V8, если вам это понадобиться. Frida определяет несколько режимов работы для взаимодействия с приложениями (включая возможность использования инструментария даже на устройстве без прав супер-пользователя), но сейчас мы рассмотрим самые частые примеры использования, не особо затрагивая методов их работы.
Для начала вам понадобиться:
- Frida
- frida-server, исполняемый файл скаченный со страницы релизов (на момент написания это frida-server-9.1.16-android-arm.xz. Версия frida-server должна совпадать с версией Frida.
- Android Emulator или устройство с root. Frida была разработана для Android 4.4 ARM, но работает и с более поздними версиями. Я успешно использовал Android 7.1.1 ARM для этого руководства. Для CrackMe во второй части статьи вам в любом случае понадобиться, что-то современнее, чем Android 4.4.
Если вы планируете повторить решение OWASP Unbreakable Crackme Level 1, который решается во второй части статьи, вам так же следует загрузить:
Frida предоставляет несколько APIs и способов для начала анализа. Вы можете использовать командную строку или инструмент наподобие frida-trace для отслеживания низкоуровневых функций (таких как вызов "open" в libc.so) и максимально быстрого начала. Вы можете использовать биндинги на C, NodeJS или Python для нетривиальных задач. Под капотом Frida работает на JavaScript и в большинстве случаев вам потребуется писать код на этом языке. Поэтому, если вы как и я немного недолюбливаете JavaScript (помимо его возможностей XSS), Frida еще одна причина что бы разобраться с ним получше.
Установите Frida, если еще не сделали этого (посмотрите README для других способов установки):
Код:
pip install frida
npm install frida
Код:
michael@sixtyseven:~$ adb devices
List of devices attached
emulator-5556 device
Код:
adb push /home/michael/Downloads/frida-server-9.1.16-android-arm /data/local/tmp/frida-server
Код:
adb shell
su
cd /data/local/tmp
chmod 755 frida-server
./frida-server
В другом терминале, на хостовой системе проверьте, что Frida запустилась и вы можете посмотреть список процессов на Android:
Код:
frida-ps -U
Код:
michael@sixtyseven:~$ frida-ps -U
PID Name
---- --------------------------------------------------
696 adbd
5828 android.ext.services
6188 android.process.acore
5210 audioserver
5211 cameraserver
8334 com.android.calendar
6685 com.android.chrome
6245 com.android.deskclock
5528 com.android.inputmethod.latin
6120 com.android.phone
6485 com.android.printspooler
8355 com.android.providers.calendar
5844 com.android.systemui
7944 com.google.android.apps.nexuslauncher
6416 com.google.android.gms
[...]
Например, вы можете отслеживать нужные вам вызовы функций в Chrome (запустите Chrome на эмуляторе, если он еще не запущен):
Код:
frida-trace -i "open" -U com.android.chrome
Код:
michael@sixtyseven:~$ frida-trace -i open -U -f com.android.chrome
Instrumenting functions...
open: Loaded handler at "/home/michael/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x2740 */
282 ms open(pathname=0xa843ffc9, flags=0x80002)
/* TID 0x2755 */
299 ms open(pathname=0xa80d0c44, flags=0x2)
/* TID 0x2756 */
309 ms open(pathname=0xa80d0c44, flags=0x2)
/* TID 0x2740 */
341 ms open(pathname=0xa80d06f7, flags=0x2)
592 ms open(pathname=0xa77dd3bc, flags=0x0)
596 ms open(pathname=0xa80d06f7, flags=0x2)
699 ms open(pathname=0xa80d105e, flags=0x80000)
717 ms open(pathname=0x9aff0d70, flags=0x42)
742 ms open(pathname=0x9ceffda0, flags=0x0)
758 ms open(pathname=0xa63b04c0, flags=0x0)
Код:
[...]
onEnter: function (log, args, state) {
log("open(" + "pathname=" + args[0] + ", flags=" + args[1] + ")");
},
[...]
Код:
onEnter: function (log, args, state) {
log("open(" + "pathname=" + Memory.readUtf8String(args[0])+ ", flags=" + args[1] + ")");
},
Код:
michael@sixtyseven:~$ frida-trace -i open -U -f com.android.chrome
Instrumenting functions...
open: Loaded handler at "/home/michael/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x29bf */
240 ms open(pathname=/dev/binder, flags=0x80002)
/* TID 0x29d3 */
259 ms open(pathname=/dev/ashmem, flags=0x2)
/* TID 0x29d4 */
269 ms open(pathname=/dev/ashmem, flags=0x2)
/* TID 0x29bf */
291 ms open(pathname=/sys/qemu_trace/process_name, flags=0x2)
453 ms open(pathname=/dev/alarm, flags=0x0)
456 ms open(pathname=/sys/qemu_trace/process_name, flags=0x2)
562 ms open(pathname=/proc/self/cmdline, flags=0x80000)
576 ms open(pathname=/data/dalvik-cache/arm/system@app@Chrome@Chrome.apk@classes.dex.flock, flags=0x42)
Еще одна вещь о которой стоит рассказать, вы можете либо запустить приложение перед внедрением Frida, либо передать аргумент -f для Frida, чтобы приложение было запущено автоматически.
Теперь используем интерфейс командной строки Frida, frida-cli:
Код:
frida -U -f com.android.chrome
В обоих случаях вы получаете shell (который не закрывается автоматически), куда вы можете вводить команды Frida следуя Frida JavaScript API. Нажмите TAB для просмотра доступных команд. Оболочка так же поддерживает автодополнение команд.
Большинство возможностей хорошо документированы. Для Android особенно внимательно посмотрите на секцию Java в JavaScript API (Я буду говорить о "Java API", хотя технически это просто обертка на JavaScript для доступа к Java объектам). Мы сосредоточимся на Java API, поскольку это наиболее удобный способ взаимодействия с Android приложениями. Вместо перехвата функций из libc мы будем работать напрямую с функциями и объектами из Java. (Примечание: Если вы заинтересованы, что вы можете делать с помощью Frida за пределами Java API, перехватывая более низкоуровневые C функции на Android, как мы делали с помощью frida-trace, посмотрите раздел документации посвященный функциям. Я не рассматриваю это здесь.)
Начиная работу с Java API просто получим версию Android на устройстве:
Код:
[USB::Android Emulator 5556::['com.android.chrome']]-> Java.androidVersion
"7.1.1"
Код:
[USB::Android Emulator 5556::['com.android.chrome']]-> Java.perform(function(){Java.enumerateLoadedClasses({"onMatch":function(className){ console.log(className) },"onComplete":function(){}})})
org.apache.http.HttpEntityEnclosingRequest
org.apache.http.ProtocolVersion
org.apache.http.HttpResponse
org.apache.http.impl.cookie.DateParseException
org.apache.http.HeaderIterator
Это тело функции, находящейся внутри обертки Java.perform:
Код:
Java.enumerateLoadedClasses(
{
"onMatch": function(className){
console.log(className)
},
"onComplete":function(){}
}
)
Код:
{
"onMatch":function(arg1, ...){ ... },
"onComplete":function(){ ... },
}
Сейчас мы углубимся в магию Frida и перезапишем функцию. Кроме того мы будем загрузим код из внешнего скрипта, вместо того что бы набирать его в консоли, это намного удобнее. Сохраните следующий код, например, как chrome.js:
Код:
Java.perform(function () {
var Activity = Java.use("android.app.Activity");
Activity.onResume.implementation = function () {
console.log("[*] onResume() got called!");
this.onResume();
};
});
Открой ваш эмулятор, откройте Chrome и выполните внедрение кода передав -l.
Код:
frida -U -l chrome.js com.android.chrome
Код:
[*] onResume() got called!
Прежде чем мы продолжим, предостережение: Когда наша эмуляция достаточно медленная Frida может останавливается по тайм-ауту. Для предотвращения этого оберните свои скрипты в функцию setImmediate или экспортируйте их как RPC. По умолчанию RPC во Frida не имеют тайм-аутов. setImmediateавтоматически перезапускает ваши скрипты во Frida, после того как вы изменили исходный файл, поэтому это еще и удобно. Также это запускает ваши скрипты в фоновом режиме. Это означает, что сразу получите cli, хотя Frida все еще обрабатывает ваши скрипты. Просто подождите и не покидайте cli, пока Frida не показала вам вывод ваших скриптов. Еще раз отредактируем chrome.js:
Код:
setImmediate(function() {
console.log("[*] Starting script");
Java.perform(function () {
Java.choose("android.view.View", {
"onMatch":function(instance){
console.log("[*] Instance found");
},
"onComplete":function() {
console.log("[*] Finished heap search")
}
});
});
});
Код:
[*] Starting script
[*] Instance found
[*] Instance found
[*] Instance found
[*] Instance found
[*] Finished heap search
Код:
setImmediate(function() {
console.log("[*] Starting script");
Java.perform(function () {
Java.choose("android.view.View", {
"onMatch":function(instance){
console.log("[*] Instance found: " + instance.toString());
},
"onComplete":function() {
console.log("[*] Finished heap search")
}
});
});
});
Код:
[*] Starting script
[*] Instance found: android.view.View{7ccea78 G.ED..... ......ID 0,0-0,0 #7f0c01fc app:id/action_bar_black_background}
[*] Instance found: android.view.View{2809551 V.ED..... ........ 0,1731-0,1731 #7f0c01ff app:id/menu_anchor_stub}
[*] Instance found: android.view.View{be471b6 G.ED..... ......I. 0,0-0,0 #7f0c01f5 app:id/location_bar_verbose_status_separator}
[*] Instance found: android.view.View{3ae0eb7 V.ED..... ........ 0,0-1080,63 #102002f android:id/statusBarBackground}
[*] Finished heap search
Предостережения
Когда вы экспериментируете с Frida вы заметите, что есть некоторые нестабильности. Во-первых внедрение стороннего когда в другой процесс может вызвать сбои, потому приложение будет работать не так, как планировалось. Во-вторых, сама Frida является экспериментальной. Она работает, но часто вы должны попробовать несколько способов для получения желаемого результата. Например, Frida всегда крашится, когда я пытаюсь загрузить скрипт и запустить процесс в одну команду. Вместо этого я сначала запускаю процесс, а затем внедряю скрипт. Поэтому я и показывал вам разные способы избежания тайм-аута. Возможно, вам будет нужно какой из них работает в вашем случае.
Python bindings
Если вы хотите автоматизировать вашу работу с Frida еще больше, вы должны посмотреть на биндинги для Python, C и NodeJS, их очень просто использовать как только вы разобрались как работать с Frida. Например, инъекция вашего скрипта chrome.js из Python, вы можете использовать Python bindings и создать
Код:
chrome.py
#!/usr/bin/python
import frida
# put your javascript-code here
jscode= """
console.log("[*] Starting script");
Java.perform(function() {
var Activity = Java.use("android.app.Activity");
Activity.onResume.implementation = function () {
console.log("[*] onResume() got called!");
this.onResume();
};
});
"""
# startup frida and attach to com.android.chrome process on a usb device
session = frida.get_usb_device().attach("com.android.chrome")
# create a script for frida of jsccode
script = session.create_script(jscode)
# and load the script
script.load()
Для других примеров, как обычно, смотрите документацию Frida.
Продолжение следует!
Frida Framework vs Android p.2
Frida Framework vs Android p.3