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

  • 相关阅读:
    ES之2:海量数据处理之倒排索引
    架构-伸缩性
    ES之3:elasticsearch优化收集
    架构-扩展性
    关于静态方法与非静态方法的执行效率
    架构师
    拖库 洗库 撞库
    SOA架构设计经验分享—架构、职责、数据一致性
    mysql索引之五:多列索引
    mysql索引之四:复合索引之最左前缀原理,索引选择性,索引优化策略之前缀索引
  • 原文地址:https://www.cnblogs.com/cc11001100/p/13975169.html
Copyright © 2020-2023  润新知