- Название: Android UnCrackable L3
- Категория: reverse (android)
- Платформа:
Ссылка скрыта от гостей
При заходе в приложение нас встречает нелицеприятная, но вполне знакомая картина:
Что радует - из приложения нас моментально не выкидывает, как в L2. Можно было бы, как я и раньше делал, воспользоваться модулем DenyList и надеяться, что приложение не задетектит рут... Но сегодня мы пожалеем тех, у кого этого модуля нет, и поступим по другому: пы пропатчим само приложение!
Для начала откроем его в JADX-GUI (сразу MainActivity):
Сразу с самого начала бросается в глаза xorkey - это нам может пригодиться, запоминаем.
А здесь можно видеть один из методов защиты от патчеров - проверка библиотек и classes.dex на CRC-хэш. Запоминаем потенциальное место для патчинга и смотрим дальше.
Здесь идёт проверка на дебаггинг и наличие рута. Методы checkRoot1, 2, и 3 остались с прошлых крякми, там всё то же самое. Если посмотреть на этот же код в smali, можно увидеть следующее:
Каждая проверка if-nez ведёт на метку cond_0041, куда нам точно не нужно попасть. В таком случае, можно было бы просто удалить эти строчки, а последнюю - if-eqz v0, :cond_0046 - просто заменить на goto. А можно просто из метки cond_0041 сделать пустышку, удалив вызов метода showDialog(). Это куда проще - этим и воспользуемся. И заодно, раз уж мы тут, можно и предоставить себе возможность спокойно дебажить приложение! Для этого понадобится убрать ещё 2 строчки:
Но не забывайте - в таком случае в собранном из отредактированных smali-файлов classes.dex изменится CRC-хэш, а значит, нам нужно пропатчить ещё и тот кусок кода. Если хорошенько туда присмотреться, то можно понять, что самое опасное для нас - это последний System.exit():
Можно было бы удалить этот вызов - пускай программа высчитывает хэши и логирует об ошибках на здоровье. А можно просто добавить return-void сразу после объявления метода - и на выходе verifyLibs() окажется просто функцией, которая ничего не делает. Как вы захотите это обойти и захотите ли - дело ваше.
А нам для нашего грядущего патчинга JADX недостаточно (без плагинов). Для этого нужно самим распаковать приложение в smali через
Ссылка скрыта от гостей
(я буду пользоваться удобной GUI-версией). Декомпилируем наш apk:После этого рядом с ним у вас должна появиться папка со smali-кодом, ресурсами приложения и прочим. Так как JADX под капотом использует apktool, то имена классов и методов, что там, что там, совпадают. Поэтому нам нужен тот самый MainActivity по пути
smali/sg/vantagepoint/uncrackable3/
, который мы смотрели раньше. В нашем случае он разделился на несколько файлов MainActivity$n. Открываем их в любом текстовом редакторе, ищем непонравившиеся нам строчки (они не в одном файле), и смело их удаляем или комментируем (# в начале).Когда вы будете пытаться собрать приложение обратно, не забудьте проверить, что в пути к папке с кодом нет символов кириллицы - иначе такая вот петрушка вылезет:
Переместите папку в другое место и соберите всё, как полагается:
Установим приложение на устройство и попробуем зайти в него:
Получилось! Мы успешно пропатчили приложение, обойдя его защиту. Чем не повод для гордости?
Но не стоит забывать о главной цели - нам нужно достать подходящую под формочку строку. В этот раз разнообразим нашу методологию и попробуем frida. Не буду вещать сейчас, как она устанавливается, скажу лишь, что мне помог этот
Ссылка скрыта от гостей
. Подключаем отладку через adb, запускаем на смартфоне frida-server и проверяем его работоспособность с помощью frida-ps -Ua
. Если всё прошло хорошо, то на выходе вы получите список пакетов с их названиями (все лишние я замазал):Если же нет - гугл в помощь, товарищи. Из наблюдаемых нами ивентов, которые могут помочь нам с решением, присутствует только кнопка Verify, с соответствующим метод onClick. Попробуем с помощью frida-trace просмотреть, какой класс отвечает за эту кнопку. Конечная команда будет выглядеть так:
frida-trace -U -F -i "*!onClick"
(U - usb-устройство, F - текущее открытое приложение, j - паттерн вида class!method). Пробуем:И получаем такую хренотень. Гугл на такую ошибку ничего дельного не выдаёт, что же делать? После получаса активного поиска причины этой ошибки и листинга логов (
logcat | grep -i "uncrackable"
) я нашёл её:Всё-таки фриду приложение видит и защищается от неё. И если в самом приложении мы уже все виды защиты нейтрализовали, остаются только подключаемые библиотеки - не зря же их на CRC проверяли?
Сначала глянем libfoo.so (x86_64) в IDA, для начала откроем строки:
А вот и то, что мы видели в логах! Посмотрим, где эта строка используется:
А вот и ещё одна мешающая нам защита - уже от frida и xposed. Её логика проста: она постоянно читает данные из
/proc/self/maps
, а когда ей это не удаётся - ругается. Можно пропатчить её так: сразу после начала, до каких-либо пушей (пока сверху в стеке у нас лежит адрес возврата) прописать ret, чтобы функция сразу же умирала. Для этого вам очень поможет этот
Ссылка скрыта от гостей
- он может переводить байткод в ассемблерные инструкции и наоборот. Первой инструкцией в функции идёт push rbp (0x55) - её можно заменить на ret (0xC3). Обратите внимание - когда вы патчите программу, вам необходимо, чтобы общее количество байт было одинаково. Соответственно, если патчить без жутких костылей, то количество байт в заменяющей инструкции должно не превосходить количество байт в заменяемой. Если они равны - прекрасно! Если в заменяющей меньшее количество байт, то добавьте, сколько нужно до равного числа, nop'ов (0x90). В нашем случае обе инструкции занимают 1 байт, соответственно, нам лишь нужно переписать 55 на C3:Если вы всё сделали правильно, то у вас должен смениться код:
После этого не забудьте применить ваш патч к исходному файлу:
.
Ну и, конечно, с помощью apktool соберите заново приложение с новой библиотекой - думаю, с этим у вас проблем не возникнет, только закройде IDA и удалите все остаточные файлы её базы данных, а то будет ошибка при сборке. И, наконец, попробуем с пропатченным приложением и его пропатченной библиотекой запустить всё ту же frida-trace:
Опять то же самое... Чекаем логи:
А помните, что у нас не одна библиотека, а целых 4? Кажется, нам придётся патчить именно ту библиотеку, архитектура которой совпадает с архитектурой вашего устройства, об этом я сразу как-то не подумал... Чтобы узнать, какая именно архитектура вам нужна, пропишите следующее:
Bash:
adb shell getprop ro.product.cpu.abi
Я на всякий случай распишу все возможные варианты: для патча Arm-библиотек я пользовался
Ссылка скрыта от гостей
сайтом, он мне очень помог. Для библиотеки arm64-v8a
достаточно заменить первые 4 байта на C0 03 5F D6 (RET)
. А вот при патчинге библиотеки armeabi-v7a
не забывайте немножко теории: там инструкции могут распознаваться в двух режимах: ARM и Thumb, каждый со своими опкодами. IDA поддерживает оба и позволяет переключаться между ними через виртуальный регистр, о чём она вас при открытии библиотеки предупредит (переключение на Alt+G). В режиме Thumb нужно заменить первые 2 байта на 70 47
, чтобы у вас получилась инструкция BX LR
(аналог RET).И теперь, пропатчив всё что только движется, снова собираем этот треклятый apk, устанавливаем, запускаем - и:
И наши старания оправдались!!! Отозвались 40 функций - фрида работает! Давайте ещё раз глянем, что нам нужно сделать:
Судя по всему, check_code() загружается как нативная функция из библиотеки libfoo.so. Узреем её логику работы:
В 26-й строчке наглядно видно, что байты одного массива соответствующе ксорятся с другим. Напоминает xorkey из начала? Причём v4 - судя по всему наш xorkey - мы получаем в 22 строчке, а вот второй массив (v9) мелькает только в функции sub_FA0, куда передаётся его адрес. Можно предположить, что там - то, что там будет то, что при XOR с xorkey даст правильный ответ. И всё было бы хорошо - можно было бы изучить sub_FA0 и узнать, как геренируется этот секрет, но ревёрсить эту функцию... Ну...
...не самая лучшая идея. Мы настроили нашу программу под свободный дебаггинг/тамперинг, так что у нас есть 2 пути: либо можно решить через frida (находим базовый адрес libfoo.so, добавляем оффсет функции, и через функции onEnter/onLeave вытаскиваем готовый массив), либо можно отладить это дело через gdb. Признаюсь - этот райтап я писал долгое время, в процессе решения. Изначально я планировал решить крякми через Frida, но почему-то она отчаянно не хотела находить нужную функцию (мистика =] ), поэтому я представлю своё решение через отладку... Прямо в IDA!
Чтобы вы не искали по всему интернету gdbserver под arm64, в IDA PRO () в папке dbgsrv лежит любезно предоставленный HexRays android_server64. Его достаточно будет запустить из-под рута, через adb настроить форвард порта - и можно спокойно дебажить библиотеку android-приложения!
Bash:
adb push android_server64 /data/local/tmp
adb shell
А затем в самом шелле:
Bash:
su
cd /data/local/tmp
chmod +x android_server64
./android_server64
Затем настройте форвардинг используемого порта:
Bash:
adb forward tcp:23946 tcp:23946
Надеюсь, вы ещё не удалили папку с патченной библиотекой? Если нет, то круто - напомню, что нас интересует функция, которая вызывается с адресом массива, с которым будет идти проверка - на ней и ставьте бряк:
В качестве дебаггера выберите
"Remote ARM Linux/Android debugger"
:И дальше в
Debugger -> Debugger Options
в качестве адреса ставьте 127.0.0.1
, а в качестве порта - тот, который вы форвардилиТеперь запускайте приложение на смартфоне, и в
Debugger -> Attach to process
выбирайте owasp.mstg.uncrackable3
:После этого Ида начнёт судорожно качать библиотеки и скажет: "Я нашла точно такую же библиотеку, как у вас открыта, но она в папке с самим приложением. Хотите её отлаживать?"
Мы, конечно же, хотим, поэтому нажимаем "Same". После этого вы остановитесь в непонятном месте - это лишь инициализация библиотеки, так что смело жмём "Continue Process" (F9). А уже после этого можно ввести что-нибудь в формочку и нажать verify - и вы остановитесь на нужной вам функции:
Перешагните через эту функцию, не заходя внутрь (F8) и посмотрите, что в переменной v8:
Эти 24 байта - и есть наш секретный массив, теперь мы его можем спокойно расшифровать, используя ранее упомянутый xorkey:
Python:
xor_key = "pizzapizzapizzapizzapizz"
memory = """[stack]:0000007FFA7C0CF8 DCB 0x1D
[stack]:0000007FFA7C0CF9 DCB 8
[stack]:0000007FFA7C0CFA DCB 0x11
[stack]:0000007FFA7C0CFB DCB 0x13
[stack]:0000007FFA7C0CFC DCB 0xF
[stack]:0000007FFA7C0CFD DCB 0x17
[stack]:0000007FFA7C0CFE DCB 0x49 ; I
[stack]:0000007FFA7C0CFF DCB 0x15
[stack]:0000007FFA7C0D00 DCB 0xD
[stack]:0000007FFA7C0D01 DCB 0
[stack]:0000007FFA7C0D02 DCB 3
[stack]:0000007FFA7C0D03 DCB 0x19
[stack]:0000007FFA7C0D04 DCB 0x5A ; Z
[stack]:0000007FFA7C0D05 DCB 0x1D
[stack]:0000007FFA7C0D06 DCB 0x13
[stack]:0000007FFA7C0D07 DCB 0x15
[stack]:0000007FFA7C0D08 DCB 8
[stack]:0000007FFA7C0D09 DCB 0xE
[stack]:0000007FFA7C0D0A DCB 0x5A ; Z
[stack]:0000007FFA7C0D0B DCB 0
[stack]:0000007FFA7C0D0C DCB 0x17
[stack]:0000007FFA7C0D0D DCB 8
[stack]:0000007FFA7C0D0E DCB 0x13
[stack]:0000007FFA7C0D0F DCB 0x14""".split()
secret = []
for i in range(len(memory)):
if memory[i - 1] == "DCB":
base = 10
if "x" in memory[i]:
base = 16
secret.append(int(memory[i], base))
for i in range(len(secret)):
print(chr(ord(xor_key[i]) ^ secret[i]), end="")
print()
Запускаем:
Пробуем нашу строку:
Получилось! Поздравляю, мы решили уже довольно сложный крякми, в отличие от первых двух. И декомпилили приложение, и патчили smali, и даже обходили антидебаггинг в библиотеках под разные архитектуры, и дебажили одну из них... Но наши старания окупились!
Статья вышла немаленькой - я писал её три дня, в процессе решения самого крякми. Конечно, если бы получилось решить через Фриду, было бы чуточку полегче, но что есть, то есть - может, кто-то захочет решить по-своему =]
Надеюсь, этот немаленький райтап помог вам в нашей нелёгкой стезе.
Удачного ревёрса!
made 4 @rev_with_da_boys
Вложения
Последнее редактирование: