Antutu是用户常用的一款智能设备跑分软件。如下图所示,它从四个大类来评估一台移动设备的性能,分别是CPU、GPU、UX和MEM。Antutu最终给出总分、一级汇总得分和二级得分,人们一般习惯使用总分来量化设备间的性能。
但近期我用Antutu的Android版本做性能测试时,发现UX大类中的UX图形处理-鱼眼、Blur和JPG解码
一项分数明显比竞品低,便想查清楚原因。但此项中又包含三个子项,究竟每个子项的分数是多少呢?每个子项究竟是怎样进行评测的呢?一番网上搜寻后,我并没有找到Antutu官方出的分数计算报告白皮书,也没有在设备Log中或是其他地方找到详细分数的输出。那么似乎只能进入Antutu评测软件内部窥探一番了。
注意:破解Antutu并篡改分数等行为是被官方禁止的,本文只做Antutu分数的获取过程分享,测试用Antutu版本为V7.1.0。
0x1 梳理Antutu跑分逻辑
首先解压Aututu主apk包,并同时使用apktool
再将主apk解压反编译备用。1
> apktool.bat d "antutuv7.apk"
接下来使用dex2jar
工具将Aututu主apk包中的两个dex文件转换为jar包,并使用JD-GUI
打开分析。
搜寻后发现非常显眼的是jni
这个类。从中可以看到定义了Antutu每一个子测试的编号,能查询到我们想看到详细分数的三个子项分别是20、21和22号。并且还能看到jni
类中关联着的Native函数,其实现都位于lib\x86|armeabiv7\libabenchmark.so
中。
接下来不难发现,Antutu的跑分主流程在com.antutu.benchmark.service.BenchmarkService的f函数中,因为其中有大量的Native函数调用。
Antutu 3D跑分为第一项进行,逻辑复杂,暂时跳过分析。后面的每一项测试的大体流程为,如果该跑分项目由Native层进行测试,比如多线程测试,则先由a(int)函数通知UI线程某项跑分开始了,然后通过dc.a(int)判断该跑分是否可以在Native层执行,并等待2秒后才开始真正测试。由于此处的Java类和方法有部分做了混淆,所以需要根据每个函数的具体实现猜测其功能。
通过IDA打开libabenchmark.so
后,我们发现Native层并没有做更加完备的防护,搜索benchmarkV6
函数,便可以找到更多测试函数的入口了。
如果该跑分项目在Java层进行,则先跑分,再通过jni.benchmarkProcessUX
接口将分数导入Native层。比如测试XML(14)。
接下来我们继续研究UX图片处理的相应逻辑,我们寻找20~22编号的测试并详细查看其逻辑。
其中,JPG decode比较简单,使用Java的BitmapFactory.decodeByteArray测试。分数为5秒内decode的次数。由于是纯Java实现,我们可以将相应的代码自己实现后测试,但结果显示JPG decode并不是导致UX图像处理跑分低的根源。
而FishEye和Blur都使用了Native实现。搜索fisheye
关键字,我们很快能看到其Native测试实现。
0x2 获取Antutu 主应用的实际跑分值
虽然我们已经看到了Antutu测试的部分实现的反编译结果,但除了直接分析,还有什么样的方法能更快地获取到Antutu的详细分数呢?
一种可能的办法是自己写一个Android App,并参照Antutu的JNI接口定义来定义类,比如说定义一个com.antutu.aaa.bbb.jni
的类,并将libabenchmark.so
去出打包到我们自己的App的Apk中并申明加载。加载后既可直接调用Native方法进行测试,或者是通过恢复Antutu存放分数的文件来直接读取已经存储的分数。这种方法应该是可行的,但比较麻烦。
经过不断的搜索,我们发现Antutu的详细跑分在一个内部flag开启的时候,会自动写到/sdcard/.antutu/last_result.json
中。在跑分流程的末尾,可以发现这样一个dc.a(context)
这样一个函数,它在dc.b()
返回true
时被触发,而dc.b()
只是单纯地返回了dc.f
这样一个静态变量,并且并没有找到在其他地方有被修改。
此时,一个简单的想法便是如果能将dc.f
的默认值改为true,则可以获得这个分数了。
重新使用apktool b
指令重建Antutu的主Apk,并使用jarsign重新签名。但安装后发现Antutu卡死。
这是为何呢?寻找原因后发现Antutu在Native层做了自身的签名验证,验证不通过的话会不断sleep卡死App。
详细观察代码后发现,result需要返回0才能通过判断,而绕过验证签名的方法为将if(v21)
判断绕过即可。一个简单的办法就是将这里对应汇编的cmp操作全填nop
即可。
最终可以获取到Antutu自动打印的详细分数了。
0x3 获取Antutu 3D应用的实际跑分值
然而分析发现,主要是Fisheye得分较低。但分析Native代码无果,没有发现明显的优化点。将3D App屏蔽后,发现三者在Fisheye上分数都低了很多,并且十分接近,因此判断Fisheye分数是3D App中GPU加速的Fisheye测试与CPU Fisheye测试结合在一起得出的,而在GPU FishEye上得分低拖累了我们的分数。因此接下来我们需要继续探究Antutu 3D App。
Antutu 3D是使用Unity开发的,使用dnSpy看到可以看到其内部逻辑。
在OnTestPhysX找到了测试主逻辑。
测试方式为3秒中统计图像处理次数。
检查逻辑后还发现,Antutu对一些特性不支持时,暴力地降低分数,比如不支持computeShaders时,分数只有1/3,不支持ARGBFloat时,分数再减半。
为了获取到真实的分数,将debug的开关翻转,重新打包Antutu 3D APK,运行后可以在Logcat中看到分数的输出了。