[Android] Dexguard分析&钛备份破解

信息安全 4653 Views

钛备份,貌似是备份方向很火的一个软件(可能类似于win下的super recovery)。Claud说是加的Dexguard壳,由于本人是初学者,也没见过什么世面,更不懂得Dexguard是个什么东西了,就一股子蛮劲,结果不小心把钛备份干掉了,看来功夫不负有心人啊。以下写出心得和大家分享,互相学习进步。

菜鸟第一次分析,请大牛勿喷,会打击小菜的积极性的!

一、APKtools反编译

可能是Dexguard壳子利用了apktoolsbug吧,反正我是没有反编译成功。AndroidManifest.xml里所有不在<intent-filter></intent-filter>的元素key值都没有反编译出来。

如图所示:

修复后如下:

但是还是无法打包。用apktools还是打包失败:

既然无法打包,很多方法就行不通了,log的方法也失效了,不过不要紧,反编译失败导致无法正常打包,那就用点原始的方法吧。不管那么多,先分析分析这个程序再说,程序分析透了,至少也算是对自身的一种提高,说干就干。(这个问题现在还没有解决,希望会的大牛告知一下,在下感激不尽。)

一、逆向分析

虽然说打包是没搞定,但主要是因为xml文件apktools分析出错的原因,还好不影响我们亲爱的smali文件的反编译。JEB载入,(这里要感谢SCZ大神的无私奉献,虽然说好像保存功能用不了,但是已经非常感谢了)随便翻了一下,看见一个这个:

我本来准备吐槽作者了,但是往后一翻,我觉得这个也不必怎么大惊小怪。因为对于第一次分析APK,发现如下的东西,我开始有点不淡定了。类名和函数的名称被加密成这样:

不过不管它多么花,也要干掉它!

那就要开始定位关键地方,进行破解了。

2.1.关键定位

我目前知道的一共有2种方法可以成功定位到关键地方。

第一种方法:搜Strings.

如图:

这是一种方法,当然可能搜到的不止一处,这就需要自己去甄别了。

第二种方法:这种方法在我之前的一篇学习笔记里也有讲到,地址:http://bbs.pediy.com/showthread.php?t=195202。里面的第四种方法,所谓的“遗留下的宝藏”。用到了DDMS 如图:

OK,真幸运,直接定位到了fs中。Nice

2.2log分析

既然发现了验证的类,那就不能放过。发现大部分字符串都被加密过了,我们就从log开始着手吧。找了几个log如下:

发现了解密函数,把它Rename:

解密函数如下:

找到了解密函数,这就好办了,先把strings解密了再说,我写了一个python脚本来解密encryptStringsPython脚本如下:

#Author Erickyimport sysimport osimport timefrom jeb.api import IScriptfrom jeb.api import EngineOptionfrom jeb.api.ui import Viewfrom jeb.api.dex import Dexfrom jeb.api.ast import Class, Field, Method, Call, Constant, StaticField, NewArrayencbytes = [69, 21, -111, -111, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -36, -45, -4, -1, -4, 4, -5, 83, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 42, 25, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -55, -12, -12, 2, 4, 2, -20, 10, -6, 6, 70, -71, -13, 2, 1, 76, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 68, -15, -1, -1, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -51, -20, -15, 2, 0, 0, -6, 13, 68, -74, -11, 82, -79, -2, -6, 83, -87, 20, -12, 2, 4, 67, -66, -14, -12, 11, -3, -4, 12, 54, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -51, -20, -15, 2, 0, 0, -6, 13, 68, -88, 13, -4, -1, 75, -84, -2, 10, -4, -1, 75, -83, 12, -9, 11, -9, -6, 77, -87, 20, -12, 2, 4, 67, -71, -10, -4, 81, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -51, -20, -15, 2, 0, 0, -6, 13, 68, -74, -11, 82, -84, -2, 10, -4, -1, 75, -87, 16, -14, -8, 88, -71, -13, 12, -15, 10, 57, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -52, -15, -22, 12, -6, 6, 70, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 42, 25, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -36, -31, -3, -6, 2, 68, -87, 20, -12, 2, 4, 36, 30, -30, 28, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -51, -20, -15, 2, 0, 0, -6, 13, 68, -88, 13, -4, -1, 75, -67, -4, 68, -83, 12, -2, -13, 12, -15, 10, 2, 0, 67, -67, -4, 1, 1, -21, 1, 13, 68, -80, -8, 16, -14, 81, -18, 1, -5, 0, 17, -80, 8, 69, -74, -12, 0, 82, -87, 20, -12, 2, 4, -6, -12, -6, 88, -69, -18, 2, 16, -20, 10, -7, 0, 77, -74, -11, 82, -70, -8, 10, -16, -4, 13, 0, 53, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -47, -34, 78, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 42, 25, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -37, -34, -8, 6, -16, 10, -6, 6, 70, -79, -2, 0, 64, -74, 20, -12, 2, 4, 67, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 42, 25]class Mydecypt(IScript): def run(self, jeb): self.jeb = jeb self.dex = self.jeb.getDex() self.cstbuilder = Constant.Builder(jeb) self.csig = 'fs' self.encbytes = encbytes self.mname_decrypt = None r = jeb.decompileClass(self.csig) decrypted_string = self.decrypt(66, 26, 0) #Here enter your encrypt strings print ' Decrypted string: %s' % repr(decrypted_string) def decrypt(self, length, curChar, pos): length = 93 - length pos = pos * 2 + 91 curChar = 378 - curChar r = '' for i in range(length): curChar +=1 r += chr(pos & 0xFF) if i >= len(self.encbytes): break curEncodedChar = self.encbytes[curChar] pos = pos - curEncodedChar -1 return r

把字符串解密了,那就事半功倍了。具体的分析就比较简单了。分析解密后如下:

private static final BigInteger 大整数; private static final byte[] 加密字符串; public static boolean 普通版开关; public static boolean 专业版开关; public static hG HG类; private static final String 字符串常量; private static int 常数; private static boolean modaco隐藏版本开关; static { fs.加密字符串 = new byte[]{69, 21, -111, -111, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -36, -45, -4, -1, -4, 4, -5, 83, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 42, 25, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -55, -12, -12, 2, 4, 2, -20, 10, -6, 6, 70, -71, -13, 2, 1, 76, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 68, -15, -1, -1, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -51, -20, -15, 2, 0, 0, -6, 13, 68, -74, -11, 82, -79, -2, -6, 83, -87, 20, -12, 2, 4, 67, -66, -14, -12, 11, -3, -4, 12, 54, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -51, -20, -15, 2, 0, 0, -6, 13, 68, -88, 13, -4, -1, 75, -84, -2, 10, -4, -1, 75, -83, 12, -9, 11, -9, -6, 77, -87, 20, -12, 2, 4, 67, -71, -10, -4, 81, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -51, -20, -15, 2, 0, 0, -6, 13, 68, -74, -11, 82, -84, -2, 10, -4, -1, 75, -87, 16, -14, -8, 88, -71, -13, 12, -15, 10, 57, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -52, -15, -22, 12, -6, 6, 70, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 42, 25, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -36, -31, -3, -6, 2, 68, -87, 20, -12, 2, 4, 36, 30, -30, 28, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -51, -20, -15, 2, 0, 0, -6, 13, 68, -88, 13, -4, -1, 75, -67, -4, 68, -83, 12, -2, -13, 12, -15, 10, 2, 0, 67, -67, -4, 1, 1, -21, 1, 13, 68, -80, -8, 16, -14, 81, -18, 1, -5, 0, 17, -80, 8, 69, -74, -12, 0, 82, -87, 20, -12, 2, 4, -6, -12, -6, 88, -69, -18, 2, 16, -20, 10, -7, 0, 77, -74, -11, 82, -70, -8, 10, -16, -4, 13, 0, 53, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -47, -34, 78, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 42, 25, 14, -30, 5, -3, -10, -6, 9, -6, 6, 9, 60, -37, -34, -8, 6, -16, 10, -6, 6, 70, -79, -2, 0, 64, -74, 20, -12, 2, 4, 67, -68, 1, -3, -6, 2, 68, -71, -4, -4, 6, 42, 25}; fs.常数 = 245; boolean v0 = !fs.class.desiredAssertionStatus() ? true : false; fs.ʼ = v0; fs.字符串常量 = fs.class.getName(); fs.普通版开关 = false; fs.专业版开关 = false; fs.HG类 = null; fs.modaco隐藏版本开关 = false; fs.大整数 = BigInteger.ONE.shiftLeft(16).add(BigInteger.ONE);

值得注意的地方是这个函数:

private static void 关键函数(Runnable arg3, boolean arg4) { if(arg4) { fs.ˊ(true); fs.专业版开关 = true; fs.HG类 = new hG(); } else { fs.ˊ(fs.ˊ(MainApplication.ʾ)); boolean v0 = !fs.modaco隐藏版本开关 || !"95116f196c3b".equals(fs.HG类.get("keyId")) ? false : true; fs.普通版开关 = v0; } arg3.run();//很明显了吧 }

分析好的fs类我会打包在附件中。

三、破解

3个开关,事情就变得很简单了,改几个字节就行了。找到类中的开关,打开交叉引用:

找到每一个地方,稍微分析一下,把需要改的置真即可。当然你不怕麻烦的话,可以每一处都置“1”,也是一样的。

看了吾爱的一篇文章说修改之后,会弹出版本不正确。打开APK中的so,你会看到一个mprotect的函数,根据名字我猜的话应该是so里面的MD5完整性校验,但是奇怪的是,由于我更改的是几个“开关”,貌似dexguard对开关,或者说是声明函数里的东西是不检查的,因此这种方法直接避过了dexguard 6.0anti-tampering check.不仅省去了逆SO的时间和精力,动态加载也不用去担心,因为找到的这块是风水宝地啊,直接避开了dexguard的检测,当然也有可能是我直接改的dex文件里的opcode,可能dexguarddex的检测只是依赖与dex自身的SHA-1值和签名值。

刚才说了打包不了,怎么办呢?我的方法是直接解压apk,用IDA load dex,然后在IDA中定位到具体的offset,然后用16进制工具直接找到offset直接修改dexopcode即可。还是很期待有人指点下过dexguard反编译方法啦,我自己当然也会继续研究的。

如图:

四、总结

11.22号开始看丰生强的那本入门书,这个程序3天分析得差不多了,也算是对最近20天自己的一个交代。这个程序真的很有意思,除了表面上的2个版本之外后来还发现有一个隐藏的版本,真的是耐人寻味啊。从中加强了自己的逆向分析,同时也了解到了作者的一些验证思路。本来找到了keygen算法(第一个函数)想分析算法,patch之后做一个keygen出来,但是因为实力有限吧,最后还是放弃了。因为JEB不能保存的缘故,这一次fs里面分析的东西是临时做的,难免有疏漏,还望各位见谅。

在这里衷心谢谢非虫的书,越反复看觉得越写得好,虽然有的地方有一些冗长。也感谢论坛上的一些文章,认真读了,很有收获。接下来还是要继续学习吧,不过基础还是一定要打牢!

总的来说,收获还是挺大的,学到了很多东西。欢迎大家和我一起交流,一起进步。新手难免没有纰漏,失敬之处还望各位大侠多多指点。

(*^__^*)

By Ericky

2014.12.9

–推荐给朋友

公众微信号:吾爱破解论坛

或搜微信号:pojie_52

–内容分享

点击右上角“…”标志,分享到朋友圈

–更多精彩内容

请点击右上角“…”标志,点击“查看公众号”,往下拉一点再点击“查看历史消息”即可!

–官方论坛

www.52pojie.cn

如未说明则本站原创,转载请注明出处:NULL » [Android] Dexguard分析&钛备份破解