R0 CREW

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

android
crackme
reverse
ru
#1

Оригинал: codemetrix.net

Вскоре после моего второго поста о Frida @muellerberndt опубликовал другой OWASP Android CrackMe и мне стало интересно смогу ли я снова решить его с помошью Frida. Итак, если вы вдруг захотите повторять мои действия вам потребуются:

Примечание: со времен написания этого поста CrackMe немного изменился, поэтому дизассеблированный и декомпилированный код может немного иначе. Общие шаги решение не должны значительно измениться.

Если вам нужна информация как установить Frida, пожалуйста посмотрите документацию. Для получения начальной информации об использовании Frida вы так же можете прочитать первый пост из этой серии статей. Я полагаю, что у вас есть все что будет необходимо и вы немного знакомы с Frida. Убедитесь, что Frida может подключиться к вашему устройству или эмулятору (Например, используя команду frida-ps -U).

Предупреждение: Это не просто пошаговое руководство для решение CrackMe. Вместо этого я собираюсь показать различные техники преодоления конкретных проблем. Если вам нужно только само решение, просто посмотрите на итоговый скрипт для Frida в конце этой статьи.

Примечание, если вы столкнетесь с:

Error: access violation accessing 0xebad8082

Или похожими ошибками, при использовании Frida, вам может помочь удаление пользовательских данных на эмуляторе, перезапустите его и установите apk снова.

Будьте готовы повторять действия по нескольку раз. Приложение может внезапно крашнуться, эмулятор может перезапуститься, все может пойти не так, но в конченом итоге это все равно работает.

Первый запуск

Мы начинаем делать тоже самое, что делали с UnCrackable1, просто запустим приложение: ничего нового, когда вы запускаете его на эмуляторе он обнаруживает root на устройстве.

Мы можем повторить действия из UnCrackable 1 перезаписывая OnClickListener. Но для начала проверим можем ли мы подключиться чтобы начать исследование:

michael@sixtyseven:~/Development$ frida -U sg.vantagepoint.uncrackable2
     ____
    / _  |   Frida 9.1.27 - 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/
Failed to attach: ambiguous name; it matches: sg.vantagepoint.uncrackable2 (pid: 5184), sg.vantagepoint.uncrackable2 (pid: 5201)

Как так? Frida сообщает, что существует два процесса с одинаковым именем. Проверим это выполнив frida-ps -U:

5184  sg.vantagepoint.uncrackable2
5201  sg.vantagepoint.uncrackable2

Странно. Давайте попробуем внедрить Frida в родительский процесс:

michael@sixtyseven:~/Development$ frida -U 5184
     ____
    / _  |   Frida 9.1.27 - 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/
Failed to attach: unable to access process with pid 5184 due to system restrictions; try `sudo sysctl kernel.yama.ptrace_scope=0`, or run Frida as root

Не получается и поскольку мы получаем аналогичный результат когда запускаем Frida от рута, предлагаемое решение нам, увы, никак не поможет. Почему так происходит? Распакуем APK и декомпилируем classes.dex с помощью BytecodeViewer.

package sg.vantagepoint.uncrackable2;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.c;
import android.text.Editable;
import android.view.View;
import android.widget.EditText;
import sg.vantagepoint.a.a;
import sg.vantagepoint.a.b;
import sg.vantagepoint.uncrackable2.CodeCheck;
import sg.vantagepoint.uncrackable2.MainActivity;

public class MainActivity
extends c {
    private CodeCheck m;

    static {
        System.loadLibrary("foo"); //[1]
    }

    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 /* Unavailable Anonymous Inner Class!! */);
        alertDialog.setCancelable(false);
        alertDialog.show();
    }

    static /* synthetic */ void a(MainActivity mainActivity, String string) {
        mainActivity.a(string);
    }

    private native void init(); //[2]

    protected void onCreate(Bundle bundle) {
        this.init(); //[3]
        if (b.a() || b.b() || b.c()) {
            this.a("Root detected!");
        }
        if (a.a((Context)this.getApplicationContext())) {
            this.a("App is debuggable!");
        }
        new /* Unavailable Anonymous Inner Class!! */.execute((Object[])new Void[]{null, null, null});
        this.m = new CodeCheck();
        super.onCreate(bundle);
        this.setContentView(2130968603);
    }

    public void verify(View view) {
        String string = ((EditText)this.findViewById(2131427422)).getText().toString();
        AlertDialog alertDialog = new AlertDialog.Builder((Context)this).create();
        if (this.m.a(string)) {
            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 /* Unavailable Anonymous Inner Class!! */);
        alertDialog.show();
    }
}

Можно заметить staticблок с вызовом System.load для загрузки библиотеки foo. Приложение также вызывает this.init() в первой строке метода onCreate, который объявлен как native метод, так, возможно это часть foo.

Теперь, рассмотрим библиотеку foo. В radre2 откроем библиотеку (вы можете найти ее скомпилированной под различные архитектуры в папке lib, здесь я использую файл из lib/x86_64), посмотрим на функции, которые она экспортирует:

michael@sixtyseven:~/Development/UnCrackable2/lib/x86_64$ r2 libfoo.so 
 -- Don't look at the code. Don't look.
[0x000007a0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[ ] [*] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan))

[0x000007a0]> iE
[Exports]
vaddr=0x00001060 paddr=0x00001060 ord=004 fwd=NONE sz=183 bind=GLOBAL type=FUNC name=Java_sg_vantagepoint_uncrackable2_CodeCheck_bar
vaddr=0x00001050 paddr=0x00001050 ord=006 fwd=NONE sz=15 bind=GLOBAL type=FUNC name=Java_sg_vantagepoint_uncrackable2_MainActivity_init
vaddr=0x00004008 paddr=0x00003008 ord=014 fwd=NONE sz=0 bind=GLOBAL type=NOTYPE name=__bss_start
vaddr=0x00004008 paddr=0x00003008 ord=015 fwd=NONE sz=0 bind=GLOBAL type=NOTYPE name=__bss_start
vaddr=0x0000400d paddr=0x0000400d ord=016 fwd=NONE sz=0 bind=GLOBAL type=NOTYPE name=_end

5 exports

[0x000007a0]> 

Мы можем заметить, что библиотека экспортирует две интересные функции: Java_sg_vantagepoint_uncrackable2_MainActivity_init и Java_sg_vantagepoint_uncrackable2_CodeCheck_bar (если вам интересно, почему методы названы именно так, ознакомьтесь с Java Native Interface). Посмотрим на Java_sg_vantagepoint_uncrackable2_MainActivity_init:

[0x000007a0]> s 0x00001050
[0x00001050]> V
It is a rather short function:
Это довольно короткая функция:
[0x00001050 29% 848 libfoo.so]> pd $r @ sym.Java_sg_vantagepoint_uncrackable2_MainActivity_init                                                                                                     
/ (fcn) sym.Java_sg_vantagepoint_uncrackable2_MainActivity_init 15                                                                                                                                  
|   sym.Java_sg_vantagepoint_uncrackable2_MainActivity_init ();                                                                                                                                     
|           0x00001050      50             push rax                                                                                                                                                 
|           0x00001051      e8caf7ffff     call sub.fork_820           ;[1]                                                                                                                         
|           0x00001056      c605af2f0000.  mov byte [0x0000400c], 1    ; [0x400c:1]=58 ; ": (GNU) 4.9.x 20150123 (prerelease)"                                                                      
|           0x0000105d      58             pop rax                                                                                                                                                  
\           0x0000105e      c3             ret                                                                                                                                                      
            0x0000105f      90             nop                      

Здесь вызывается другая функция sub.fork_820, в которой гораздо больше кода:

[0x00000820 14% 265 libfoo.so]> pd $r @ sub.fork_820                                                                                                                                                
/ (fcn) sub.fork_820 242                                                                                                                                                                            
|   sub.fork_820 ();                                                                                                                                                                                
|           ; var int local_8h @ rsp+0x8                                                                                                                                                            
|           ; var int local_10h @ rsp+0x10                                                                                                                                                          
|              ; CALL XREF from 0x00001051 (sym.Java_sg_vantagepoint_uncrackable2_MainActivity_init)                                                                                                
|           0x00000820      4156           push r14                                                                                                                                                 
|           0x00000822      53             push rbx                                                                                                                                                 
|           0x00000823      4883ec18       sub rsp, 0x18                                                                                                                                            
|           0x00000827      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=0x3180 ; '('                                                                                                      
|           0x00000830      4889442410     mov qword [local_10h], rax                                                                                                                               
|           0x00000835      e806ffffff     call sym.imp.fork           ;[1]                                                                                                                         
|           0x0000083a      8905c8370000   mov dword loc.__bss_start, eax ; [0x4008:4]=0x43434700 ; loc.__bss_start                                                                                 
|           0x00000840      85c0           test eax, eax                                                                                                                                            
|       ,=< 0x00000842      741a           je 0x85e                    ;[2]                                                                                                                         
|       |   0x00000844      488d15a5ffff.  lea rdx, 0x000007f0         ; 0x7f0                                                                                                                      
|       |   0x0000084b      488d7c2408     lea rdi, [local_8h]         ; 0x8                                                                                                                        
|       |   0x00000850      31f6           xor esi, esi                                                                                                                                             
|       |   0x00000852      31c9           xor ecx, ecx                                                                                                                                             
|       |   0x00000854      e8f7feffff     call sym.imp.pthread_create ;[3]; ssize_t read(int fildes, void *buf, size_t nbyte)                                                                      
|      ,==< 0x00000859      e990000000     jmp 0x8ee                   ;[4]                                                                                                                         
|      ||      ; JMP XREF from 0x00000842 (sub.fork_820)                                                                                                                                            
|      |`-> 0x0000085e      e8fdfeffff     call sym.imp.getppid        ;[5]                                                                                                                         
|      |    0x00000863      89c3           mov ebx, eax                                                                                                                                             
|      |    0x00000865      bf10000000     mov edi, 0x10                                                                                                                                            
|      |    0x0000086a      31d2           xor edx, edx                                                                                                                                             
|      |    0x0000086c      31c9           xor ecx, ecx                                                                                                                                             
|      |    0x0000086e      31c0           xor eax, eax                                                                                                                                             
|      |    0x00000870      89de           mov esi, ebx                                                                                                                                             
|      |    0x00000872      e8f9feffff     call sym.imp.ptrace         ;[6]                                                                                                                         
|      |    0x00000877      4885c0         test rax, rax                                                                                                                                            
|      |,=< 0x0000087a      7572           jne 0x8ee                   ;[4]                                                                                                                         
|      ||   0x0000087c      4c8d742408     lea r14, [local_8h]         ; 0x8                                                                                                                        
|      ||   0x00000881      31d2           xor edx, edx                                                                                                                                             
|      ||   0x00000883      89df           mov edi, ebx                                                                                                                                             
|      ||   0x00000885      4c89f6         mov rsi, r14                                                                                                                                             
|      ||   0x00000888      e883feffff     call sym.imp.waitpid        ;[7]         

Мы видим вызовы frok, а затем pthread_create, getppid, ptrace и waitpid. Не тратя слишком много времени на дизассемлированный листинг мы можем догадаться, что задесь происходит, а именно основной процесс порождает дочерний, который уже присоединяется к родительскому, как отладчик используя ptrace. Это базовая технология отладки, вы можете прочитать об этом здесь.

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

Избавляемся от Анти-Дебага: Frida

Frida спешит на помощь. Вместо того, что бы внедрять Frida в уже запущенный процесс, мы можем позволить Frida начать процесс за нас. Используя параметр -f мы сообщаем Frida внедриться в Zygote и запустить приложение. Закройте открытое приложение на устройстве и посмотрите, что произойдет когда мы запустим Frida:

frida -U -f sg.vantagepoint.uncrackable2

Мы получим:

michael@sixtyseven:~/Development/UnCrackable2/lib/x86_64$ frida -U -f sg.vantagepoint.uncrackable2 --no-pause
     ____
    / _  |   Frida 9.1.27 - 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/
Spawned `sg.vantagepoint.uncrackable2`. Resuming main thread!           
[USB::Android Emulator 5554::['sg.vantagepoint.uncrackable2']]-> 

Ура! Frida внедряется в Zygote, порождает нужный процесс и ожидает дальнейшего ввода. (Я признаю, что это было достаточно длинное введение, что бы познакомить вас с опцией -f, но теперь вы знаете как это использовать)

Теперь мы готовы идти дальше. Но прежде чем мы продолжим я предлагаю вам посмотреть на другое возможное решение для преодоления защиты от отладки.

Избавляемся от Анти-Дебага альтернативное решение: Патчинг

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

Исправлять приложение нам поможет apktool:

michael@sixtyseven:~/Disassembly$ /opt/apktool/apktool.sh -r d UnCrackable-Level2.apk 
I: Using Apktool 2.2.0 on UnCrackable-Level2.apk
I: Copying raw resources...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

(Я пропустил извлечение ресурсов передав аргумент -r, из-за того что это вызывало проблемы его обратного построения. В любом случае, нам не нужны ресурсы.)

Посмотрите на smali код в smali/sg/vantagepoint/uncrackable2/MainActivity.smali. Вы можете найти вызов метода init на 82 строке и закомментировать его.

# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 4

    const/4 v3, 0x0

#   invoke-direct {p0}, Lsg/vantagepoint/uncrackable2/MainActivity;->init()V

    invoke-static {}, Lsg/vantagepoint/a/b;->a()Z

Recompile the apk (ignoring the fatal error…):

Перекомпилируйте APK (игнорируя фатальную ошибку):

michael@sixtyseven:~/Disassembly/UnCrackable-Level2$ /opt/apktool/apktool.sh b
I: Using Apktool 2.2.0
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
[Fatal Error] AndroidManifest.xml:1:1: Content ist nicht zulässig in Prolog.
I: Checking whether resources has changed...
I: Copying raw resources...
I: Copying libs... (/lib)
I: Building apk file...
I: Copying unknown files/dir...

Сожмем его:

michael@sixtyseven:~/Disassembly/UnCrackable-Level2$ zipalign -v 4 dist/UnCrackable-Level2.apk  UnCrackable2.recompiled.aligned.apk
Verifying alignment of UnCrackable2.recompiled.aligned.apk (4)...
      49 AndroidManifest.xml (OK - compressed)
     914 classes.dex (OK - compressed)
  269899 lib/arm64-v8a/libfoo.so (OK - compressed)
  273297 lib/armeabi-v7a/libfoo.so (OK - compressed)
  279346 lib/armeabi/libfoo.so (OK - compressed)

И подпишем (примечание: вы должны иметь ключ и keystore для этого):

michael@sixtyseven:~/Disassembly/UnCrackable-Level2$ jarsigner -verbose -keystore ~/.android/debug.keystore  UnCrackable2.recompiled.aligned.apk signkey
Enter Passphrase for keystore: 
   adding: META-INF/MANIFEST.MF
   adding: META-INF/SIGNKEY.SF
   adding: META-INF/SIGNKEY.RSA
  signing: AndroidManifest.xml
  signing: classes.dex
  signing: lib/arm64-v8a/libfoo.so
  signing: lib/armeabi-v7a/libfoo.so
  signing: lib/armeabi/libfoo.so
  signing: lib/mips/libfoo.so
[...]

Более подробные инструкции вы можете получить прочитав соответствующий раздел в OWASP Mobile Security Testing Guide. Удалите оригинальное приложение и установите модифицированное:

adb uninstall sg.vantagepoint.uncrackable2
adb install UnCrackable2.recompiled.aligned.apk

Запустите приложение снова. Теперь, мы видим только один процесс в frida-ps

29996  sg.vantagepoint.uncrackable2

И теперь, Frida подключается без каких-либо проблем:

michael@sixtyseven:~/Disassembly/UnCrackable-Level2$ frida -U sg.vantagepoint.uncrackable2
     ____
    / _  |   Frida 9.1.27 - 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/
                                                                                
[USB::Android Emulator 5554::sg.vantagepoint.uncrackable2]-> 

Это немного утомительнее, чем просто добавить опцию -f при запуске Frida, но зато более универсально.

Как уже отмечалось ранее, позже мы не сможем легко извлечь секретную строку, если будем использовать модифицированную версию (хотя я покажу вам, как это обойти). Но пока мы будем использовать оригинальный APK файл. Убедитесь, что у вас установлен оригинальный APK, для продолжения дальше.

Продолжение головоломки

После того, как мы нашли возможности избавиться от анти-отладки мы можем идти дальше. Приложение обнаруживает root на устройстве и закрывается как только мы нажимаем кнопку “ОК”. Мы наблюдали ровно такое же поведение в UnCrackable1. Мы опять могли бы решить проблему пропатчив приложение удалив вызов System.exit, но на этот раз мы воспользуемся Frida. Посмотрев на декомпилированный листинг снова мы не обнаруживаем отдельный класс, реализующий OnClickListener, только автоматически сгенерированный анонимный класс. Поскольку реализация onClickListener вызывает System.exit мы можем легко перехватить вызов и сделать его бесполезным.

Следующий скрипт Frida сделает это:

setImmediate(function() {
    console.log("[*] Starting script");
    Java.perform(function() {
        exitClass = Java.use("java.lang.System");
        exitClass.exit.implementation = function() {
            console.log("[*] System.exit called");
        }
        console.log("[*] Hooking calls to System.exit");
    });
});

Снова закройте запущенный UnCrackable2 и запустите его посредством Frida:

frida -U -f sg.vantagepoint.uncrackable2 -l uncrackable2.js --no-pause 

Подождите пока приложение запуститься и Frida отобразит строку Hooking calls…. После чего нажмите “OK”. Вы должны получить, что-то вроде этого:

michael@sixtyseven:~/Development/frida$ frida -U -f sg.vantagepoint.uncrackable2 --no-pause -l uncrackable2.js 
     ____
    / _  |   Frida 9.1.27 - 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/
Spawned `sg.vantagepoint.uncrackable2`. Resuming main thread!           
[USB::Android Emulator 5554::['sg.vantagepoint.uncrackable2']]-> [*] Hooking calls to System.exit

[*] System.exit called

Приложение больше не закрывается. Мы можем вводить секретную строку:

Но что вводить? Посмотрим на Java код в MainActivity, который проверяет корректность ввода:

this.m = new CodeCheck();

[...]

//in method: public void verify
if (this.m.a(string)) {
            alertDialog.setTitle((CharSequence)"Success!");
            alertDialog.setMessage((CharSequence)"This is the correct secret.");
}

Это листинг класса CodeCheck:

package sg.vantagepoint.uncrackable2;

public class CodeCheck {
    private native boolean bar(byte[] var1);

    public boolean a(String string) {
        return this.bar(string.getBytes()); //Call to a native function
    }
}

Можно заметить, что наш ввод в текстовое поле передается в нативую функцию, с названием bar. Опять же, мы можем найти эту функцию в библиотеки libfoo.so. Найдите адрес функции (так же как мы это делали с init) и дизассемблируйте ее с помощью radare2.

Рассматривая дизассемблированный код мы можем заметить сравнение строк и интересную читаемую сроку Thanks for all t. Пробуем использовать ее в качестве секретного кода, но она не подходит. Нужно продолжать искать.

Посмотрим на код по адресу 0x000010d8:

0x000010d8      83f817         cmp eax, 0x17                                                                                                                                            
0x000010db      7519           jne 0x10f6                  ;[1]  

видим сравнение регистра eaxс 0x17, что равно 23 в десятичной системе счисления. Если сравнение не удалось, strncmp не будет вызываться дальше. Мы так же замечаем, что 0x17, это один из параметров strncmp.

0x000010e1      ba17000000     mov edx, 0x17

Помните, что согласно 64-битному соглашению о вызовах в Linux, в регистрах передаются параметры функции, по крайне мере с 1 по 6. В частности первые три параметра передаются в RDI, RSI and RDX в этом порядке (смотрите здесь). Заголовок функции strncmp:

int strncmp ( const char * str1, const char * str2, size_t num );

Поэтому, наша strncmp будет сравнивать 0x17 = 23 символа. Следовательно, не трудно догадаться, что секретная строка имеет длину 23 символа.

Давайте, наконец попробуем перехватить функцию strncmp и просто вывести ее аргументы. Мы ожидаем получить расшифрованную строку. Мы должны

  1. Найти адрес в памяти функции strncmp в libfoo.so
  2. Перехватить функцию strncmp в libfoo.so с Interceptor.attach и получить параметры функции

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

var strncmp = undefined;
imports = Module.enumerateImportsSync("libfoo.so");

for(i = 0; i < imports.length; i++) {
if(imports[i].name == "strncmp") {
        strncmp = imports[i].address;
        break;
    }

}

Interceptor.attach(strncmp, {
            onEnter: function (args) {
               if(args[2].toInt32() == 23 && Memory.readUtf8String(args[0],23) == "01234567890123456789012") {
                    console.log("[*] Secret string at " + args[1] + ": " + Memory.readUtf8String(args[1],23));
               }
            }
});

Несколько замечаний по этому скрипту:

  1. Этот скрипт вызывает Module.enumerateImportsSync для получения массива объектов импортируемых libfoo.so (посмотрите документацию). Мы проходим по массиву, пока не найдем strncmp и не получим его адрес. Затем мы присоединяемся по адресу перехватчиком (Interceptor).
  2. Строки в Java не оканчиваются нулевым байтом. Когда мы обращаемся по адресу в памяти используя указатель на строку переданный в strncmp с помощью метода Memory.readUtf8String и не указываем длину Frida продолжит чтение до тех пор пока не встретит \0 или выведет часть памяти не являющейся строкой. Нельзя точно сказать, где оканчивается строка. Если мы передадим точное значение длины мы сможем преодолеть это.
  3. Если не указать другие ограничения на получение аргументов strncmp мы получим множество результатов от вызовов, которые нам не нужны. Мы указываем, что будем выводить аргумент функции только если третий аргумент size_t к strncmp равен 23 и когда первый аргумент указывает на строку 01234567890123456789012, которую мы будем вводить в поле ввода (которая, как вы можете заметить, длиной 23 символа)

Откуда я знаю, что args[0] указывает на введенную нами строку, а args[1] на секретную строку? А я не знал этого, пока не проверил это, я сделал вывод всех аргументов и обнаружил введенную мной строку в одном из них. Если вы не хотите пропускать данный шаг вы можете удалить конструкцию if в коде выше и использовать функцию hexdump и вывод Frida:

buf = Memory.readByteArray(args[0],32);
console.log(hexdump(buf, {
     offset: 0,
     length: 32,
     header: true,
     ansi: true
}));

buf = Memory.readByteArray(args[1],32);
console.log(hexdump(buf, {
    offset: 0,
    length: 32,
    header: true,
   ansi: true
}));

Это выводит много информации, после каждого вызова strncmp, я вас предупредил.

Здесь полная версия скрипта, который выводит параметры достаточно хорошо:

setImmediate(function() {
    Java.perform(function() {
        console.log("[*] Hooking calls to System.exit");
        exitClass = Java.use("java.lang.System");
        exitClass.exit.implementation = function() {
            console.log("[*] System.exit called");
        }

        var strncmp = undefined;
        imports = Module.enumerateImportsSync("libfoo.so");

        for(i = 0; i < imports.length; i++) {
        if(imports[i].name == "strncmp") {
                strncmp = imports[i].address;
                break;
            }

        }

        Interceptor.attach(strncmp, {
            onEnter: function (args) {
               if(args[2].toInt32() == 23 && Memory.readUtf8String(args[0],23) == "01234567890123456789012") {
                    console.log("[*] Secret string at " + args[1] + ": " + Memory.readUtf8String(args[1],23));
                }
             },
        });
        console.log("[*] Intercepting strncmp");
    });
});

Сейчас запустим Frida и загрузим скрипт:

frida -U -f sg.vantagepoint.uncrackable2 --no-pause -l uncrackable2.js

Введите строку и нажмите проверить:

В консоли вы получите:

michael@sixtyseven:~/Development/frida$ frida -U -f sg.vantagepoint.uncrackable2 --no-pause -l uncrackable2.js 
     ____
    / _  |   Frida 9.1.27 - 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/
Spawned `sg.vantagepoint.uncrackable2`. Resuming main thread! 
          
[USB::Android Emulator 5554::['sg.vantagepoint.uncrackable2']]-> [*] Hooking calls to System.exit

[*] Intercepting strncmp

[*] System.exit called

[*] Secret string at 0x7fffa628f010: Thanks for all the fish

Так красиво и просто: это нужная нам секретная строка. Наберите ее в проверяемом поле и наслаждайтесь полученным сообщением:

Исправление решения патчингом

Наконец, некоторое замечание о модификации и почему мы не получили бы секрет при использовании исправленного APK. Дело в том, что функция init из libfoo содержит некоторую логику инициализации, которая предотвращает успешное прохождение проверки строки без запуска функции init.

Если мы посмотрим на дизассеблированный код функции init мы увидим интересную строку:

0x00001056      c605af2f0000.  mov byte [0x0000400c], 1 

Эта же переменная проверяется позже, внутри функции bar, и если переменная не установлена дальнейших проверок не происходит, следовательно до strncmp очередь не доходит:

0x0000107d      803d882f0000.  cmp byte [0x0000400c], 1    ; [0x1:1]=69                                                                                                                 
0x00001084      7570           jne 0x10f6                  ;[1]       

Таким образом, вероятно, это некоторая логическая переменная, которая устанавливается, если запускается функция init. Если мы хотим, чтобы исправленная версия нашего apk вызывала, strncmp нам нужно установить эту переменную или, по крайней мере, избавиться от прыжка через вызову strncmp после неудачной проверки введенной строки.

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

Нам необходимо:

  1. Получить базовый адресс загруженной библиотеки foo
  2. Найти переменную по ее относительному адресу (мы знаем, что ее смещение от базового адреса составляет 0x400C, что мы узнали из дизассеблирования)
  3. Установить значение 1

В Frida это выглядит примерно так:

//Get base address of library
var libfoo = Module.findBaseAddress("libfoo.so");

//Calculate address of variable
var initialized = libfoo.add(ptr("0x400C"));

//Write 1 to the variable
Memory.writeInt(initialized,1);

Вот полный скрипт для модифицированной версии приложения:

setImmediate(function() {
    Java.perform(function() {
        console.log("[*] Hooking calls to System.exit");
        exitClass = Java.use("java.lang.System");
        exitClass.exit.implementation = function() {
            console.log("[*] System.exit called");
        }

        var strncmp = undefined;
        imports = Module.enumerateImportsSync("libfoo.so");

        for(i = 0; i < imports.length; i++) {
            if(imports[i].name == "strncmp") {
                strncmp = imports[i].address;
                break;
            }

        }

        //Get base address of library
        var libfoo = Module.findBaseAddress("libfoo.so");

        //Calculate address of variable
        var initialized = libfoo.add(ptr("0x400C"));

        //Write 1 to the variable
        Memory.writeInt(initialized,1);

        Interceptor.attach(strncmp, {
            onEnter: function (args) {
               if(args[2].toInt32() == 23 && Memory.readUtf8String(args[0],23) == "01234567890123456789012") {
                    console.log("[*] Secret string at " + args[1] + ": " + Memory.readUtf8String(args[1],23));
                }
             },
        });
        console.log("[*] Intercepting strncmp");
    });
});

Теперь запустите приложение, загрузите скрипт через Frida и снова введите волшебное 01234567890123456789012. Нажмите кнопку проверки. Приложение вызывает strncmp и мы можем увидеть секретную строку:

root@sixtyseven:/home/michael/Development/frida# frida -U sg.vantagepoint.uncrackable2 -l uncrackable2-final.js 
     ____
    / _  |   Frida 9.1.27 - 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/
                                                                                
[USB::Android Emulator 5554::sg.vantagepoint.uncrackable2]-> [*] Hooking calls to System.exit

[*] Intercepting strncmp

[*] System.exit called

[*] Secret string at 0x7fffd52c6570: Thanks for all the fish

Надеюсь вам понравилось использовать Frida.

Спасибо @oleavr за исправление моей ошибки и за совет о том как следует работать с указателями в Frida.

© Translated by norver special for r0 Crew
Уроки взлома Adnroid приложений с помощью Frida