首页
社区
课程
招聘
[分享]hfourx解题报告
发表于: 2015-1-25 21:45 2495

[分享]hfourx解题报告

2015-1-25 21:45
2495
1. 第一题

下载apk解包,把内部的classes.dex文件直接拿去反编译。用dex2jar和jd-gui可以完美看到反编译结果。

第一题是一个简单的一次代换密码。从输入框中读入英文字符,转换为0-255以内的ASCII码(严谨的说是UTF-8,但是UTF-8兼容ASCII,不需考虑额外的情况就可以得到正确答案),然后在一个预置长度256的汉字数组中按下标找到对应的中文串,最终需要中文串与指定的串一致。因此只需要根据指定的串逆推即可得到答案的ASCII序列,转换成原文得到答案“581026”

2. 第二题

第二题解开apk包后,还按照第一题的做法,jd-gui内部可以发现关键的验证函数是native实现。因此第二题的验证算法是用JNI实现的。

因此分析“lib/armabi/libcrackme.so”。用IDA打开,可以发现JNI函数的原型Java_com_yaotong_crackme_MainActivity_securityCheck。在函数中,关键代码反编译如下:

[FONT="Courier New"]  chars = (unsigned __int8 *)((int (__fastcall *)(JNIEnv *, jstring, _DWORD))(*env)->GetStringUTFChars)(env, str, 0);
  password = (unsigned __int8 *)passwordBuffer;
  while ( 1 )
  {
    passChar = *password;
    if ( passChar != *chars )
      break;
    ++password;
    ++chars;
    flagTrue = 1;
    if ( !passChar )
      return flagTrue;
  }
  return 0;[/FONT]


此处有个小技巧,准备一份Dalvik和JNI的头文件,载入到IDA可以帮助识别JNIEnv的各种函数。

代码基本是把输入字符转换为UTF-8字节,然后和passwordBuffer逐个字节对比。其中后者是密码的保存的位置。直接用IDA查看得到的“wojiushidaan”并不是正确答案,所以确定他的值被动态修改了。

下一步就是用gdb动态调试,直接获取密码内容。启动程序,然后在Android的shell中运行gdbserver,在pc端连接至调试器。程序本身没有做较强的反调试保护,可以直接附加。按照“/proc/self/maps”中的基地址加上密码的偏移0x00004450,得到了正确的密码“aiyou,bucuoo”

3. 第三题

第三题同样解开apk,用jd-gui观察,发现只载入了libmobisec.so。再看libmobisec.so,内部比较复杂,但没有看到JNI实现,进一步观察,发现字串明文很少,各种函数内部无意义跳转次数很多,判断apk已经加壳。而另外两个so文件,其中一个其实是一个apk包,解包后得到一个classes.dex,不包含多少有用信息,另一个so文件是密文无法解析。

起初精力主要集中在逆向libmobisec.so,已经成功逆向出了全部的字串解密函数。但是即使这样依然没有任何攻破的希望。此后曾尝试使用gdb从内存中dump一份libmobisec.so,虽然得到了大量明文字串,但是在仔细修复偏移之前依然不能用于分析。

在这一阶段耗费了很多时间,经过多次探索,最终认为最可行的办法是脱出dex文件明文反编译。在“/proc/self/maps”中查看映射模块,发现可疑项libmobisecx.so (deleted),考虑dump下来研究。

* 反调试

然而在dump的过程中遇到了反调试,显示进程已被ptrace。经过查看,反调试的原理是libmobisec.so在gdb附加之前对自己执行ptrace调用,从而排除外部调试器。在前面的分析中已经解密了所有字串,很容易定位到了ptrace的地址0x000104E8,利用IDA的交叉引用,最终定位到了调用点JNI_OnLoad

[FONT="Courier New"].text:000111BC                 BL      sub_86A28    ; nop[/FONT]


为了禁止ptrace调用,最简单的办法就是直接patch这个动态库。使用hex编辑器找到偏移0x111bc,将它的四字节OpCode改为nop:00 f0 20 e3。替换手机内的文件,设为root:root 755权限,重新执行程序,反调试消失。

* Dump

待程序完全运行后,使用gdb附加至进程,观察“maps”文件可得到libmobsecx.so在内存中的映射范围。gdb提供了dump memory <file> <addr-begin> <addr-end>命令,执行后得到了此部分的dump。

查看dump内容,发现是一个dex包,直接使用baksmali反编译,发现文件并不完整。重新dump,同时延长<addr-end>参数,最终得到了可用的dex包。

* 反编译与逆向

Dump得到的是一个odex文件,使用smali套件可以把它反汇编为smali指令,也可以重新编译回dex。可以用dex2jar与jd-gui得到java反编译代码,但是反编译的结果有一部分失败,并不完整,因此需要同时参考smali来逆向。

核心部分位于两个类:be

其中观察e可知是一个类莫斯电码的转换器,e.a(String)可以把一组空格分开的电码转换为对应的文字([a-z0-9]),而转换表也保存在e之中。

b类中包含了解密的核心函数b.run(),函数过程冗长,jd-gui的反编译也完全错误,需要手动分析。其基本流程是:

    1. 获取用户输入内容
    2. 调用e.a(String)解码电码
    3. 使用内置的AES key "GXiQHT1CZ2elMzwpvvAoPA==" 解密 "hjdsUjIT5je69WXIZP7Kzw=="
    4. 要求解密后的内容与解码的电码保持一定关系(具体并不重要)
    5. 要求电码的前两个字符hashCode()值等于3618
    6. 从类ea中获取f标签的值分别为"7e" "1p",连接得到"7e1p"
    7. 要求电码的长度为6且后四个字符是"7e1p"
    8. 其余所有内容均为无意义的运算,对于解密并不重要

根据4、5、7三点要求可知,输入的电码,其明文的后4位是"7e1p",而前2位哈希值固定,可以枚举。

Java的字符串hashCode()的计算,对于两个字符"xy"而言,值为(31x+y),其中xy均为ASCII码,编写脚本计算可得前两位只可能为“s5”“qs”。带入验证,只有“s57e1p”可行。使用e类的转换表逆推,得到答案“... ____ ___. . ..___ .__.”

本题的逆向结果将附在跟帖中。

4. 第四题

...没搞定,已经能做到动态库注入、dump、hook,时间不够没能完全干掉反调试。

调试:在系统开发选项里选择调试程序“crackme.4”,以及等待调试器附加,在程序运行后可以附加一个jdb。jdb中下断System.loadLibrary,断下后可以用gdb附加到程序,这个时候可以进一步破解。

注入:两种方法,第一种是任何时刻,可以直接在jdb中调用System.load(sopath)来注入一个动态库,另一种是在gdb里调用dlopen来注入。

hook:注入之后可以用libsubstrate.so挂钩子。可以hook掉ptraceinotify_add_watcher之类的函数破坏反调试。理论可行,不过时间不够,最终没能完成。

经过字符串分析,怀疑第四题的加密部分是用JNI实现在native层的,所以重点大概还是dump一份libmobisec.so。

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 226
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
第三题的关键代码逆向如下

1. b.run()

[FONT="Courier New"]
    public void run()
    {
        dn.b(dn.a() ? 0 : 0);
        if ((Build.VERSION.SDK_INT >= 10) && (Debug.isDebuggerConnected()))
        {
            this.b.sendEmptyMessage(1);
            return;
        }
        String str_v5;
        try
        {
            str_v5 = new e().a(this.c);
            try
            {
                if (str_v5.equals("sos"))
                {
                    this.b.sendEmptyMessage(2);
                    return;
                }
            }
            catch (Exception localException2)
            {
                this.b.sendEmptyMessage(1);
                return;
            }
        }
        catch (Exception localException1)
        {
            this.b.sendEmptyMessage(3);
            return;
        }

        CRC32 localCRC32 = new CRC32();
        localCRC32.update(str_v5.getBytes());
        int crcval = ((int)localCRC32.getValue());
        str_v5.hashCode();

        // build SHA-1 digester

        MessageDigest localMessageDigest1 = null;
        Cipher localCipher1 = null;
        try
        {
            MessageDigest localMessageDigest2 = MessageDigest.getInstance("sha1");
            localMessageDigest1 = localMessageDigest2;
        }
        catch (NoSuchAlgorithmException localNoSuchAlgorithmException1)
        {
            throw new AssertionError();
        }

        // build AES cipher

        try
        {
            while (true)
            {
                Cipher localCipher2 = Cipher.getInstance("AES");
                localCipher1 = localCipher2;
                if ((a) || (localCipher1 != null)) {
                    break;
                }
            }
        }
        catch (NoSuchAlgorithmException localNoSuchAlgorithmException2)
        {
            while (true)
            {
                localNoSuchAlgorithmException2.printStackTrace();
                localCipher1 = null;
            }
        }
        catch (NoSuchPaddingException localNoSuchPaddingException)
        {
            localNoSuchPaddingException.printStackTrace();
            return;
        }

        // init AES cipher

        try {
            localCipher1.init(2, new SecretKeySpec(Base64.decode("GXiQHT1CZ2elMzwpvvAoPA==".getBytes(), 0), "AES"));
        }
        catch (InvalidKeyException err)
        {
            // mov exc v2
        }
        catch (Exception err)
        {
            // mov exc v0
            this.b.sendEmptyMessage(1);
            return;
        }

        Object array_v2;
        String str_v6 = null, str_v1 = null, str4 = null;

        try {
            array_v2 = new byte[0];
        }
        catch (Exception err)
        {
            // mov exc v0
            this.b.sendEmptyMessage(1);
            return;
        }

        // decrypt arrayOfByte2

        byte[] array_v1 = null;
        try {
            array_v1 = localCipher1.doFinal(Base64.decode("hjdsUjIT5je69WXIZP7Kzw==".getBytes("UTf-8"), 0));
        }
        catch (IllegalBlockSizeException err)
        {
            // mov exc v1
        }
        catch (BadPaddingException err)
        {
            // mov exc v1
        }
        catch (UnsupportedEncodingException err)
        {
            // mov exc v1
            err.printStackTrace();
        }
        catch (Exception err)
        {
            // mov exc v0
            this.b.sendEmptyMessage(1);
            return;
        }

        // calculate digest of decoded STR1

        try
        {
            array_v2 = array_v1;
            str_v6 = new String((byte[])array_v2);
            localMessageDigest1.update(new byte[] { 127 });
            localMessageDigest1.update(str_v5.getBytes());
            localMessageDigest1.update(new byte[] { 1 });
        }
        catch (Exception err)
        {
            // mov exc v0
            this.b.sendEmptyMessage(1);
            return;
        }

        // digest to base 64

        try
        {
            str_v1 = new String(Base64.encode(localMessageDigest1.digest(), 0));


            String str_v0;

            if (str_v5.equals(str_v6) == false)
            {
                // to 1a5
                str_v0 = str_v1;
            }
            else
            {
                if(str_v1.getBytes().equals("2398lj2n".getBytes()) == false)
                {
                    // 104
                    str_v0 = "234";
                }
                else
                {
                    this.b.sendEmptyMessage(0);
                    return;
                }
            }

            // 106
            str_v1 = "lsdf==";

            if(str_v0.equals(str_v1))
            {
                this.b.sendEmptyMessage(0);
                return;
            }
            else
            {
                // 116
                char[] charArray_v1 = str_v5.toCharArray();
                int i = str_v5.substring(0, 2).hashCode();
                if (i > 3904)
                {
                    this.b.sendEmptyMessage(4);
                    return;
                }

                // cond_130
                if ((i == 3618) && (charArray_v1[0] + charArray_v1[1] == 168))      // <-- i must be "qs" or "s5", later one passes the exam
                {
                    // cond_13f
                    while(true) {

                        // e.ann(f).a = "7e", a.ann(f).a = "1p"
                        byte[] array_v5 = ("7e" + "1p").getBytes(); // has '\0'?
                        if (-2 + charArray_v1.length == array_v5.length) {
                            int j = 0;
                            // 177

                            for (j = 0; j < array_v5.length; j++) {
                                if (charArray_v1[(j + 2)] != array_v5[j]) {         // <-- array_v5[2:] must be "7e1p"
                                    break; // goto 190
                                }
                            }

                            if (j == array_v5.length)    // not from "goto"
                            {
                                this.b.sendEmptyMessage(0);
                                return;
                            }
                        }

                        // 190
                        if (array_v2 == null)
                        {
                            continue; // goto 13f
                        }
                        else
                        {
                            this.b.sendEmptyMessage(1);
                            return;
                        }
                    }


                }
                else
                {
                    // cond_192
                    this.b.sendEmptyMessage(1);
                    return;
                }
            }

            //this.b.sendEmptyMessage(0);
            //return;
        }
        catch (Exception err)
        {
            // fe
            err.printStackTrace();
            return;
        }

    }
[/FONT]


1. class e

[FONT="Courier New"]
public class e {
    static Map a;
    static
    {
        HashMap map = new HashMap<String, String>();
        a("a", ". _");
        a("b", "_ . . .");
        a("c", "_ . _ .");
        a("d", "_ . .");
        a("e", ".");
        a("f", ". . _ .");
        a("g", "_ _ .");
        a("h", ". . . .");
        a("i", ". .");
        a("j", ". _ _ _");
        a("k", "_ . _");
        a("l", ". _ . .");
        a("m", "_ _");
        a("n", "_ .");
        a("o", "_ _ _");
        a("p", ". _ _ .");
        a("q", "_ _ . _");
        a("r", ". _ .");
        a("s", ". . .");
        a("t", "_");
        a("u", ". . _");
        a("v", ". . . _");
        a("w", ". _ _");
        a("x", "_ . . _");
        a("y", "_ . _ _");
        a("z", "_ _ . .");
        a("2", ". _ _ _ _");
        a("1", ". . _ _ _");
        a("3", ". . . _ _");
        a("4", ". . . . _");
        a("0", ". . . . .");
        a("6", "_ . . . .");
        a("9", "_ _ . . .");
        a("8", "_ _ _ . .");
        a("7", "_ _ _ _ .");
        a("5", "_ _ _ _ _");
    }

    static void a(String key, String value)
    {
        value = value.replaceAll(" ", "");
        a.put(key, value);
    }

    public String a(String code) throws Exception
    {
        if(code.equals("...___..."))
            return "sos";

        StringBuilder sb = new StringBuilder();
        String[] components = code.split("\\s+");
        int len = components.length;

        try {
            for (int i = 0; i < len; i++) {
                String comp = components[i];
                String decoded = (String) a.get(comp);
                sb.append(decoded);
            }

            return sb.toString();
        }
        catch (Exception err)
        {
            throw err;
        }
    }
}
[/FONT]
2015-1-25 22:05
0
游客
登录 | 注册 方可回帖
返回
//