CTF之安卓逆向uncrackme level1

安卓逆向方面好像没有类似vulnhub的网站,有大量的靶机练习渗透测试。目前我找到了XCTF有逆向和移动端的题目,和 OWASP MSTG CTF项目。先从MSTG开始做完吧!

CTF项目的github在点击这里。不过由于项目较大,github经常会出现下载失败的情况,像我就下载了一个上午,50m的带宽下载速度只有20kb/s,速度极其感人。不过我已经上传到CSDN了,有需要的小伙伴可以直接下载

运行

将UnCrackable-Level1.apk安装到安卓设备。
adb install UnCrackable-Level1.apk

打开运行,存在root检测,由于我的模拟器是已经root了的,所以点击OK,程序它就自己退出了。
在这里插入图片描述
有两种方法可以解决这个问题:

  • 关掉root权限
  • 绕过root检测

第一种方法显然不符合我气质,那么就开始进行检测绕过。

反编译静态分析

我使用jeb进行反编译,编译后是smali汇编代码,按tab可以将smali汇编转成Java伪代码。

.uncrackable1.MainActivity代码部分

package sg.vantagepoint.uncrackable1;

import android.app.Activity;
import android.app.AlertDialog$Builder;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface$OnClickListener;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import sg.vantagepoint.a.b;
import sg.vantagepoint.a.c;

public class MainActivity extends Activity {
    public MainActivity() {
        super();
    }

    private void a(String arg4) {
        AlertDialog v0 = new AlertDialog$Builder(((Context)this)).create();
        v0.setTitle(((CharSequence)arg4));
        v0.setMessage("This is unacceptable. The app is now going to exit.");
        v0.setButton(-3, "OK", new DialogInterface$OnClickListener() {
            public void onClick(DialogInterface arg1, int arg2) {
                System.exit(0);
            }
        });
        v0.setCancelable(false);
        v0.show();
    }

    protected void onCreate(Bundle arg2) {
        if((c.a()) || (c.b()) || (c.c())) {
            this.a("Root detected!");
        }

        if(b.a(this.getApplicationContext())) {
            this.a("App is debuggable!");
        }

        super.onCreate(arg2);
        this.setContentView(0x7F030000);
    }

    public void verify(View arg4) {
        String v4 = this.findViewById(0x7F020001).getText().toString();
        AlertDialog v0 = new AlertDialog$Builder(((Context)this)).create();
        if(a.a(v4)) {
            v0.setTitle("Success!");
            v4 = "This is the correct secret.";
        }
        else {
            v0.setTitle("Nope...");
            v4 = "That\'s not it. Try again.";
        }

        v0.setMessage(((CharSequence)v4));
        v0.setButton(-3, "OK", new DialogInterface$OnClickListener() {
            public void onClick(DialogInterface arg1, int arg2) {
                arg1.dismiss();
            }
        });
        v0.show();
    }
}

程序首先运行onCreate函数,从onCreate一段一段分析看它干了什么。
首先,如果c类中的a,b,c方法中有一个条件满足,那么它就会进入MainActivity类的a方法,并传入"Root detected!"字符串。

这就是程序一开始打开时的情况,所以我们先看看c类中的a、b、c方法和MainActivity.a方法分别做了些啥。
在这里插入图片描述

a类的代码如下:

package sg.vantagepoint.a;

import android.os.Build;
import java.io.File;

public class c {
    public static boolean a() {
        String[] v0 = System.getenv("PATH").split(":");
        int v1 = v0.length;
        int v3;
        for(v3 = 0; v3 < v1; ++v3) {
            if(new File(v0[v3], "su").exists()) {
                return 1;
            }
        }

        return 0;
    }

    public static boolean b() {
        String v0 = Build.TAGS;
        if(v0 != null && (v0.contains("test-keys"))) {
            return 1;
        }

        return 0;
    }

    public static boolean c() {
        String[] v0 = new String[]{"/system/app/Superuser.apk", "/system/xbin/daemonsu", "/system/etc/init.d/99SuperSUDaemon", "/system/bin/.ext/.su", "/system/etc/.has_su_daemon", "/system/etc/.installed_su_daemon", "/dev/com.koushikdutta.superuser.daemon/"};
        int v1 = v0.length;
        int v3;
        for(v3 = 0; v3 < v1; ++v3) {
            if(new File(v0[v3]).exists()) {
                return 1;
            }
        }

        return 0;
    }
}
  • a方法检测路径中有没有存在su这个文件,如果存在就判断为设备已经被root了
  • b方法检查Build.TAGS中是否存在test-keys,如果存在就判断设备已经被root了
  • c方法是在检测一系列文件,如果有一个被找到就判断设备已经被root。

很显然,c类中使用3中方式对root进行检测。

MainActivity.a方法代码

private void a(String arg4) {
        AlertDialog v0 = new AlertDialog$Builder(((Context)this)).create();
        v0.setTitle(((CharSequence)arg4));
        v0.setMessage("This is unacceptable. The app is now going to exit.");
        v0.setButton(-3, "OK", new DialogInterface$OnClickListener() {
            public void onClick(DialogInterface arg1, int arg2) {
                System.exit(0);
            }
        });
        v0.setCancelable(false);
        v0.show();
    }

调用了此方法,函数会弹出对话框,并显示传入的字符串内容,如果点击OK,程序将退出,System.exit(0)。

绕过root 检测

回到onCreate中,经过静态分析,有两种办法绕过root检测。

  • 使用动态调试的方式,查看c类中a、b、c三个方法中哪一个方法返回了1,选择hook修改返回值或者修改smali代码重新打包,绕过root检测。
  • 修改MainActivity.a函数,让其不执行System.exit(0),而只是return void。需要修改smali代码并重新打包。

目前选择第二种方法,因为它是最简单的,只需要将System.exit(0)对应的smali汇编代码注释掉即可。但由于出于学习目的,后面文章我还会附上第一种方法的做法。

使用apktool将apk进行反编译。

apktool d UnCrackable-Level1.apk -o uncrackable_dissas

在这里插入图片描述

smali代码位于
uncrackable_dissas/smali/sg/vantagepoint/

在这里插入图片描述
可以看出和jeb反编译的目录是一样的。
在这里插入图片描述
接下来要找smali代码的注入点,我发现其实这一步是有技巧的,而且非常有用,这个项目比较小,所以找的比较快。如果是比较大的项目,没有一定技巧是没有那么容易就能找到注入点的。

首先我们需要确定要修改的smali代码在哪个文件里,主要针对含有匿名内部类的Java文件而言。MainActivity方法被反编译成MainActivity.smali、MainActivity$ 1.smali、MainActivity$ 2.smali。这里MainActivity$ 1.smali、MainActivity$ 2.smali这些都是匿名内部类的smali代码文件,由于没有名字,所以编译后只能用$XXX来区分。

然后使用vscode打开的smali文件和jeb打开的Java代码进行上下文比对,就能找到注入点。(ps:推荐使用vscode打开smali,下载smali插件即可完美编辑)。

在这里插入图片描述

将41行进行注释,那么调用MainActivity.a方法后,点击确定也不会退出程序,就可以进行绕过了。
在这里插入图片描述
重新进行打包

apktool b uncrackable_dissas -o modified_uncracjable.apk

然后进行签名,就可以安装运行在android设备上。
关于签名我写了一键签名工具,直接拖入apk位置就可以自动完成签名。

点击了ok,程序也没有退出,root检测成功绕过!
在这里插入图片描述

拿到FLAG!

接下来在jeb中找到关键函数,verify函数很可疑,并且有Sucess提示。
关键是a.a()内部,让其返回值为真就可以。
在这里插入图片描述
进入a.a函数看看。

package sg.vantagepoint.uncrackable1;

import android.util.Base64;
import android.util.Log;

public class a {
    public static boolean a(String arg5) {
        byte[] v0_2;
        String v0 = "8d127684cbc37c17616d806cf50473cc";
        byte[] v1 = Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0);
        byte[] v2 = new byte[0];
        try {
            v0_2 = sg.vantagepoint.a.a.a(a.b(v0), v1);
        }
        catch(Exception v0_1) {
            Log.d("CodeCheck", "AES error:" + v0_1.getMessage());
            v0_2 = v2;
        }

        return arg5.equals(new String(v0_2));
    }

    public static byte[] b(String arg7) {
        int v0 = arg7.length();
        byte[] v1 = new byte[v0 / 2];
        int v2;
        for(v2 = 0; v2 < v0; v2 += 2) {
            v1[v2 / 2] = ((byte)((Character.digit(arg7.charAt(v2), 16) << 4) + Character.digit(arg7.charAt(v2 + 1), 16)));
        }

        return v1;
    }
}


函数内部实现比较简单,主要是算法构成。
v1是字符串5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=进行base64解密后的byte数组。
v0也是一个常量。

这里的关键点在于v0_2,这个是由加密最密集的地方产生的数据,而它也是最后和传入的arg5进行比较的,所以我会把关注点放在产生v0_2的函数:sg.vantagepoint.a.a.a()

而它的函数实现如下:

package sg.vantagepoint.a;

import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class a {
    public static byte[] a(byte[] arg2, byte[] arg3) {
        SecretKeySpec v0 = new SecretKeySpec(arg2, "AES/ECB/PKCS7Padding");
        Cipher v2 = Cipher.getInstance("AES");
        v2.init(2, ((Key)v0));
        return v2.doFinal(arg3);
    }
}

这里的方法有两个参数、arg2和arg3。

首先使用arg2随机生成一个key秘钥,再使用秘钥对arg3进行AES进行对称加密,外部参数和它进行对比。

而这里由于传入的v0、v1都是常量,我们需要hook此方法,把这个固定的加密返回值打印出来即可,这就是crack的思路。

这里使用frida工具进行解密,有关frida的介绍和文档在这里
贴上脚本:

Java.perform(function(){
    //hook the target class 
    var aes = Java.use("sg.vantagepoint.a.a");

    //hook the function inside the class
    aes.a.implementation = function(var0, var1){
        //call itself
        var decrypt = this.a(var0,var1);
        var flag = "";

        for (var i =0; i < decrypt.length; i++){
            flag += String.fromCharCode(decrypt[i]);
        }
        console.log(flag);
        return decrypt;
    }
});


找到运行的apk名称,
在这里插入图片描述
执行,

frida -U -f owap.mstg.uncrackable1 -l exploit.js --no-pause

-U 代表进入USB设备
-f 代表指定应用程序文件
-l 指定脚本
–no-pause 在应用程序启动后,自动加载到主进程中去。

当我们运行上面的命令行后,模拟器或手机APP会自动加载,

随便在app中输入

在这里插入图片描述
查看脚本打印…
在这里插入图片描述
ok,脚本将flag打印出来了。
在这里插入图片描述
拿到flag!

总结

  • 在进行静态分析的时候,对一个问题进行多角度思考,有没有更好的办法能够解决目前的问题
  • 找到smali注入点的技巧,熟能生巧,经验活。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值