+ Reply to Thread
Results 1 to 1 of 1

Thread: Взлом Adnroid приложений с помощью Frida (Часть 2, CrackMe 1)

  1. #1

    Default Взлом Adnroid приложений с помощью Frida (Часть 2, CrackMe 1)

    Оригинал: 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:
    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 (или любой другой дизассемблер на ваш выбор, главное что бы он поддерживал Java). Вы также можете попробовать загрузить APK напрямую в BytecodeViewer или открыть его classes.dex, но у меня это не работало, поэтому я предварительно сконвертировал в jar с помощью dex2jar.

    В BytecodeViewer выберете View->Panel 1->CFR->Java для использования CFR Decompiler. Вы можете включить отображение smali кода во втором окне (Panel 2) если вы хотите сравнивать вывод декомпилятора с дизассемблированным листингом (который зачастую точнее декомпилированного кода).


    Вывод CFR Decompiler для MainActivity:
    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();
        }
    }
    Взглянув на остальные декомпилированные файлы, можно заметить, что приложение достаточно небольшое, и возможно, мы могли бы решить CrackMe просто разобрав алгоритм расшифровки и преобразования строк. Однако, после знакомства с Frida разве мы не можем сделать проще?

    Сперва посмотрим как приложение проверяет наличие root. Прямо над сообщением "Root detected" можно увидеть код:
    Code:
    if (sg.vantagepoint.a.c.a() || sg.vantagepoint.a.c.b() || sg.vantagepoint.a.c.c())
    Если вы перейдете в класс sg.vantagepoint.a.c вы увидите различные проверки на root:
    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;
        }
    Используя Frida мы можем сделать так, что бы все эти методы возвращали false, перезаписав их как мы делали еще в первой части руководства. Но что происходит, когда функция возвращает true, потому что обнаруживает root. Как мы видели в функции a, находящейся в MainActivity происходит открытие диалога. Так же устанавливается обработчик onClickListener на нажатие кнопки "OK".
    Code:
    alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new b(this));
    Реализация onClickListener довольно небольшая:
    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);
        }
    }
    После события нажатия приложение закрывается, вызывая System.exit(0). Таким образов все, что нам нужно сделать это избежать закрытия приложения, переписав метод onClick в Frida. Создайте файл uncrackable1.js и разместите там следующий код:
    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")
    
        })
    })
    Если вы прочитали первый пост из этой серии статей, скрипт должен быть вам понятен: мы оборачиваем наш код функцией setImmediate для предотвращения тайм-аутов (возможно вам это и не понадобится), а затем вызываем, Java.perform чтобы использовать функции Frida для работы с Java. Затем начинается настоящая магия: мы получаем класс который реализует интерфейс OnClickListener и перезаписываем его метод onClick. В нашей версии эта функция просто пишет какой-то консольный вывод. В отличие от оригинала, выход из приложения не выполняется. Поскольку оригинал функции onClickHandler подменяется на нашу внедренную функцию с помощью Frida, она никогда не выполнится и приложение больше не закроется, когда мы нажмем кнопку "OK" в диалоге. Давайте проверим это.
    Откройте приложение (пусть оно снова отобразит диалоговое окно «Root detected»)



    и внедрите скрипт:
    Code:
    frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
    Дайте Frida несколько секунд для применения изменений кода, пока вы не увидите сообщение “onClick handler modified” (возможно вы получите shell раньше, из-за оборачивания в setImmediate функция выполняется в фоне):


    Затем нажмите кнопку "ОК" в приложении. Если все прошло хорошо приложение не закроется.



    Отлично, диалог исчезает, и мы можем ввести пароль. Давайте введем что-нибудь и посмотрим, что происходит:



    Неверный код, как, собственно, и следовало ожидать. Но мы знаем, что нам нужно искать: какие-либо функции шифрования, расшифровки и сравнения нашего ввода.

    Снова смотрим в MainActivity и видим функцию:
    Code:
    public void verify(View object) {
    Она вызывает метод a из класса sg.vantagepoint.uncrackable1.a
    Code:
    if (a.a((String)object)) {
    Это декомпилированный код класса sg.vantagepoint.uncrackable1.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;
        }
    }
    Обратите внимание на вызов string.equals для сравнения строк в конце метода a и создание строки arrby2 в блоке try выше. arrby2 это значение возвращаемое из функции sg.vantagepoint.a.a.a. string.equals сравнивает наш ввод с arrby2. Итак, нам нужно получить значение возвращаемое из 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");
    Мы перезаписываем функцию sg.vantagepoint.a.a.a, перехватываем ее возвращаемое значение, а после преобразовываем в читаемую строку. Это расшифрованная строка, которую мы ищем, поэтому вы выводим ее в консоли и надеемся, что получили решение.

    Соединяем два скрипта в один полный:
    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");
    
    
        });
    
    });
    Давайте запустим этот скрипт. Как и прежде сохраните его как uncrackable1.js и выполните (если Frida не сделает это автоматически):
    Code:
    frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
    Дождитесь сообщения о модификации sg.vantagepoint.a.a.a, после чего нажмите "ОК" в диалоге Root detected введите что-нибудь в поле секретного кода и нажмите проверить. Проверку мы не прошли, но посмотрим на вывод во Frida:
    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
    Хорошо. Теперь мы имеем расшифрованную строку I want to believe. Вот и все, давайте проверим, что она действительно подходит:



    Я надеюсь, что к этому моменту вы, по крайне мере, немного впечатлены тем, что можете делать с Frida и какие возможности открывает динамическая бинарная иструментация.

    © Translated by norver special for r0 Crew
    Last edited by Darwin; 25-10-2018 at 11:42.

  2. 2 пользователя(ей) сказали cпасибо:
    Darwin (25-10-2018) korsader (24-10-2018)
+ Reply to Thread

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 01:17
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org