Android APP破解利器Frida之反调试对抗

简介: 本文讲的是Android APP破解利器Frida之反调试对抗,在我发表了关于Frida的第二个博文后不久,@muellerberndt决定发布另外一个新的OWASP Android 破解APP,我很想知道我是否可以再次使用Frida解决这个CrackMe。如果你想跟随我一起操作,你需要做以下准备:
本文讲的是 Android APP破解利器Frida之反调试对抗在我发表了关于Frida的第二个博文后不久, @muellerberndt 决定发布另外一个新的OWASP Android  破解 APP,我很想知道我是否可以再次使用Frida解决这个CrackMe。如果你想跟随我一起操作,你需要做以下准备:

· OWASP Level2的CrackMe APK

· Android SDK和模拟器(我使用的是Android 7.1 x64映像)

· Frida安装(以及frida-server二进制文件

· ByteCode viewer

· radare2(或你喜欢使用的其他一些反汇编程序)

· apktool

如果你需要知道如何安装Frida,请查看Frida的相关 文档。对于Frida的使用,你还可以查看本教程系列的第I部分。我想你在继续本文的操作之前已经拥有所有需要的东西,并且基本上熟悉了Frida的使用。另外,确保Frida可以连接到你的设备或模拟器(例如使用frida -ps -U 命令)。

警告:本教程不仅仅是一个快速破解Crackme的练习。相反,我将向你展示各种方法来克服所遇到的具体问题。如果你只是寻找一个快速的破解方案,请在本教程末尾查看相关的Frida脚本。

注意:如果你遇到了下面这个错误:

Error: access violation accessing 0xebad8082

或者使用Frida时会出现了类似的错误,再模拟器中擦除用户数据,重新启动并重新安装该apk可能有助于解决此类错误问题。

做好多次尝试的准备。该应用程序可能会崩溃,模拟器也可能会重新启动,一切也可能会搞砸,但是,它依旧可以正常工作。

先把APP跑起来

在开始时,我们做的事情和之前破解UnCrackable1是一样的,先运行应用程序:接下来,当你在模拟器中运行它时,它会检测到它是在有root权限的设备上运行的。

Android APP破解利器Frida之反调试对抗

我们可能会尝试像之前破解UnCrackable 1一样Hook OnClickListener。但首先我们来看看我们是否可以连接到Frida来进行篡改:

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 -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

看起来它并不起作用,因为当我们使用root权限运行Frida时,我们得到了相同的结果,所以提出的解决方案并没有什么帮助。这里到底发生了什么?我们来看看应用程序吧。解压缩apk并使用ByteCodeViewer 查看classes.dex并进行反编译(例如CFR-Decompiler):

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库(参见[1])。该应用程序还在其onCreate方法的第一行调用了this.init()(参见[3]),该方法被声明为一种native方法(参见[2]),因此它可能是foo的一部分。

现在让我们来看看foo库。在radare2中,打开这个库文件(你可以在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]>

我们注意到,该库出口2个有趣的导出函数:

Java_sg_vantagepoint_uncrackable2_MainActivity_init和Java_sg_vantagepoint_uncrackable2_CodeCheck_bar

(对这些方法的具体命名可以查看Java接口NATIV JNI)。

我们来看看Java_sg_vantagepoint_uncrackable2_MainActivity_init:

[0x000007a0]> s 0x00001050
[0x00001050]> V

这是一个相当短的函数:

[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]

我们看到有调用fork,pthread_create,getppid,ptrace和waitpid。不需要花费太多的时间来进行反汇编,我们就可以猜测出,主进程使用ptrace作为调试器来fork了一个附加到它的子进程。这是一个基本的常见的反调试技术,你可以在这里阅读到更多信息。

由于Frida 使用 ptrace用于初始的注入操作,所以这就解释了为什么我们没有成果连接到父进程:附加调试进程被阻止,因为已经有其他的进程作为调试器进行了连接。

绕过反调试解决方案1: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,产生了我们的进程并等待输入。(我承认,告诉你在启动Frida时添加-f选项是一个很长的介绍,不过你已被警告过…)

我们现在准备继续破解, 但是在我们继续之前,我们将要检查另一个解决方案来克服这个破解的调试保护。

绕过反调试解决方案2:补丁

除了让Frida生成进程之外,我们还可以通过对应用打补丁来解决我们遇到的问题。这意味着我们需要反汇编应用程序,对修改的apk进行重建和签名。但是,在这种情况下,这将会给我们麻烦带来麻烦。不过我还是会告诉你如何解决这个麻烦,我们稍后会注意到这个问题。

我们可以通过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参数在重新编译apk时引起了新的问题,我们在这里不需要APP的资源。)

看一下smali/sg/vantagepoint/uncrackable2/MainActivity.smali 文件里的smali代码。你可以在82行处找到调用init的代码,并且可以将其注释掉:

# 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

重新编译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)

签名(注意:你需要有一个密钥和密钥库):

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移动安全测试指南”中找到更广泛的描述。卸载掉原始的apk并安装打过补丁的apk:

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]->

比起给Frida增加 -r选项更为繁琐一点,但也更普遍的适用。

如前所述,当我们使用补丁版本后(我会告诉你如何克服这个,所以不要把它丢弃),我们就不能轻易地提取这个秘密字符串了。但是现在我们继续使用原来的apk。确保你按照正确的方式安装了原始的apk。

继续解惑

在我们找到了一些摆脱反调试的可能性之后,我们来看看我们接下来该如何进行。一旦我们按下OK按钮,应用程序就会在模拟器的运行时中进行root权限的检测并退出。我们已经知道UnCrackable1的一些行为。另外,我们可以对这种检测行为打补丁,删除System.exit的调用代码,但是我们试图用Frida来解决这个问题。再看看反编译的代码,我们看到没有OnClickListener类,只是一个匿名的内部类。由于onClickListener的实现中调用了System.exit所以我们简单地Hook住该函数并无卵用。

这是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

等到App启动后,Frida 在控制台中显示了Hooking calls…的消息。然后按“确定”。你应该会得到这样的输出信息:

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

该应用程序不再退出。我们可以输入一个秘密字符串试试:

Android APP破解利器Frida之反调试对抗

但是我们应该在这里输入什么呢?看看MainActivity的Android代码,检查正确的输入逻辑如下:

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
    }
}

我们注意到,我们的文本字段的输入也就是我们的“秘密字符串” 被传递给一个native函数bar。我们可以在libfoo.so库中找到这个函数。搜索函数地址(如我们之前使用的init函数),并用radare2进行反汇编:

Android APP破解利器Frida之反调试对抗

看看反汇编代码,我们注意到有一些字符串比较的逻辑,另外我们还注意到一个有趣的明文字符串Thanks for all t.测试这个字符串作为我们的crackme的解决方案后,我们发现它并不起作用。我们必须继续向前。

看看0x000010d8这个地址处的反汇编代码:

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

这里有一个eax寄存器与0x17的比较,其十进制格式为23。如果这个比较不成功,strncmp在这里就不会调用。我们也注意到了0x17已作为参数传递给了在

0x000010e1 ba17000000 mov edx, 0x17 处的strncmp。

记住,根据linux 64位的调用约定,函数参数在寄存器中传递至少1到6个参数。尤其是前3个参数要按照这个顺序在RDI,RSI和RDX中传递(参见[PDF],第20页)。Strncmp函数声明如下:

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

所以我们的strncmp函数将比较0x17 = 23个字符。我们可以推断,我们的秘密字符串的长度应该是23个字符。

让我们最后尝试Hook住strncmp函数,并简单地打印出它的参数。我们可以期望这里给我们直接输出解密的输入字符串。我们必须

1.    查找strncmp函数在libfoo.so中的内存地址

2.    使用 Interceptor.attach Hook libfoo.so的strncmp函数和转储参数

如果你这样做了,你会发现有很多地方调用了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并检索其地址。然后我们将拦截器附加到它上面。

2.  Java中的字符串不会已空字符终止。当我们使用Frida的Memory.readUtf8String方法访问strncmp字符串指针的内存位置,并且不提供长度时,Frida会期望一个终止符,否则会输出一些内存垃圾。因为它不知道字符串在哪里结束。如果我们指定要读取的字符数量作为第二个参数,我们就可以解决掉这个警告。

3.  如果我们没有限制我们转储strncmp参数的条件,我们会得到很多输出。所以我们只输出strncmp中当第三个参数的参数size_t为23且第一参数的字符串指针指向我们在输入框输入的01234567890123456789012这个字符串作为过滤条件

我是如何知道args[0]也就是我们的输入和args[1]也就是秘密字符串的?我其实并不知道,我只是在测试它,并将大量的输出转储到屏幕上,以找到我的输入。如果你不想跳过,你可以在上述脚本中移除if语句,并使用Frida的hexdump输出:

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时都会输出很多hexdumps,所以要提醒你一下。

以下是完整版本的脚本,可以更好地输出参数:

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

输入字符串并按验证:

Android APP破解利器Frida之反调试对抗

在控制台,你会得到:

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

没错,输出内容很好很简单:完整的秘密字符串。将其输入到输入框中并享受成功吧

Android APP破解利器Frida之反调试对抗

修复补丁的解决方案

最后,是有一些关于补丁修复的说明,以及为什么我们在使用打过补丁的apk时不会得到秘密字符串。Libfoo中的init函数包含一些初始化逻辑,阻止应用程序根据我们的输入检查或解码秘密字符串。

如果我们再次看看init函数的反汇编,我们会看到有趣的一行代码:

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

在bar函数的后面会检查相同的变量libfoo,如果未设置,则代码会跳过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后能体验到更多的乐趣。




原文发布时间为:2017年5月11日
本文作者:李白
本文来自云栖社区合作伙伴嘶吼,了解相关信息可以关注嘶吼网站。
目录
相关文章
|
1月前
|
XML Java 数据库
安卓项目:app注册/登录界面设计
本文介绍了如何设计一个Android应用的注册/登录界面,包括布局文件的创建、登录和注册逻辑的实现,以及运行效果的展示。
140 0
安卓项目:app注册/登录界面设计
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
123 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
2月前
|
存储 开发工具 Android开发
使用.NET MAUI开发第一个安卓APP
【9月更文挑战第24天】使用.NET MAUI开发首个安卓APP需完成以下步骤:首先,安装Visual Studio 2022并勾选“.NET Multi-platform App UI development”工作负载;接着,安装Android SDK。然后,创建新项目时选择“.NET Multi-platform App (MAUI)”模板,并仅针对Android平台进行配置。了解项目结构,包括`.csproj`配置文件、`Properties`配置文件夹、平台特定代码及共享代码等。
163 2
|
2月前
|
XML Android开发 数据格式
🌐Android国际化与本地化全攻略!让你的App走遍全球无障碍!🌍
在全球化背景下,实现Android应用的国际化与本地化至关重要。本文以一款旅游指南App为例,详细介绍如何通过资源文件拆分与命名、适配布局与方向、处理日期时间及货币格式、考虑文化习俗等步骤,完成多语言支持和本地化调整。通过邀请用户测试并收集反馈,确保应用能无缝融入不同市场,提升用户体验与满意度。
103 3
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
69 10
|
1月前
|
移动开发 前端开发 安全
uni-app跨域调试你学会了没
uni-app跨域调试你学会了没
47 0
|
1月前
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
224 0
|
2月前
|
XML 数据库 Android开发
10分钟手把手教你用Android手撸一个简易的个人记账App
该文章提供了使用Android Studio从零开始创建一个简单的个人记账应用的详细步骤,包括项目搭建、界面设计、数据库处理及各功能模块的实现方法。
|
3月前
|
API Android开发
Android P 性能优化:创建APP进程白名单,杀死白名单之外的进程
本文介绍了在Android P系统中通过创建应用进程白名单并杀死白名单之外的进程来优化性能的方法,包括设置权限、获取运行中的APP列表、配置白名单以及在应用启动时杀死非白名单进程的代码实现。
62 1
|
3月前
|
域名解析 网络协议 网络安全
【App Service】遇见本地访问Azure App Service应用慢或者是调用第三方接口慢的调试小工具
【App Service】遇见本地访问Azure App Service应用慢或者是调用第三方接口慢的调试小工具

热门文章

最新文章