Оригинал: codemetrix.net
Теперь, после знакомства с Frida в первой части, мы готовы к решению небольшого CrackMe используя Frida. После того что мы узнали о Frida это должно быть очень легко, ну теоретически. Если вы хотите повторять за мной, скачайте:
Кончено, я предполагаю, что вы уже установили Frida (версии 9.1.16 или позднее) на свой компьютере и запустили соответствующий frida-server на устройстве с правами супер-пользователя. Для этого руководства я буду использовать образ Android 7.1.1 ARM запущенный в эмуляторе.
Установите Uncrackable Crackme Level 1 на ваше устройство:
Подождите пока он установится, затем запустите его (оранжевая иконка в правом нижнем углу):Code:adb install sg.vantagepoint.uncrackable1.apk
После запуска приложение сообщит, что не оно не работает на устройстве с правами супер-пользователя:
Если вы нажмете “ОК” приложение сразу же закроется. Хм. Нехорошо. Похоже мы не сможем решить CrackMe таким способом. А разве мы ожидали другого? Давайте разберемся что происходит, разберем как работает приложение внутри.
Сконвертируем APK в jar c помощью dex2jar:
Теперь загрузим файл а BytecodeViewer (или любой другой дизассемблер на ваш выбор, главное что бы он поддерживал Java). Вы также можете попробовать загрузить APK напрямую в BytecodeViewer или открыть его classes.dex, но у меня это не работало, поэтому я предварительно сконвертировал в jar с помощью dex2jar.Code:michael@sixtyseven:/opt/dex2jar/dex2jar-2.0$ ./d2j-dex2jar.sh -o /home/michael/UnCrackable-Level1.jar /home/michael/UnCrackable-Level1.apk dex2jar /home/michael/UnCrackable-Level1.apk -> /home/michael/UnCrackable-Level1.jar
В BytecodeViewer выберете View->Panel 1->CFR->Java для использования CFR Decompiler. Вы можете включить отображение smali кода во втором окне (Panel 2) если вы хотите сравнивать вывод декомпилятора с дизассемблированным листингом (который зачастую точнее декомпилированного кода).
Вывод CFR Decompiler для MainActivity:
Взглянув на остальные декомпилированные файлы, можно заметить, что приложение достаточно небольшое, и возможно, мы могли бы решить CrackMe просто разобрав алгоритм расшифровки и преобразования строк. Однако, после знакомства с Frida разве мы не можем сделать проще?Code:package sg.vantagepoint.uncrackable1; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.text.Editable; import android.view.View; import android.widget.EditText; import sg.vantagepoint.uncrackable1.a; import sg.vantagepoint.uncrackable1.b; import sg.vantagepoint.uncrackable1.c; public class MainActivity extends Activity { private void a(String string) { AlertDialog alertDialog = new AlertDialog.Builder((Context)this).create(); alertDialog.setTitle((CharSequence)string); alertDialog.setMessage((CharSequence)"This in unacceptable. The app is now going to exit."); alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new b(this)); alertDialog.show(); } protected void onCreate(Bundle bundle) { if (sg.vantagepoint.a.c.a() || sg.vantagepoint.a.c.b() || sg.vantagepoint.a.c.c()) { this.a("Root detected!"); //This is the message we are looking for } if (sg.vantagepoint.a.b.a((Context)this.getApplicationContext())) { this.a("App is debuggable!"); } super.onCreate(bundle); this.setContentView(2130903040); } public void verify(View object) { object = ((EditText)this.findViewById(2131230720)).getText().toString(); AlertDialog alertDialog = new AlertDialog.Builder((Context)this).create(); if (a.a((String)object)) { alertDialog.setTitle((CharSequence)"Success!"); alertDialog.setMessage((CharSequence)"This is the correct secret."); } else { alertDialog.setTitle((CharSequence)"Nope..."); alertDialog.setMessage((CharSequence)"That's not it. Try again."); } alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new c(this)); alertDialog.show(); } }
Сперва посмотрим как приложение проверяет наличие root. Прямо над сообщением "Root detected" можно увидеть код:
Если вы перейдете в класс sg.vantagepoint.a.c вы увидите различные проверки на root:Code:if (sg.vantagepoint.a.c.a() || sg.vantagepoint.a.c.b() || sg.vantagepoint.a.c.c())
Используя Frida мы можем сделать так, что бы все эти методы возвращали false, перезаписав их как мы делали еще в первой части руководства. Но что происходит, когда функция возвращает true, потому что обнаруживает root. Как мы видели в функции a, находящейся в MainActivity происходит открытие диалога. Так же устанавливается обработчик onClickListener на нажатие кнопки "OK".Code:public static boolean a() { String[] a = System.getenv("PATH").split(":"); int i = a.length; int i0 = 0; while(true) { boolean b = false; if (i0 >= i) { b = false; } else { if (!new java.io.File(a[i0], "su").exists()) { i0 = i0 + 1; continue; } b = true; } return b; } } public static boolean b() { String s = android.os.Build.TAGS; if (s != null && s.contains((CharSequence)(Object)"test-keys")) { return true; } return false; } public static boolean c() { String[] a = new String[7]; a[0] = "/system/app/Superuser.apk"; a[1] = "/system/xbin/daemonsu"; a[2] = "/system/etc/init.d/99SuperSUDaemon"; a[3] = "/system/bin/.ext/.su"; a[4] = "/system/etc/.has_su_daemon"; a[5] = "/system/etc/.installed_su_daemon"; a[6] = "/dev/com.koushikdutta.superuser.daemon/"; int i = a.length; int i0 = 0; while(i0 < i) { if (new java.io.File(a[i0]).exists()) { return true; } i0 = i0 + 1; } return false; }
Реализация onClickListener довольно небольшая:Code:alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new b(this));
После события нажатия приложение закрывается, вызывая System.exit(0). Таким образов все, что нам нужно сделать это избежать закрытия приложения, переписав метод onClick в Frida. Создайте файл uncrackable1.js и разместите там следующий код:Code:package sg.vantagepoint.uncrackable1; class b implements android.content.DialogInterface$OnClickListener { final sg.vantagepoint.uncrackable1.MainActivity a; b(sg.vantagepoint.uncrackable1.MainActivity a0) { this.a = a0; super(); } public void onClick(android.content.DialogInterface a0, int i) { System.exit(0); } }
Если вы прочитали первый пост из этой серии статей, скрипт должен быть вам понятен: мы оборачиваем наш код функцией setImmediate для предотвращения тайм-аутов (возможно вам это и не понадобится), а затем вызываем, Java.perform чтобы использовать функции Frida для работы с Java. Затем начинается настоящая магия: мы получаем класс который реализует интерфейс OnClickListener и перезаписываем его метод onClick. В нашей версии эта функция просто пишет какой-то консольный вывод. В отличие от оригинала, выход из приложения не выполняется. Поскольку оригинал функции onClickHandler подменяется на нашу внедренную функцию с помощью Frida, она никогда не выполнится и приложение больше не закроется, когда мы нажмем кнопку "OK" в диалоге. Давайте проверим это.Code:setImmediate(function() { //prevent timeout console.log("[*] Starting script"); Java.perform(function() { bClass = Java.use("sg.vantagepoint.uncrackable1.b"); bClass.onClick.implementation = function(v) { console.log("[*] onClick called"); } console.log("[*] onClick handler modified") }) })
Откройте приложение (пусть оно снова отобразит диалоговое окно «Root detected»)
и внедрите скрипт:
Дайте Frida несколько секунд для применения изменений кода, пока вы не увидите сообщение “onClick handler modified” (возможно вы получите shell раньше, из-за оборачивания в setImmediate функция выполняется в фоне):Code:frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
Затем нажмите кнопку "ОК" в приложении. Если все прошло хорошо приложение не закроется.
Отлично, диалог исчезает, и мы можем ввести пароль. Давайте введем что-нибудь и посмотрим, что происходит:
Неверный код, как, собственно, и следовало ожидать. Но мы знаем, что нам нужно искать: какие-либо функции шифрования, расшифровки и сравнения нашего ввода.
Снова смотрим в MainActivity и видим функцию:
Она вызывает метод a из класса sg.vantagepoint.uncrackable1.aCode:public void verify(View object) {
Это декомпилированный код класса sg.vantagepoint.uncrackable1.a:Code:if (a.a((String)object)) {
Обратите внимание на вызов string.equals для сравнения строк в конце метода a и создание строки arrby2 в блоке try выше. arrby2 это значение возвращаемое из функции sg.vantagepoint.a.a.a. string.equals сравнивает наш ввод с arrby2. Итак, нам нужно получить значение возвращаемое из sg.vantagepoint.a.a.a.Code:package sg.vantagepoint.uncrackable1; import android.util.Base64; import android.util.Log; /* * Exception performing whole class analysis ignored. */ public class a { public static boolean a(String string) { byte[] arrby = Base64.decode((String)"5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", (int)0); byte[] arrby2 = new byte[]{}; try { arrby2 = arrby = sg.vantagepoint.a.a.a((byte[])a.b((String)"8d127684cbc37c17616d806cf50473cc"), (byte[])arrby); } catch (Exception var2_2) { Log.d((String)"CodeCheck", (String)("AES error:" + var2_2.getMessage())); } if (!string.equals(new String(arrby2))) return false; return true; } public static byte[] b(String string) { int n = string.length(); byte[] arrby = new byte[n / 2]; int n2 = 0; while (n2 < n) { arrby[n2 / 2] = (byte)((Character.digit(string.charAt(n2), 16) << 4) + Character.digit(string.charAt(n2 + 1), 16)); n2 += 2; } return arrby; } }
Мы можем начать реверс-инжинерить преобразование строк и функций расшифровки и работать с оригинальным зашифрованными строками, которые содержаться в вышеприведенном коде. Или мы можем оставить все эти действия приложению, не заботясь о них и просто перехватить возвращаемое значение функции sg.vantagepoint.a.a.a. Возвращаемое значение является уже расшифрованной строкой (в виде массива байтов) с которой сравнивается наш ввод. Следующий скрипт нам в этом поможет:
Мы перезаписываем функцию sg.vantagepoint.a.a.a, перехватываем ее возвращаемое значение, а после преобразовываем в читаемую строку. Это расшифрованная строка, которую мы ищем, поэтому вы выводим ее в консоли и надеемся, что получили решение.Code:aaClass = Java.use("sg.vantagepoint.a.a"); aaClass.a.implementation = function(arg1, arg2) { retval = this.a(arg1, arg2); password = '' for(i = 0; i < retval.length; i++) { password += String.fromCharCode(retval[i]); } console.log("[*] Decrypted: " + password); return retval; } console.log("[*] sg.vantagepoint.a.a.a modified");
Соединяем два скрипта в один полный:
Давайте запустим этот скрипт. Как и прежде сохраните его как uncrackable1.js и выполните (если Frida не сделает это автоматически):Code:setImmediate(function() { console.log("[*] Starting script"); Java.perform(function() { bClass = Java.use("sg.vantagepoint.uncrackable1.b"); bClass.onClick.implementation = function(v) { console.log("[*] onClick called."); } console.log("[*] onClick handler modified") aaClass = Java.use("sg.vantagepoint.a.a"); aaClass.a.implementation = function(arg1, arg2) { retval = this.a(arg1, arg2); password = '' for(i = 0; i < retval.length; i++) { password += String.fromCharCode(retval[i]); } console.log("[*] Decrypted: " + password); return retval; } console.log("[*] sg.vantagepoint.a.a.a modified"); }); });
Дождитесь сообщения о модификации sg.vantagepoint.a.a.a, после чего нажмите "ОК" в диалоге Root detected введите что-нибудь в поле секретного кода и нажмите проверить. Проверку мы не прошли, но посмотрим на вывод во Frida:Code:frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
Хорошо. Теперь мы имеем расшифрованную строку I want to believe. Вот и все, давайте проверим, что она действительно подходит:Code:michael@sixtyseven:~/Development/frida$ frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1 ____ / _ | Frida 9.1.16 - A world-class dynamic instrumentation framework | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/ [*] Starting script [USB::Android Emulator 5554::sg.vantagepoint.uncrackable1]->[*] onClick handler modified [*] sg.vantagepoint.a.a.a modified [*] onClick called. [*] Decrypted: I want to believe
Я надеюсь, что к этому моменту вы, по крайне мере, немного впечатлены тем, что можете делать с Frida и какие возможности открывает динамическая бинарная иструментация.
© Translated by norver special for r0 Crew










Reply With Quote
Thanks
