首页
社区
课程
招聘
[原创]对某APK的一次分析
发表于: 2012-5-18 23:48 39976

[原创]对某APK的一次分析

2012-5-18 23:48
39976

第一次发非求助帖,没啥技术含量,老鸟请无视
一直是在Windows上学习软件安全的,这几天在使用一个安卓上的软件,带有注册功能,突然心血来潮,就想拿来分析一下,其实我Java都没学过,也是从来没有在安卓上写过软件,实现一个对话框都要百度,查N久的SDK文档。再加上分析的第一个软件就是经过混淆处理的,于是乎自然而然的就会遇到很多困难,折腾了几天原来的激情也没有了,就此作罢,但是因为查资料花了很大力气,所以但是还是这几天得到的一些东西记下来吧,让其他的人能够不用再浪费那么多时间。
一,工具分析当然少不了得力的工具,像在Windows上,要是少了OD和IDA等众多出色的工具,那就举步维艰了,这里列出几个我试过的分析安卓Apk的工具:
1, apktool,smali,baksmali:
https://code.google.com/p/android-apktool/
apktool是基于smali的项目,提供APK代码,资源反编译+编译一条龙服务,还是很方便的,不过看到有人说这是土八路用的玩意儿,反编译来的代码是smali文件,详细的语法请参考https://code.google.com/p/smali/.下面是效果图:



2, dextojar + jd-gui:
http://code.google.com/p/dex2jar/
http://java.decompiler.free.fr/?q=jdgui
首先用把编译好的dex文件转为class文件,然后用jd-gui可以转为java源文件,不过有时错误很多,看到有人说是因为dex2jar不能正确处理循环导致的,效果如下:



3 ,dextojar + DJ这个跟上面那个差不多,基于dextojar,所以也会继承上面的错误,不过JD反编译处理的代码带很多goto和标号,值得一提的是,DJ虽然是个收费软件,但是其目录下的JAD文件实现了它的全部功能,可以直接命令行操作,效果如下:


百度基本上搜出的都是上面那几个了,下面还有些google的结果。

4,ddx:网址忘记存了,大家google一下吧,这个是个和smali独立的项目,语法和smali类似,都是虚拟字节码,效果如下:



5,ded
http://siis.cse.psu.edu/ded/index.html#banner
这个是我比较推荐的工具,也是提供直接从apk到java源代码的一条龙服务,而且可读性非常好,那时相当的舒服,不过有些方法无法反编译,这就要借助上面其他的工具了,如果如下:


6,AndroChef Java Decompiler
这个忘记说了,也是很好用的。
好,工具介绍完了,下面来实战一下。
一.5 调试工具
增加一节,有点乱七八糟,但是想到什么就马上去实现,这也是个优点,不是么?呵呵。世界就是那么神奇。
调试的话推荐NetBean + DDMS,没什么好说的,apktool网站上摆着一个smali调试,但是发布的版本和视频教程里面不一样,不懂他是何居心。难道是为了apk的安全着想?所以没有测试成功,还有 古河 兄说的AndBug也试了一下,没有脚本而单独使用工具自身提供的几个命令的话,那个还是纠结的,又不能单步,在方法上下断还不一定成功,参数不合规范立马罢工,这个还是比较顽皮的。
二,实战
其实这也没有什么好说的,都直接反编译成源代码了,爱干啥干啥,虽然我没学过Java,但是这一点也不能妨碍我理解它呀,都是高级语言,长得差不多
这里跟Windows不同的是,程序设计的时候是面向对象的,反编译以后呈现的结果也还是面向对象的。
程序可以选择一个Key文件进行导入,找到它的onitemclick()事件:
public final void onItemClick(AdapterView var1, View var2, int var3,
                        long var4)
        {
                w var6 = (w) this.b.b.elementAt(var3);
                //如果点击的是上一层目录
                if (var6.b.equals(".."))
                {
                        this.c((new File(this.b.a)).getParent());
                        this.e.setText(this.b.a);
                        this.e.requestLayout();
                        this.m.notifyDataSetChanged();
                }
                //如果点击的是目录
                else if (var6.a)
                {
                        this.c((new File(this.b.a, var6.b)).getPath());
                        this.e.setText(this.b.a);
                        this.e.requestLayout();
                        this.m.notifyDataSetChanged();
                }
                else
                {
                        this.d.dismiss();        //销毁对话框
                        if (this.g != null)
                        {
                                File var7 = new File(this.b.a, var6.b);
                                this.g.a(var7.getPath());        //调用a处理
                        }
                }

        }
代码经过混淆处理,名字变成abcdefg没有意义了有些不好懂,但是借助IDE慢慢分析改过来就行了。选择目录以后调用this.g.a(string),这里前面有private ac g = null;可是ac是个接口而已,
public interface ac {

   void a(String var1);
},这就有点为难了,我的办法是查看所有实现了这个接口的类,很低级的哈,然后发现一个可疑的类,其中方法为:
public final void a(String filepath)
        {
                try
                {
                        Context context = this.a.getContext();
                        FileInputStream fileinput = new FileInputStream(filepath);
                        byte[] buffer = new byte[1024];
                        int nbufferread = fileinput.read(buffer);
                        fileinput.close();
                        if (nbufferread > 0)
                        {
                                ///license.dat
                                //把key的内容写进/license.dat
                                FileOutputStream fileoutput = new FileOutputStream(ai.getKeyFilepath(context, "",true));        //静态方法
                                fileoutput.write(buffer, 0, nbufferread);
                                fileoutput.flush();
                                fileoutput.close();
                        }

                        d.a(this.a.getContext(), GkMainView.a(this.a).c());
                }
                catch (Exception var8)
                {
                        d.a(this.a.getContext(), var8);
                }

        }
现在我们再看d.a(Context  context, boolean  ifnull);
public static void a(Context  context, boolean  ifnull) throws Throwable
   {

       Object n0;
       byte traChinese, sinChinese;
       boolean bRegist;
       char firstbyte;
       String szWarning, r7, r8, szifregist, r11, r18, szIMEI, szMISI, _szIMEI, _szMISI, r27, r29, r59, $r73;
       WebView webview;
       AlertDialog.Builder alerdialogbuilder;
       n0 = null;
       traChinese = (byte) (byte) 1;
       bRegist = (new ai()).a(context);      //主要就是这个返回了
       firstbyte = context.getString(2131099663).charAt(0);

       if (firstbyte != '\u5173')        //简体中文
       {
           sinChinese = (byte) (byte) 0;
       }
       else
       {
           sinChinese = (byte) (byte) 1;
       }

       if (firstbyte != '\u95dc')        //繁体中文
       {
           traChinese = (byte) (byte) 0;
       }

       r7 = "";

       if (sinChinese == (byte) 0)        //不是简体中文
       {
}
.........
}
下面是根据是否注册及本机语言呈现不同的用户界面而已,现在让我们把精力放到主要的那个方法那里,a(Context context),
public final boolean a(Context context) throws Throwable
        {
                boolean bresult = true;
                int var3 = 0;

                while (true)        //while( i < 2)
                {
                        if (var3 >= 2)
                        {
                                bresult = false;
                                break;
                        }

                        String szIMEI = cn.maocai.components.am.GetIMSI_(context);
                        //IEMI码不为空的话
                        if (szIMEI != null && szIMEI.length() > 0)
                        {
                                boolean bresult_;
                                if (var3 == 0)
                                {
                                        bresult_ = bresult;
                                }
                                else
                                {
                                        bresult_ = false;
                                }

                                //第一遍的时候寻找/data/data/.../license.dat
                                //第二遍为SD卡
                                if (this.a(getKeyFilepath(context, szIMEI, bresult_), szIMEI))
                                {
                                        break;
                                }
                        }

                        String szIMSI = cn.maocai.components.am.getIMSI(context);
                        if (szIMSI != null && szIMSI.length() > 0)
                        {
                                boolean var6;
                                if (var3 == 0)
                                {
                                        var6 = bresult;
                                }
                                else
                                {
                                        var6 = false;
                                }
                                                                //这里
                                if (this.a(getKeyFilepath(context, szIMSI, var6), szIMSI))
                                {
                                        break;
                                }
                        }

                        ++var3;
                }

                return bresult;
        }
现在再看:
private boolean a(String keyfilepath, String szdevicesID) throws java.lang.Throwable
        {

                boolean bResult, $z4;
                java.io.FileInputStream fileinput1, keyfileinput;
                int navailable, i5;
                byte[] bytekeybuffer, decryptKeybuf;
                X509EncodedKeySpec x509key;
                PublicKey publickey;
                Cipher cipher;
                String sttraKey, r29;
                Throwable r38;
                bResult = false;

                //如果在黑名单中就一切免谈了
                label_6:
                if (ai.checkblacklist(szdevicesID) == false)
                {
                        fileinput1 = null;

                        label_5:
                        {
                                label_4:
                                {
                                        label_3:
                                        {
                                                label_2:
                                                {
                                                        try
                                                        {
                                                                //处理一下文件名
                                                                if ((new File(keyfilepath)).exists() == false)
                                                                {
                                                                        if (keyfilepath.endsWith(".dat") != false)
                                                                        {
                                                                                break label_6;
                                                                        }
                                                                        else
                                                                        {
                                                                                keyfilepath = (new StringBuilder())
                                                                                                .append(keyfilepath)
                                                                                                .append(".dat")
                                                                                                .toString();
                                                                        }
                                                                }

                                                                keyfileinput = new FileInputStream(keyfilepath);
                                                        }
                                                        catch (Exception $r33)
                                                        {
                                                                break label_2;
                                                        }
                                                        catch (Throwable $r37)
                                                        {
                                                                r38 = $r37;
                                                                keyfileinput = fileinput1;
                                                                break label_3;
                                                        }

                                                        try
                                                        {
                                                                navailable = keyfileinput.available();
                                                                //可读字节不能为0
                                                                if (navailable <= 0)
                                                                {
                                                                        break label_5;
                                                                }
                                                                else
                                                                {
                                                                        bytekeybuffer = new byte[navailable];
                                                                       
                                                                        keyfileinput.read(bytekeybuffer);
                                                                        keyfileinput.close();
                                                                        //得到公钥
                                                                        x509key = new X509EncodedKeySpec(statsbytes);
                                                                        publickey = KeyFactory.getInstance("RSA").generatePublic(x509key);
                                                                        cipher = Cipher.getInstance("RSA");
                                                                        //解密模式
                                                                        cipher.init(javax.crypto.Cipher.DECRYPT_MODE ,publickey);
                                                                       
                                                                        //解密字串
                                                                        decryptKeybuf = cipher.doFinal(bytekeybuffer);
                                                                        //解密密后的密码转成字符串
                                                                        sttraKey = new String(
                                                                                        decryptKeybuf,
                                                                                        0,
                                                                                        decryptKeybuf.length,
                                                                                        "UTF-8");
                                                                        i5 = sttraKey.indexOf("GameKiller\nIMEI\n");

                                                                        if (i5 >= 0)
                                                                        {
                                                                                a = sttraKey.substring(i5);
                                                                        }

                                                                        r29 = (new StringBuilder(
                                                                                        "GameKiller\nIMEI\n"))
                                                                                        .append(szdevicesID)
                                                                                        .append("\n")
                                                                                        .toString();
                                                                        $z4 = a.startsWith(r29);
                                                                        break label_4;
                                                                }
...........
返回 $z4;
}
//看到这里是不是感觉被忽悠了哈,嘿嘿,他用私钥加密注册信息生成key然后程序用公钥解密后对比,所以写注册机还是算了吧。

三,安卓平台上的软件安全
感觉革命尚未成功,同志仍须努力,这个问题需要解决的东西应该很多吧,包括google该做的事情。开发者请参考ZhWeir兄的帖子
【原创】APK反破解之一:Android Java混淆(ProGuard)http://bbs.pediy.com/showthread.php?t=137112
【原创】APK反破解之二:Android APK 签名比对
【转帖】APK反破解之三:NDK编译.so动态库
四,后记
   最后抱怨一下潜水了很久,发了两个求助帖,竟然一个回复都没有,难免有点心寒,因为我发帖不符合规范?太简单?缺少百度?还是因为我是临时会员?
   最后申请转正,希望版主批准。


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

收藏
免费 6
支持
分享
最新回复 (20)
雪    币: 278
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
大家都像你一样在潜水...呵呵  我来给你顶下
2012-5-20 01:05
0
雪    币: 210
活跃值: (207)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
我删了,兄弟,早点休息~~
2012-5-20 23:25
0
雪    币: 27
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
dex2jar反编译的循环已经折磨死我了。。。。ded是只有linux版本的么。。。
2012-5-21 17:02
0
雪    币: 29
活跃值: (60)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
太强大可 什么时候我看也可以像这样
2012-5-21 23:51
0
雪    币: 168
活跃值: (17)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
很强大啊。这个看起来流程很清晰的了。
2012-5-22 16:43
0
雪    币: 31
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
看不懂的菜鸟 帮顶
2012-5-23 11:46
0
雪    币: 210
活跃值: (207)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
上面有一个工具是可以明确的解析出循环的,
像while(true)
{
if(xx>i)
break;
xx++;
}
它可以明确的解析为
while(xx<i)
{
i++;
}
具体是哪个忘记了,兄弟你试一下吧。
2012-5-23 12:45
0
雪    币: 27
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
你是说ded和AndroChef Java Decompiler么?
ded没有win版本的。。AndroChef Java Decompiler免费的可以用10次。按你描述的功能我感觉这俩工具用的是同一种方式反编译的,有些函数无法反编译。
说回来AndroChef Java Decompiler的反编译为神马会把.java都放在jar里面= =,我一开始还以为文件内容是空的。。。经验主义害死人啊。。。
2012-5-24 09:33
0
雪    币: 88
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
新手学到很多,感谢楼主的分析。
2013-10-30 17:13
0
雪    币: 6
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
哈哈希望楼主还能提供更多更高级的东东
2013-10-30 19:01
0
雪    币: 158
活跃值: (206)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
apktool网站上摆着一个smali调试,但是发布的版本和视频教程里面不一样,不懂他是何居心。难道是为了apk的安全着想? 这个作者说了 有bug 不完善 去掉了 。在2.0版本会提供支持,也就是说还需要等一段时间
2013-10-31 08:54
0
雪    币: 2832
活跃值: (1080)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
正接触学习学习
2015-12-16 20:55
0
雪    币: 1822
活跃值: (1050)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
感谢楼主分享,学习学习
2016-10-22 16:36
0
雪    币: 36
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
感谢楼主的分享!
2017-7-13 15:26
0
雪    币: 2465
活跃值: (10757)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
分析流程很清晰啊,精神可嘉
2017-7-14 14:49
0
雪    币: 1110
活跃值: (569)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
17
我是不是来晚了,有没有环境可以进行练习一下呀
2017-12-21 17:24
0
雪    币: 23
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
刚刚开始学习,,脑袋看大了啊
2017-12-27 14:00
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
潜水中,看看
2019-5-21 09:11
0
雪    币: 367
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
潜水中,学习学习
2020-4-2 16:20
0
雪    币: 190
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
也刚接触,一头雾水中,还是需要多实践。
2020-12-28 09:15
0
游客
登录 | 注册 方可回帖
返回
//