Fork me on GitHub

Android逆向笔记:练手crackme之num1r0的几个apk

注意:本文涉及到的三个apk都是非常非常简单的级别,适合逆向新手阅读。

num1r0是一个人(其实我也不知道是谁,就是搜Android练手apk时搜到的),他做了几个android crack me,只是感觉蛮有意思的所以研究一下。

https://persianov.net/crackme-challenges-for-android

我很喜欢这种风格,于是把整个网站截图放在这里:

1

1

1

不过目前来看只提供了三个,作者是把它们放到了GitHub上了:

https://github.com/num1r0/android_crackmes

好接下来就挨个玩耍一下。


crackme_0x01

https://github.com/num1r0/android_crackmes/tree/master/crackme_0x01

或:

https://github.com/CC11001100/Android-RE-attachment-netdisk/blob/master/cnblogs/13975169/crackme_0x01.apk

下载下来安装到夜神模拟器,如果安装不了可能是自己的模拟器版本过期,自行创建更高版本的模拟器:

1

让我们输入密码,那就在密码框随便输入点内容提交:

1

提示我们错误的密码,OK,现在把apk文件拖到jeb打开看一下:

1

看上去项目结构十分简单,于是就挨个看一下,首先是MainActivity:

package com.entebra.crackme0x01;

import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.app.AppCompatActivity;
import android.view.View.OnClickListener;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
    private final String description;
    private final String name;

    public MainActivity() {
        this.name = "CrackMe 0x01";
        this.description = "Level: Beginner";
    }

    public void follow_clicked(View arg3) {
        try {
            this.startActivity(new Intent("android.intent.action.VIEW", Uri.parse("twitter://user?screen_name=num1r0")));
        }
        catch(Exception unused_ex) {
            this.startActivity(new Intent("android.intent.action.VIEW", Uri.parse("https://twitter.com/#!/num1r0")));
        }
    }

    @Override  // android.support.v7.app.AppCompatActivity
    protected void onCreate(Bundle arg3) {
        this.getSupportActionBar().hide();
        super.onCreate(arg3);
        this.setContentView(0x7F09001B);  // layout:activity_main
        ((Button)this.findViewById(0x7F070078)).setOnClickListener(new View.OnClickListener() {  // id:submit
            @Override  // android.view.View$OnClickListener
            public void onClick(View arg4) {
                String v4 = new FlagGuard().getFlag(((EditText)this.findViewById(0x7F070055)).getText().toString());  // id:password
                if(v4 != null) {
                    Builder v0 = new Builder(MainActivity.this);
                    v0.setTitle("Congratulations!");
                    v0.setMessage("The flag is: " + v4);
                    v0.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override  // android.content.DialogInterface$OnClickListener
                        public void onClick(DialogInterface arg1, int arg2) {
                            arg1.dismiss();
                        }
                    });
                    v0.create().show();
                    return;
                }

                Builder v4_1 = new Builder(MainActivity.this);
                v4_1.setTitle("Nope!");
                v4_1.setMessage("Wrong password -> No flag :))");
                v4_1.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override  // android.content.DialogInterface$OnClickListener
                    public void onClick(DialogInterface arg1, int arg2) {
                        arg1.dismiss();
                    }
                });
                v4_1.create().show();
            }
        });
    }
}

比较重要的是onCreate中为submit按钮绑定了一个事件,当按钮被单击时获取id为password的文本输入框的内容进行校验,如果校验结果不为null则认为是ok,然后再来看校验的那部分:

package com.entebra.crackme0x01;

import android.util.Log;

public class FlagGuard {
    private String flag;
    private final String pad;
    private final String scr_flag;

    public FlagGuard() {
        this.pad = "abcdefghijklmnopqrstuvwxyz";
        this.scr_flag = "qw4r_q0c_nc4nvx3_0i01_srq82q8mx";
        this.flag = "";
    }

    public String getFlag(String arg2) {
        return arg2.equals(new Data().getData()) ? this.unscramble() : null;
    }

    private String unscramble() {
        String v5_1;
        StringBuilder v0 = new StringBuilder();
        int v2 = 0;
        char[] v3 = "qw4r_q0c_nc4nvx3_0i01_srq82q8mx".toCharArray();
        while(v2 < v3.length) {
            char v5 = v3[v2];
            Log.e("Char: ", String.valueOf(v5));
            int v6 = "abcdefghijklmnopqrstuvwxyz".indexOf(v5);
            Log.e("indexOf: ", String.valueOf(v6));
            if(v6 < 0) {
                v5_1 = String.valueOf(v5);
            }
            else {
                int v5_2 = "abcdefghijklmnopqrstuvwxyz".length();
                int v6_1 = (v6 - ((int)Integer.valueOf(String.valueOf(1337).split("\\.")[0]))) % v5_2;
                v5_1 = v6_1 >= 0 ? String.valueOf("abcdefghijklmnopqrstuvwxyz".toCharArray()[v6_1]) : String.valueOf("abcdefghijklmnopqrstuvwxyz".toCharArray()[v6_1 + "abcdefghijklmnopqrstuvwxyz".length()]);
            }

            Log.e("letter ", v5_1);
            v0.append(v5_1);
            ++v2;
        }

        Log.e("FLAG: ", v0.toString());
        return v0.toString();
    }
}

其中比较重要的是getFlag这个方法,在这个方法中又调用了:

new Data().getData()

我们输入的内容要和这个方法的返回值一致,继续追进去看一下:

package com.entebra.crackme0x01;

public class Data {
    private final String secret;

    public Data() {
        this.secret = "s3cr37_p4ssw0rd_1337";
    }

    public String getData() {
        this.getClass();
        return "s3cr37_p4ssw0rd_1337";
    }
}

OK,这个方法只是简单的返回了一个字符串s3cr37_p4ssw0rd_1337,而这个字符串就是我们要输入的字符串,再切换到app界面,输入试一下:

1


crackme_0x02

apk下载地址为:

https://github.com/num1r0/android_crackmes/tree/master/crackme_0x02

或:

https://github.com/CC11001100/Android-RE-attachment-netdisk/blob/master/cnblogs/13975169/crackme_0x02.apk

拖到夜神模拟器安装看一下:

1

随便输入点东西,然后submit:

1

好了,拖到jeb看下代码,结构比较简单:

1

打开MainActivity,它的onCreate方法中标记的这一行比较关键,这是获取我们输入的字符串,然后调用另一个方法校验,如果返回的值不为空就认为是通过了:

1

然后看它调用的这样代码,这里面又用到了一个Data,我们的输入要和Data.getData()相等:

1

然后Data这个类比较简单,就是读取一个资源id的值,这里jeb已经自动识别出来这个资源id是一个strings.xml中的名为secret值为s0m3_0th3r_s3cr3t_passw0rd:

1

这个s0m3_0th3r_s3cr3t_passw0rd就是我们要输入的值,来试一下:

1

OK,破解成功。


crackme_0x03

apk下载地址:

https://github.com/num1r0/android_crackmes/tree/master/crackme_0x03

或:

https://github.com/CC11001100/Android-RE-attachment-netdisk/blob/master/cnblogs/13975169/crackme_0x03.apk

把apk文件下载下来拖到jeb看下项目结构:

1

这个家伙的套路怎么都一模一样,要不是看源码不同我都怀疑我搞错apk文件了...

然后看下MainActivity:

package net.persianov.crackme0x03;

import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.app.AppCompatActivity;
import android.view.View.OnClickListener;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
    public void followClicked(View arg3) {
        try {
            this.startActivity(new Intent("android.intent.action.VIEW", Uri.parse("twitter://user?screen_name=num1r0")));
        }
        catch(Exception unused_ex) {
            this.startActivity(new Intent("android.intent.action.VIEW", Uri.parse("https://twitter.com/#!/num1r0")));
        }
    }

    @Override  // android.support.v7.app.AppCompatActivity
    protected void onCreate(Bundle arg3) {
        this.getSupportActionBar().hide();
        super.onCreate(arg3);
        this.setContentView(0x7F09001B);  // layout:activity_main
        ((Button)this.findViewById(0x7F070078)).setOnClickListener(new View.OnClickListener() {  // id:submit
            @Override  // android.view.View$OnClickListener
            public void onClick(View arg4) {
                String v4 = new FlagGuard().getFlag(((EditText)this.findViewById(0x7F070055)).getText().toString());  // id:password
                if(v4 != null) {
                    Builder v0 = new Builder(MainActivity.this);
                    v0.setTitle("Congratulations!");
                    v0.setMessage("The flag is: " + v4);
                    v0.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override  // android.content.DialogInterface$OnClickListener
                        public void onClick(DialogInterface arg1, int arg2) {
                            arg1.dismiss();
                        }
                    });
                    v0.create().show();
                    return;
                }

                new Data();
                Builder v0_1 = new Builder(MainActivity.this);
                v0_1.setTitle("Nope!");
                v0_1.setMessage("Unknown error...");
                v0_1.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override  // android.content.DialogInterface$OnClickListener
                    public void onClick(DialogInterface arg1, int arg2) {
                        arg1.dismiss();
                    }
                });
                v0_1.create().show();
            }
        });
    }
}

次奥几乎一毛一样...

然后进入熟悉的FlagGuard:

package net.persianov.crackme0x03;

import android.os.Build.VERSION;

public class FlagGuard {
    private char[] flag;

    public FlagGuard() {
        this.flag = new char[20];
    }

    private String generate() {
        int[] v1 = new int[]{13, 1, 19, 14, 5, 8, 18, 9, 2, 11, 17, 3, 10, 6, 15, 7, 0, 16, 12, 4};
        StringBuilder v2 = new StringBuilder();
        int[] v3 = new int[20];
        int v4 = 0;
        int v5;
        for(v5 = 0; v5 < v3.length; ++v5) {
            v3[v5] = 27;
        }

        if(Build.VERSION.CODENAME.length() > 0) {
            int v5_1;
            for(v5_1 = 0; v5_1 < 5; ++v5_1) {
                v3[v5_1] = 65;
                switch(v5_1) {
                    case 1: {
                        int v6 = v5_1 - 1;
                        v3[v5_1] = v3[v6] + 4;
                        ++v5_1;
                        v3[v5_1] = v3[v6] + 30;
                        break;
                    }
                    case 3: {
                        v3[v5_1] <<= 1;
                        v3[v5_1] += -23;
                        break;
                    }
                    case 4: {
                        v3[v5_1] = v3[v5_1 - 1] + 14;
                    }
                }
            }

            int v5_2;
            for(v5_2 = 5; v5_2 < 10; ++v5_2) {
                switch(v5_2) {
                    case 5: {
                        v3[v5_2] = v3[v5_2 - 3];
                        break;
                    }
                    case 6: {
                        int v8 = v5_2 - 1;
                        v3[v5_2] = v3[v8] - 11;
                        v3[v5_2 + 2] = v3[v5_2] - 2;
                        v3[v5_2 + 1] = 103;
                        v3[v5_2 + 3] = v3[v8];
                    }
                }
            }

            v3[10] = 73;
            v3[13] = 0x4F;
            v3[12] = 0x4F;
            v3[11] = v3[7] - 2;
            int v5_3;
            for(v5_3 = 15; v5_3 < 20; ++v5_3) {
                switch(v5_3) {
                    case 15: {
                        v3[v5_3 - 1] = v3[v5_3 - v5_3 + 1];
                        v3[v5_3] = 0x75;
                        break;
                    }
                    case 16: {
                        v3[v5_3] = 104;
                        v3[v5_3 + 2] = v3[v5_3 - 1] - 1;
                        break;
                    }
                    case 17: {
                        v3[v5_3] = v3[5];
                        v3[v5_3 + 2] = v3[v5_3];
                    }
                }
            }
        }
        else {
            v2.replace(0, v2.length(), "");
        }

        int v5_4 = 0;
        while(v4 < v1.length) {
            this.flag[v1[v4]] = (char)v3[v5_4];
            ++v5_4;
            ++v4;
        }

        v2.append(this.flag);
        return v2.toString();
    }

    public String getFlag(String arg2) {
        return new Data().isPasswordOk(arg2) ? this.generate() : null;
    }
}

看他的getFlag仍然是调用的Data的一个方法,再看Data:

package net.persianov.crackme0x03;

import android.util.Log;
import java.security.MessageDigest;

public class Data {
    private static String lastError = "Unknown error...";
    private final String long_password_message;
    private final String password_hash;
    private int password_length;
    private final String short_password_message;
    private final String wrong_password_message;

    static {
    }

    public Data() {
        this.short_password_message = "Password too SHORT";
        this.long_password_message = "Password too LONG";
        this.wrong_password_message = "WRONG password entered";
        this.password_length = 6;
        this.password_hash = "ac43bb53262e4edd82c0e82a93c84755";
    }

    private boolean MD5Compare(String passwd, String passwdHash) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(passwd.getBytes());
            byte[] passwdMd5Bytes = md5.digest();
            md5.reset();
            StringBuilder md5String = new StringBuilder();
            int i;
            for(i = 0; i < passwdMd5Bytes.length; ++i) {
                String j;  // 这个补前缀0的方法真是看得我要炸了
                for(j = Integer.toHexString(passwdMd5Bytes[i] & 0xFF); j.length() < 2; j = "0" + j) {  // 这个补前缀0的方法真是看得我要炸了
                }

                md5String.append(j);
            }

            return md5String.toString().contentEquals(passwdHash) ? 1 : 0;
        }
        catch(Exception v8) {
            Log.e("Exception MD5 compare", v8.getMessage());
            return 0;
        }
    }

    public String getData() {
        this.getClass();
        return "ac43bb53262e4edd82c0e82a93c84755";
    }

    public String getLastError() {
        return Data.lastError;
    }

    public boolean isPasswordOk(String passwd) {
        if(passwd.length() < this.password_length) {
            Data.lastError = "Password too SHORT";
            return 0;
        }

        if(passwd.length() > this.password_length) {
            Data.lastError = "Password too LONG";
            return 0;
        }

        if(passwd.length() == this.password_length) {
            this.getClass();
            if(!this.MD5Compare(passwd, "ac43bb53262e4edd82c0e82a93c84755")) {
                Data.lastError = "WRONG password entered";
                return 0;
            }

            this.getClass();
            return this.MD5Compare(passwd, "ac43bb53262e4edd82c0e82a93c84755");
        }

        return 0;
    }
}

是需要我们输入的文本的MD5是ac43bb53262e4edd82c0e82a93c84755,找了几个免费的网站解密了一下都没出来,看来可能不是一个常见的字符串,那咋办,回头看注意到FlagGuard方法的getFlag的内容是:

    public String getFlag(String arg2) {
        return new Data().isPasswordOk(arg2) ? this.generate() : null;
    }

那么直接执行generate方法得到flag应该也是一样的,新建一个Java类运行一下就好了:

package cc11001100.android.crack_me_kotlin;

class A {
    
    private static String generate(boolean versionGtZero) {
        char[] flag = new char[20];
        int[] v1 = new int[]{13, 1, 19, 14, 5, 8, 18, 9, 2, 11, 17, 3, 10, 6, 15, 7, 0, 16, 12, 4};
        StringBuilder v2 = new StringBuilder();
        int[] v3 = new int[20];
        int v4 = 0;
        int v5;
        for (v5 = 0; v5 < v3.length; ++v5) {
            v3[v5] = 27;
        }

//        if(Build.VERSION.CODENAME.length() > 0) {
        if (versionGtZero) {
            int v5_1;
            for (v5_1 = 0; v5_1 < 5; ++v5_1) {
                v3[v5_1] = 65;
                switch (v5_1) {
                    case 1: {
                        int v6 = v5_1 - 1;
                        v3[v5_1] = v3[v6] + 4;
                        ++v5_1;
                        v3[v5_1] = v3[v6] + 30;
                        break;
                    }
                    case 3: {
                        v3[v5_1] <<= 1;
                        v3[v5_1] += -23;
                        break;
                    }
                    case 4: {
                        v3[v5_1] = v3[v5_1 - 1] + 14;
                    }
                }
            }

            int v5_2;
            for (v5_2 = 5; v5_2 < 10; ++v5_2) {
                switch (v5_2) {
                    case 5: {
                        v3[v5_2] = v3[v5_2 - 3];
                        break;
                    }
                    case 6: {
                        int v8 = v5_2 - 1;
                        v3[v5_2] = v3[v8] - 11;
                        v3[v5_2 + 2] = v3[v5_2] - 2;
                        v3[v5_2 + 1] = 103;
                        v3[v5_2 + 3] = v3[v8];
                    }
                }
            }

            v3[10] = 73;
            v3[13] = 0x4F;
            v3[12] = 0x4F;
            v3[11] = v3[7] - 2;
            int v5_3;
            for (v5_3 = 15; v5_3 < 20; ++v5_3) {
                switch (v5_3) {
                    case 15: {
                        v3[v5_3 - 1] = v3[v5_3 - v5_3 + 1];
                        v3[v5_3] = 0x75;
                        break;
                    }
                    case 16: {
                        v3[v5_3] = 104;
                        v3[v5_3 + 2] = v3[v5_3 - 1] - 1;
                        break;
                    }
                    case 17: {
                        v3[v5_3] = v3[5];
                        v3[v5_3 + 2] = v3[v5_3];
                    }
                }
            }
        } else {
            v2.replace(0, v2.length(), "");
        }

        int v5_4 = 0;
        while (v4 < v1.length) {
            flag[v1[v4]] = (char) v3[v5_4];
            ++v5_4;
            ++v4;
        }

        v2.append(flag);
        return v2.toString();
    }

    public static void main(String[] args) {

        System.out.println(generate(true)); // hERe_yOu_gO_tAkE_IT_
        System.out.println(generate(false)); // 没有输出

    }

}

最后的输出应该是hERe_yOu_gO_tAkE_IT_

posted @ 2020-11-14 23:09  CC11001100  阅读(1023)  评论(0编辑  收藏  举报