如何获取到Antutu跑分的详细子项分数?

Antutu是用户常用的一款智能设备跑分软件。如下图所示,它从四个大类来评估一台移动设备的性能,分别是CPU、GPU、UX和MEM。Antutu最终给出总分、一级汇总得分和二级得分,人们一般习惯使用总分来量化设备间的性能。

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"

Antutu 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函数调用。

BenchmarkService

Antutu 3D跑分为第一项进行,逻辑复杂,暂时跳过分析。后面的每一项测试的大体流程为,如果该跑分项目由Native层进行测试,比如多线程测试,则先由a(int)函数通知UI线程某项跑分开始了,然后通过dc.a(int)判断该跑分是否可以在Native层执行,并等待2秒后才开始真正测试。由于此处的Java类和方法有部分做了混淆,所以需要根据每个函数的具体实现猜测其功能。

Java invoke Native

通过IDA打开libabenchmark.so后,我们发现Native层并没有做更加完备的防护,搜索benchmarkV6函数,便可以找到更多测试函数的入口了。

benchmarkV6

如果该跑分项目在Java层进行,则先跑分,再通过jni.benchmarkProcessUX接口将分数导入Native层。比如测试XML(14)。

xml

接下来我们继续研究UX图片处理的相应逻辑,我们寻找20~22编号的测试并详细查看其逻辑。

ux

其中,JPG decode比较简单,使用Java的BitmapFactory.decodeByteArray测试。分数为5秒内decode的次数。由于是纯Java实现,我们可以将相应的代码自己实现后测试,但结果显示JPG decode并不是导致UX图像处理跑分低的根源。

而FishEye和Blur都使用了Native实现。搜索fisheye关键字,我们很快能看到其Native测试实现。

fisheye

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 static

dc a

此时,一个简单的想法便是如果能将dc.f的默认值改为true,则可以获得这个分数了。

modify smali

重新使用apktool b指令重建Antutu的主Apk,并使用jarsign重新签名。但安装后发现Antutu卡死。

这是为何呢?寻找原因后发现Antutu在Native层做了自身的签名验证,验证不通过的话会不断sleep卡死App。

test sign

详细观察代码后发现,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秒中统计图像处理次数。

onTestPhysX

检查逻辑后还发现,Antutu对一些特性不支持时,暴力地降低分数,比如不支持computeShaders时,分数只有1/3,不支持ARGBFloat时,分数再减半。

为了获取到真实的分数,将debug的开关翻转,重新打包Antutu 3D APK,运行后可以在Logcat中看到分数的输出了。

3D