首页
社区
课程
招聘
[原创]提交解题思路(enimey)
发表于: 2015-1-25 17:16 2153

[原创]提交解题思路(enimey)

2015-1-25 17:16
2153
第一题:
使用JEB反编译APK,关键代码为:
                String v3 = this.val$edit.getText().toString();
                String v5 = MainActivity.this.getTableFromPic();
                String v4 = MainActivity.this.getPwdFromPic();
                Log.i("lil", "table:" + v5);
                Log.i("lil", "pw:" + v4);
                try {
                    String v2 = MainActivity.bytesToAliSmsCode(v5, v3.getBytes("utf-8"));
                    Log.i("lil", "enPassword:" + v2);
                }
                catch(UnsupportedEncodingException v1) {
                    v1.printStackTrace();
                }

                if(v4 == null || (v4.equals("")) || !v4.equals(v2)) {
                    AlertDialog$Builder v0 = new AlertDialog$Builder(MainActivity.this);
                    v0.setMessage(2131361809);
                    v0.setTitle(2131361808);
                    v0.setPositiveButton(2131361811, new DialogInterface$OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    });
                    v0.show();
                }

实际上就是一个简单的查表验证,通过Log可以直接得到table和pw,反向查找pw在table中的序号,正确的输入为:581026;

第二题:
JEB反编译APK,验证代码为:
                if(MainActivity.this.securityCheck(MainActivity.this.inputCode.getText().toString())
                        ) {
                    MainActivity.this.startActivity(new Intent(MainActivity.this, ResultActivity.class));
                }
                else {
                    Toast.makeText(MainActivity.this.getApplicationContext(), "验证码校验失败", 0).show();
                }
securityCheck为native函数,即验证逻辑在SO中完成。
SO有反调试,首先过掉反调试,步骤为:
1)准备好调试环境(ida、adb等)
1) adb shell
2)am start -D -n com.yaotong.crackme/.MainActivity
3) 使用IDA附加上该APK进程,设置加载SO时端下来,在dlopen下断点
4)jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
5)continue
6)在libcrackme.so的JNI_OnLoad下断点
7)单步跟踪结合IDA静态分析和F5,并在fgets处下断点,SO是通过读取/proc/pid/status文件中的Tracepid字段来判断是否有调试,如果检测到调试程序将会退出。最终,我才用patch跳转指令过掉了反调试:
BLX     R11             ; 调sscanf函数
LDR     R0, [SP,#0x348+var_310]
CMP     R0, #1
BGE     loc_75025600    ; 把这个跳转取消掉就可以了
8)在Java_com_yaotong_crackme_MainActivity_securityCheck下断点,continue
9)通过动态调试,验证逻辑为将用户的输入与字符串“aiyou,bucuoo”进行比较,匹配则成功。
最终正确的输入为:aiyou,bucuoo

第三题:
1)使用JDB反编译APK,发现只有一个StubApplication类,该类的功能就是加载SO。初步判断是采用了Application代理对dex进行了加壳。
2)进一步分析,发现在lib/armeabi/目录中,libmobisecy.so实际上是一个zip文件,解压后得到一个classes.dex文件,拖到JEB里,发现除了方法实体全被替换为了throw new RuntimeException();即我们只需要修复codeOff即可完成dex脱壳。
3)使用IDA动态调试SO,SO做了混淆,并且通过调用ptrace和检测/proc/pid/status检测调试器,动态调了半天,坑太大放弃了。
4)换了种思路,当APK正常运行后,其codeOff必定是已经恢复过后的,那么我们直接在APK正常运行后,得到codeOff,然后修复原dex即可。
5)由于这种加壳方式,最后必定是要通过调用Application.onCreate来启动真正的逻辑的。也就是说当执行Application.onCreate时,codeOff已经修正完毕。
6)我的最终解决办法为修改Android源码,定制一个系统,从而得到real_codeOff。
7)在dalvik/vm/interp/Stack.cpp文件的dvmCallMethodV函数,添加自己的逻辑:
    判断函数名是否为onCreate->获得DexFile结构体->解析DexFile循环打印出所有codeOff
8)通过上步得到的codeOff修复原dex
9)使用JDB反编译修复过后的dex,验证逻辑为:
    public void run() {
        int v0_6;
        String v0_5;
        String v1_5;
        byte[] v2_3;
        Cipher v1_1;
        MessageDigest v0_3;
        String v5;
        dn.b(dn.a());
        MessageDigest v1 = null;
        if(Build$VERSION.SDK_INT >= 10 && (Debug.isDebuggerConnected())) {
            this.b.sendEmptyMessage(1);
            return;
        }

        try {
            v5 = new e().a(this.c);
        }
        catch(Exception v0) {
            this.b.sendEmptyMessage(3);
            return;
        }

        try {
            if(v5.equals("sos")) {
                this.b.sendEmptyMessage(2);
                return;
            }

            CRC32 v0_1 = new CRC32();
            v0_1.update(v5.getBytes());
            v0_1.getValue();
            v5.hashCode();
            try {
                v0_3 = MessageDigest.getInstance("sha1");
            }
            catch(NoSuchAlgorithmException v0_2) {
                v0_2.printStackTrace();
                v0_3 = v1;
            }

            try {
                v1_1 = Cipher.getInstance("AES");
            }
            catch(NoSuchPaddingException v0_4) {
                v0_4.printStackTrace();
                return;
            }
            catch(NoSuchAlgorithmException v2) {
                v2.printStackTrace();
            }

            if(!b.a && v1_1 == null) {
                throw new AssertionError();
            }
        }
        catch(Exception v0) {
            goto label_25;
        }

        int v2_1 = 2;
        try {
            v1_1.init(v2_1, new SecretKeySpec(Base64.decode("GXiQHT1CZ2elMzwpvvAoPA==".getBytes(), 0),
                    "AES"));
            goto label_65;
        }
        catch(Exception v0) {
        }
        catch(InvalidKeyException v2_2) {
        label_65:
            try {
                new byte[0];
                try {
                    v2_3 = v1_1.doFinal(Base64.decode("hjdsUjIT5je69WXIZP7Kzw==".getBytes("UTf-8"),
                            0));
                    goto label_74;
                }
                catch(UnsupportedEncodingException v1_2) {
                    try {
                        v1_2.printStackTrace();
                    label_74:
                        String v6 = new String(v2_3);
                        v0_3.update(new byte[]{127});
                        v0_3.update(v5.getBytes());
                        v0_3.update(new byte[]{1});
                    }
                    catch(Exception v0) {
                        goto label_25;
                    }
                }
                catch(BadPaddingException v1_3) {
                    goto label_74;
                }
                catch(IllegalBlockSizeException v1_4) {
                    goto label_74;
                }
            }
            catch(Exception v0) {
                goto label_25;
            }

            try {
                v1_5 = new String(Base64.encode(v0_3.digest(), 0));
                goto label_93;
            }
            catch(Exception v0) {
                try {
                    v0.printStackTrace();
                    return;
                label_93:
                    if(!v5.equals(v6)) {
                        goto label_183;
                    }
                    else if(Arrays.equals(v1_5.getBytes(), "2398lj2n".getBytes())) {
                        this.b.sendEmptyMessage(0);
                        return;
                    }
                    else {
                        v0_5 = "234";
                    }

                    goto label_111;
                }
                catch(Exception v0) {
                    goto label_25;
                }
            }

        label_183:
            v0_5 = v1_5;
            try {
            label_111:
                if(v0_5.equals("lsdf==")) {
                    this.b.sendEmptyMessage(0);
                    return;
                }

                char[] v1_6 = v5.toCharArray();
                v0_6 = v5.substring(0, 2).hashCode();
                if(v0_6 > 3904) {
                    this.b.sendEmptyMessage(4);
                    return;
                }

                if(v0_6 == 3618) {
                    if(v1_6[0] + v1_6[1] != 168) {
                        goto label_171;
                    }

                    do {
                    label_138:
                        byte[] v5_1 = e.class.getAnnotation(f.class).a() + a.class.getAnnotation(f.class)
                                .a().getBytes();
                        if(v1_6.length - 2 == v5_1.length) {
                            v0_6 = 0;
                            while(true) {
                                if(v0_6 >= v5_1.length) {
                                    goto label_181;
                                }
                                else if(v1_6[v0_6 + 2] != v5_1[v0_6]) {
                                    v0_6 = 0;
                                }
                                else {
                                    ++v0_6;
                                    continue;
                                }

                                goto label_163;
                            }
                        }

                        goto label_170;
                    }
                    while(true);
                }

                goto label_171;
            }
            catch(Exception v0) {
                goto label_25;
            }

        label_181:
            v0_6 = 1;
            try {
            label_163:
                if(v0_6 != 0) {
                    this.b.sendEmptyMessage(0);
                    return;
                }

            label_170:
                if(v2_3 == null) {
                    goto label_138;
                }

            label_171:
                this.b.sendEmptyMessage(1);
            }
            catch(Exception v0) {
            label_25:
                this.b.sendEmptyMessage(1);
            }
        }
    }
    当输入正确时,跳转到this.b.sendEmptyMessage(0)
    验证逻辑:
    1) 将用户输入作为map的key,并以空格分开,得到对应的value
    2)value.substring(0, 2).hashCode需等于3618
    3)value[0] + value[1] 需等于168
    4)通过前面两个条件可以确定输入的前两个key:...和_____
    5)value的后面的数据要和e.class.getAnnotation(f.class).a() + a.class.getAnnotation(f.class)相同
    6)同样的思路直接在dalvik/vm/reflect/Annotation.cpp的processAnnotationValue函数处添加自己的逻辑:
        1)首先在开始打印出className:ALOGD("***processAnnotationValue*** className: %s", clazz->descriptor);
        2)在case kDexAnnotationString处打印出值:ALOGD("elemObj: %s", dvmCreateCstrFromString((const StringObject *)elemObj));
    7)得到后面的value为7e1p,则对应的key为:____. . ..___ .__.
    8)最终,正确的输入为:... _____ ____. . ..___ .__.

    第四题:
    和第三题同样的方法修复了dex,嗯。。未完待续

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//