吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 26414|回复: 77
收起左侧

[移动样本分析] 阿拉伯加密型锁机软件分析

  [复制链接]
云在天 发表于 2018-8-6 23:59
使用论坛附件上传样本压缩包时必须使用压缩密码保护,压缩密码:52pojie,否则会导致论坛被杀毒软件等误报,论坛有权随时删除相关附件和帖子!
病毒分析分区附件样本、网址谨慎下载点击,可能对计算机产生破坏,仅供安全人员在法律允许范围内研究,禁止非法用途!
禁止求非法渗透测试、非法网络攻击、获取隐私等违法内容,即使对方是非法内容,也应向警方求助!
本帖最后由 云在天 于 2018-8-6 09:10 编辑

Android 阿拉伯加密型锁机软件分析

作者:云在天(Harry)
发布平台:吾爱破解论坛
转载请说明出处

基本信息

文件名:com.tianlan.qq1247319341
MD5:688ea7a90e005a31ab41d79653961818   

病毒行为

  1. 由任意宿主程序释放该病毒(实机测试并无Root权限申请)
  2. 释放后运行该病毒并重启
  3. 创建一个类悬浮窗的VIew来达到锁机的目的

逆向分析

难点

  1. 因为宿主程序加壳,在不好脱壳的情况下可以模拟器或实机运行后在ADB命令的辅助下结束该病毒进程&得到病毒文件这也是比较通用的解除锁机病毒的操作。  
  2. 在得到病毒样本的前提下,载入任意安卓逆向分析软件(JEB,JD-gui,Android-killer等),发现类名方法名均为类似阿拉伯语的文字,我们知道像阿拉伯这种语言的阅读顺序都是从右向左读,所以给分析带来了一定困难。   
  3. 可能会存在网络读取或MD5解密,在之前的帖子中有提到。
  4. Last but not least, 你没有足够的耐心。
    PS:本文完成于1万米的高空,所以无图,分析均为文字,希望论坛支持阿拉伯文的显示   

0x1 获得病毒样本

首先运行模拟器或实机,实机一定要打开USB调试或按照第三方Recover(TWRP等)。
连接电脑,打开ADB,输入adb shell pm list packages -f并复制到文本文档
然后运行宿主程序,再次输入命令与之前的文本对比,得到释放的病毒文件及APK路径
输入adb shell am force-stop 包名 结束病毒进程
输入adb pull APK路径 本地路径 把病毒下载到电脑中,这样就完成了获取病毒样本。

0x2 获取关键smali

将病毒样本载入Android-Killer中查看入口
搜索关键字removeview定位关键smali
在此次的样本中关键smali为 Lݕݔݤݔݑݑݧ
找到关键字所在,是几个连续的if语句
以下代码均为JEB2中生成的伪代码      

if(!TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).getText().toString().isEmpty()) {
            if(TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).getText().toString().equals(String.valueOf(ݑݛݚݑݫݢݞ.ݐݥݫݑݤݟݖ(new byte[]{21, 80, 22, 81, 21, 65, 22, 68, 21, 80, 89, 97, 21, 66, 73, 102, 42})))) {
                TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).setText(ݖݓݒݖݥݞݣ.ݐݥݫݑݤݟݖ(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݓݖݧݠݡݬݛ).toString()));
                TL.ݑݪݚݨݣݬݞ(this.ݐݥݫݑݤݟݖ).setText(ݑݛݚݑݫݢݞ.ݐݥݫݑݤݟݖ(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 85, 69, 83, 82, 111, 115, 22, 74, 111, 119, 88, 71, 71, 29, 29, 42}));
            }
            else if(TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).getText().toString().equals(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݒݨݓݓݭݠݑ).toString())) {
                TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).setText(ݖݓݒݖݥݞݣ.ݐݥݫݑݤݟݖ(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݔݨݛݟݫݪݧ).toString()));
                TL.ݑݪݚݨݣݬݞ(this.ݐݥݫݑݤݟݖ).setText(ݑݛݚݑݫݢݞ.ݐݥݫݑݤݟݖ(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 85, 69, 83, 82, 111, 115, 20, 73, 69, 119, 88, 71, 71, 29, 29, 42}));
            }
            else if(TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).getText().toString().equals(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݓݖݧݠݡݬݛ).toString())) {
                TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).setText(ݖݓݒݖݥݞݣ.ݐݥݫݑݤݟݖ(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݕݣݤݣݤݬݦ).toString()));
                TL.ݑݪݚݨݣݬݞ(this.ݐݥݫݑݤݟݖ).setText(ݑݛݚݑݫݢݞ.ݐݥݫݑݤݟݖ(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 85, 69, 83, 82, 111, 119, 66, 77, 11, 119, 88, 71, 71, 29, 29, 42}));
            }
            else if(TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).getText().toString().equals(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݑݪݚݨݣݬݞ).toString())) {
                TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).setText(ݖݓݒݖݥݞݣ.ݐݥݫݑݤݟݖ(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݖݡݫݒݚݓݠ).toString()));
                TL.ݑݪݚݨݣݬݞ(this.ݐݥݫݑݤݟݖ).setText(ݑݛݚݑݫݢݞ.ݐݥݫݑݤݟݖ(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 85, 69, 83, 82, 111, 115, 22, 76, 111, 119, 88, 71, 71, 29, 29, 42}));
            }
            else if(TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).getText().toString().equals(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݐݥݫݑݤݟݖ).toString())) {
                TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).setText(ݖݓݒݖݥݞݣ.ݐݥݫݑݤݟݖ(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݗݣݔݒݛݤݪ).toString()));
                TL.ݑݪݚݨݣݬݞ(this.ݐݥݫݑݤݟݖ).setText(ݑݛݚݑݫݢݞ.ݐݥݫݑݤݟݖ(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 73, 100, 77, 78, 105, 100, 76, 75, 105, 23, 75, 85, 105, 100, 76, 83, 121, 105, 29, 42}));
            }
            else if(TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).getText().toString().equals(ݗݬݐݣݜݝݠ.ݑݪݚݨݣݬݞ(new StringBuffer().append("").append(this.ݐݥݫݑݤݟݖ.ݔݨݛݟݫݪݧ).toString()))) {
                TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ).removeView(TL.ݐݥݫݑݤݟݖ(this.ݐݥݫݑݤݟݖ));
            }
        }

乍一看就想放弃了,这里我们可以利用JEB的特性把类名方法名重命名即可,双击进入每一个调用的方法名,右键Rename即可。这里可以根据命令和控件类型进行重命名。结果如下

if(!TL.Input(this.TL_public).getText().toString().isEmpty()) {
            if(TL.Input(this.TL_public).getText().toString().equals(String.valueOf(Base64.decode(new byte[]{21, 80, 22, 81, 21, 65, 22, 68, 21, 80, 89, 97, 21, 66, 73, 102, 42})))) {
                TL.Lable(this.TL_public).setText(Public.text(new StringBuffer().append("").append(this.TL_public.a).toString()));
                TL.lable_TL(this.TL_public).setText(Base64.decode(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 85, 69, 83, 82, 111, 115, 22, 74, 111, 119, 88, 71, 71, 29, 29, 42}));
            }
            else if(TL.Input(this.TL_public).getText().toString().equals(new StringBuffer().append("").append(this.TL_public.a1).toString())) {
                TL.Lable(this.TL_public).setText(Public.text(new StringBuffer().append("").append(this.TL_public.b).toString()));
                TL.lable_TL(this.TL_public).setText(Base64.decode(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 85, 69, 83, 82, 111, 115, 20, 73, 69, 119, 88, 71, 71, 29, 29, 42}));
            }
            else if(TL.Input(this.TL_public).getText().toString().equals(new StringBuffer().append("").append(this.TL_public.b1).toString())) {
                TL.Lable(this.TL_public).setText(Public.text(new StringBuffer().append("").append(this.TL_public.c).toString()));
                TL.lable_TL(this.TL_public).setText(Base64.decode(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 85, 69, 83, 82, 111, 119, 66, 77, 11, 119, 88, 71, 71, 29, 29, 42}));
            }
            else if(TL.Input(this.TL_public).getText().toString().equals(new StringBuffer().append("").append(this.TL_public.c1).toString())) {
                TL.Lable(this.TL_public).setText(Public.text(new StringBuffer().append("").append(this.TL_public.d).toString()));
                TL.lable_TL(this.TL_public).setText(Base64.decode(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 85, 69, 83, 82, 111, 115, 22, 76, 111, 119, 88, 71, 71, 29, 29, 42}));
            }
            else if(TL.Input(this.TL_public).getText().toString().equals(new StringBuffer().append("").append(this.TL_public.d1).toString())) {
                TL.Lable(this.TL_public).setText(Public.text(new StringBuffer().append("").append(this.TL_public.e).toString()));
                TL.lable_TL(this.TL_public).setText(Base64.decode(new byte[]{21, 66, 18, 116, 21, 121, 77, 110, 21, 66, 103, 99, 21, 80, 119, 87, 111, 73, 100, 77, 78, 105, 100, 76, 75, 105, 23, 75, 85, 105, 100, 76, 83, 121, 105, 29, 42}));
            }
            else if(TL.Input(this.TL_public).getText().toString().equals(MD5.encode(new StringBuffer().append("").append(this.TL_public.e1).toString()))) {
                TL.Manager(this.TL_public).removeView(TL.View_TL(this.TL_public));
            }
        }

0x3 详细分析

第一个判断是判断这个输入框的内容是否为空,不为空进入下一个判断。
第二个判断是判断输入框内容与方法base64.deocde(new byte[])的内容是否相等,这个方法名是我自己修改的,修改的依据是下面的代码

        return new String(android.util.Base64.decode(arg4, 0));

这个地方可以直接把这个方法名复制到Java的IDE里调试解码即可,解出来的第一层的密码为枪宝最帅
如果这个判断结果为true就会执行以下操作
在界面的一个标签里设置一串加密后的文本以此作为下一层密码的基础,看一下加密的代码

 int v9 = 2;
        int v8 = 10;
        int v1 = 0;
        int v7 = 5;
        byte[] v2 = arg10.getBytes();
        StringBuilder v3 = new StringBuilder(v2.length * 2);
        int v0;
        for(v0 = 0; v0 < v2.length; ++v0) {
            v3.append(Public.ݑݪݚݨݣݬݞ.charAt((v2[v0] & 15) >> 0));
        }
        String v0_1 = "";
        String[] v2_1 = new String[v8];
        v2_1[0] = Base64.decode(new byte[]{109, 97, 29, 29, 42});
        v2_1[1] = Base64.decode(new byte[]{109, 113, 29, 29, 42});
        v2_1[v9] = Base64.decode(new byte[]{109, 71, 29, 29, 42});
        v2_1[3] = Base64.decode(new byte[]{109, 87, 29, 29, 42});
        v2_1[4] = Base64.decode(new byte[]{110, 97, 29, 29, 42});
        v2_1[v7] = Base64.decode(new byte[]{110, 113, 29, 29, 42});
        v2_1[6] = Base64.decode(new byte[]{110, 71, 29, 29, 42});
        v2_1[7] = Base64.decode(new byte[]{110, 87, 29, 29, 42});
        v2_1[8] = Base64.decode(new byte[]{111, 97, 29, 29, 42});
        v2_1[9] = Base64.decode(new byte[]{111, 113, 29, 29, 42});
        String[] v4 = new String[v8];
        v4[0] = Base64.decode(new byte[]{20, 80, 103, 80, 42});
        v4[1] = Base64.decode(new byte[]{20, 80, 103, 71, 42});
        v4[v9] = Base64.decode(new byte[]{20, 80, 103, 72, 42});
        v4[3] = Base64.decode(new byte[]{20, 80, 103, 73, 42});
        v4[4] = Base64.decode(new byte[]{20, 80, 103, 74, 42});
        v4[v7] = Base64.decode(new byte[]{20, 80, 103, 75, 42});
        v4[6] = Base64.decode(new byte[]{20, 80, 103, 76, 42});
        v4[7] = Base64.decode(new byte[]{20, 80, 103, 77, 42});
        v4[8] = Base64.decode(new byte[]{20, 80, 103, 78, 42});
        v4[9] = Base64.decode(new byte[]{20, 80, 103, 79, 42});
        while(v1 < v8) {
            if(v1 == 0) {
                v0_1 = v3.toString().replace(v2_1[v1], v4[v1]);
            }
            v0_1 = v0_1.replace(v2_1[v1], v4[v1]);
            ++v1;
        }
        return v0_1;
    }

虽然都用了byte作为混淆方法之一,但并不影响解密,只需要把两个数组的顺序调换即可,即

if(v1 == 0) {
                v0_1 = v3.toString().replace(v2_1[v1], v4[v1]);
            }
            v0_1 = v0_1.replace(v2_1[v1], v4[v1]);
            ++v1;

替换成     

 if(v1 == 0) {
                v0_1 = v3.toString().replace(v4[v1], v2_1[v1]);
            }
            v0_1 = v0_1.replace(v4[v1], v2_1[v1]);
            ++v1;

结果就是1-9阿拉伯数字替换成①-⑨,0替换成⑩
这里加密的是a的值,a是7位随机数
下一层判断的是a1的值,a1的值是a的值+8,以下的bcde与a相同,代码如下

        this.b = ((int)(Math.random() * (((double)10000000))));
        this.c = ((int)(Math.random() * (((double)10000000))));
        this.d = ((int)(Math.random() * (((double)10000000))));
        this.e = ((int)(Math.random() * (((double)10000000))));
        this.a1 = ((long)(this.a + 8));
        this.b1 = ((long)(this.b - 1));
        this.c1 = ((long)(this.c - 4));
        this.d1 = ((long)this.d);
        this.e1 = ((long)this.e);

根据上面的代码我们可以看到除了最后一层的密码要进一步计算,其余的都与第二层类似,这里就不提了
来着重看一下最后一层,为什么我给它重命名为MD5加密呢。我们先看一下代码然后再分析一下

public static String encode(String arg4) {
        NoSuchAlgorithmException v0_1;
        String v1_1;
        int v0;
        try {
            new String(arg4);
            v0 = 5;
        }
        catch(NoSuchAlgorithmException v1) {
            NoSuchAlgorithmException v3 = v1;
            v1_1 = ((String)v0);
            v0_1 = v3;
            goto label_15;
        }
        try {
            return MD5.Byte_String(MessageDigest.getInstance(Base64.decode(new byte[]{116, 117, 113, 17, 42})).digest(arg4.getBytes()));
        }
        catch(NoSuchAlgorithmException v0_1) {
        }
    label_15:
        v0_1.printStackTrace();
        return v1_1;
    }

它最终是调用了MessageDigest.getinstance函数返回的数据,这个函数可以返回MD5,SHA等加密算法的值。
要值得注意的是,在生成MD5值的同时,把Byte转换成了String,而这个转换与平时我们用到的定义不同,会造成0-9变为9-0
如果完全复制粘贴到IDE中可能会出错,所以要把代码进行改写。
最终结果就是把e1的值MD5加密后把0-9替换成9-0就是密码
这样整个病毒就分析完了

总结

对于这种混淆,我们可以根据代码的关键词推测该方法的作用,依照自己的习惯命名,这样病毒作者本身引以为傲的阿拉伯加密手段就失效了,对于把String字符串全部由new Byte[]来代替只能增加可读性的难度,对于解密本身并不能造成根本性影响。在分析时,一定要注意阅读顺序,避免造成不必要的麻烦。

Todo: 因为宿主程序加壳,无法判断是否调用了Root权限,实机测试并没有弹出申请Root权限的弹窗。来一个大佬解惑

最后,不要贪图小便宜,不要使用各种未知来源的软件。切记!!!

免费评分

参与人数 37威望 +2 吾爱币 +99 热心值 +34 收起 理由
sqd + 1 + 1 用心讨论,共获提升!
RoB1n_Ho0d + 1 用心讨论,共获提升!
sunnylds7 + 1 + 1 热心回复!
chenliujun + 1 我很赞同!
a37324614 + 1 我很赞同!
siuhoapdou + 1 + 1 谢谢@Thanks!
xiaoming3344 + 1 + 1 热心回复!
181842 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
ghost_kl + 1 + 1 我很赞同!
圣诞猫头鹰 + 1 + 1 我很赞同!
Milonga + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
by南枭 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
shy851212 + 1 + 1 我很赞同!
zhangbaida + 3 + 1 用心讨论,共获提升!
独行风云 + 1 + 1 用心讨论,共获提升!
夏雨微凉 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zl535320706 + 1 + 1 我很赞同!
BetaMao + 1 + 1 谢谢@Thanks!
NB2665597272 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
xkq96 + 1 + 1 谢谢@Thanks!
acail + 1 + 1 用心讨论,共获提升!
notcher + 1 + 1 谢谢@Thanks!
niulaoshi + 1 我很赞同!
shadowlieren + 1 + 1 谢谢@Thanks!
回忆流水年华 + 1 + 1 我很赞同!
Peace + 52 + 1 用心讨论,共获提升!
gqdsc + 1 + 1 真是厉害极了
不想做伸手党 + 1 同样是九年义务教育,楼下的为何这么秀1
liphily + 3 + 1 跟hmily说一声加个优秀,就说是我说的
晓杰NICE + 1 + 1 我很赞同!
孤云 + 1 + 1 我很赞同!
duolouxi + 1 + 1 谢谢@Thanks!
Hmily + 2 + 10 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zhaolisheng + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
tf30 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
winopen + 1 + 1 用心讨论,共获提升!
浓痰咬不断 + 1 + 1 感谢您的宝贵建议,我们会努力争取做得更好!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 云在天 发表于 2018-12-5 02:01
电科信息士 发表于 2018-12-4 21:53
这是python吗?这好像启发了可以通过相应的置换去简单的隐藏自己的源代码。

Java。 可以通过置换隐藏,但流程逻辑不变,只是混淆了方法名而已,降低了可阅读性。
浓痰咬不断 发表于 2018-8-7 00:17
烟99 发表于 2018-8-7 00:22 来自手机
lunaaero 发表于 2018-8-7 00:23
分析的很不错了
yubo772 发表于 2018-8-7 00:26
前排围观大佬
qqxuanxuan 发表于 2018-8-7 01:13
谢谢分享
吾爱看雪 发表于 2018-8-7 01:28
前排围观大佬
studio 发表于 2018-8-7 06:45
解密个软件还需要懂阿拉伯语言
w28196535 发表于 2018-8-7 07:21
我还以为只有英语,没想到还有阿拉伯语
DA111 发表于 2018-8-7 07:41
前排支持大佬
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-25 13:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表