- Название: Android UnCrackable L3
- Категория: reverse (android)
- Платформа: mas.owasp.org
Иногда я буду ссылаться на предыдущие две статьи, посвящённые крякми
L1 и
L2, поэтому рекомендую к ознакомлению. А на очереди третьего уровня уровень сложности крякми от
OWASP- уже довольно проблематичный экземпляр. Но обо всём по порядку!
При заходе в приложение нас встречает нелицеприятная, но вполне знакомая картина:
Что радует - из приложения нас моментально не выкидывает, как в
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 через
apktool(я буду пользоваться удобной
GUI-версией). Декомпилируем наш
apk:
После этого рядом с ним у вас должна появиться папка со
smali-кодом, ресурсами приложения и прочим. Так как
JADXпод капотом использует
apktool, то имена классов и методов, что там, что там, совпадают. Поэтому нам нужен тот самый
MainActivityпо пути
Код:
smali/sg/vantagepoint/uncrackable3/
, который мы смотрели раньше. В нашем случае он разделился на несколько файлов
MainActivity$n. Открываем их в любом текстовом редакторе, ищем непонравившиеся нам строчки (они не в одном файле), и смело их удаляем или комментируем (
# в начале).
Когда вы будете пытаться собрать приложение обратно, не забудьте проверить, что в пути к папке с кодом нет символов кириллицы - иначе такая вот петрушка вылезет:
Переместите папку в другое место и соберите всё, как полагается:
Установим приложение на устройство и попробуем зайти в него:
Получилось! Мы успешно пропатчили приложение, обойдя его защиту. Чем не повод для гордости?
Но не стоит забывать о главной цели - нам нужно достать подходящую под формочку строку. В этот раз разнообразим нашу методологию и попробуем
frida. Не буду вещать сейчас, как она устанавливается, скажу лишь, что мне помог этот
гайд. Подключаем отладку через
adb, запускаем на смартфоне
frida-server и проверяем его работоспособность с помощью
. Если всё прошло хорошо, то на выходе вы получите список пакетов с их названиями (все лишние я замазал):
Если же нет - гугл в помощь, товарищи. Из наблюдаемых нами ивентов, которые могут помочь нам с решением, присутствует только кнопка
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. Её логика проста: она постоянно читает данные из
, а когда ей это не удаётся - ругается. Можно пропатчить её так: сразу после начала, до каких-либо пушей (пока сверху в стеке у нас лежит адрес возврата) прописать
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-библиотек я пользовался
этим сайтом, он мне очень помог. Для библиотеки
достаточно заменить первые 4 байта на
. А вот при патчинге библиотеки
не забывайте немножко теории: там инструкции могут распознаваться в двух режимах:
ARM и
Thumb, каждый со своими опкодами.
IDAподдерживает оба и позволяет переключаться между ними через виртуальный регистр, о чём она вас при открытии библиотеки предупредит (переключение на
Alt+G). В режиме
Thumbнужно заменить первые 2 байта на
, чтобы у вас получилась инструкция
(аналог
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
в качестве адреса ставьте
, а в качестве порта - тот, который вы форвардили
Теперь запускайте приложение на смартфоне, и в
Код:
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