某安卓APP的分析与修改

前言

我平日里会玩一玩外服游戏消遣休闲,有一款安卓屏幕翻译软件很好用,但是作者后来推出了新版本,相比起旧版本,新版功能没有多大的变动,但是广告变得更多了,所以我对旧版进行分析和修改,以后就长期使用旧版软件。

我们的目标是:

  • 去除更新
  • 去除各种数据上报
  • 去除广告
  • 仅保留必要权限
  • 修改不规范的安卓外置存储使用为规范的使用方式。

需要准备:

  • Android Killer
  • jadx/JEB
  • Fiddler
  • 查壳工具(可选)
  • 了解smali语法

本文中所涉及的App包名已经模糊处理。

下面我们来一探究竟吧!

目标分析

首先安装软件,观察软件的行为,可以使用真机或者模拟器,我更喜欢使用模拟器,因为可以更加方便地配合电脑调试,以及具备高权限。


可以看到该软件的功能十分丰富,具有修改的价值。

抓包

简单进行一个抓包观察。模拟器的优势在这里就体现出来了,抓包十分的方便,此处要注意安卓7.0以上更改了网络安全策略,需要事先修改安装包才能完整抓包,所以这里为了方便起见采用安卓5.0。

image
可以看到该软件采用了友盟的数据上报SDK,我们之后需要去除。

反编译分析

反编译Apk文件可以采用Android Killer/jd-gui/jeb,其中Android Killer是集合反编译回编译的较完整IDE,但是由于目前缺乏维护,使用过程中会出现一些问题。(例如不能识别新Apk格式导致无法反编译到Java代码)

我使用Android Killer配合JEB来查看Java代码,首先反编译Apk,查看Manifest信息:


光标所在处为启动Application,结合Apk包目录树结构和之后的反编译目录结构存在该目录和相关业务代码可以判定该Apk没有加壳,这样回编译就容易多了。

另外注意到该App申请了很多权限,其中有一些权限完全是可以去除的,我们将要去除RECORD_AUDIO, REQUEST_INSTALL_PACKAGES, READ_EXTERNAL_STORAGE 这三个权限。录音权限由于大部分人用不到,去除掉;查看已安装应用列表这个软件并不需要,是为了上报数据而收集的;最后一个权限并不能直接去除,需要配合修改smali代码使得软件能够符合安卓存储规范正常工作后才能去除。

接下来查看目录结构:


可以看到业务代码集中打码处的包内,com.baidu中含有百度OCR SDK,com.tencent.bugly和com.umeng具有数据收集上报和强制更新的作用,需要去除。com.google是谷歌广告框架,需要去除。其他的包基本上是第三方库,不用管。

实战

去除强制更新和数据上报

作为长期使用的软件,肯定不希望自己的信息被收集,防止信息收集的办法有很多,比如路由器刷固件写规则拦截上报的请求,或者暗桩Adguard一类的软件来拦截。我们这里选择直接修改Apk来去除数据上报,而强制更新的功能也是bugly提供的,可以一并去除。

使用框架上报信息固然方便了开发者,但同时也方便了我们来修改,查看bugly官方文档得知,引入bugly只需要一句话,也就是Bugly.init()方法,我们只需要找到这个方法并且注释掉相应的smali即可:


另外还有一种办法,因为框架的异常处理都比较完善,我们可以讲bugly上报域名全局替换为localhost,这样上报必然会失败,异常由框架自身处理不会影响到程序本身。

umeng由于方法类似不再赘述,同样由官网文档得知一句话引入。

去除广告

由之前的目录分析可知,该软件使用了谷歌广告SDK,去除广告有多种思路,可以找到引入框架的代码注释掉,也可以修改Google广告id为错误的值,这样广告自然会失效,又或者修改Apk的layout,讲广告组件改为不可视或者大小改为0*0。实战中往往可以将这些方法结合起来,选取最容易的办法。

查找官方文档得知,接入Google Ad需要获取一个id,然后在初始化App的时候调用MobileAds.initialize(Context, "id")这个方法来初始化广告框架,广告的收益归属由id来确定。所以思路很清楚,只需要注释掉相应的smali代码或者更改id为一个错误的值即可,这里采用注释的方式:

去除不需要的权限

众所周知,现在的很多App不重视权限的使用,往往申请了一大堆权限,这样用户体验很不好,我们重点关注录音、存储和枚举已安装列表的权限,因为这三个权限重要性更高。

其中录音和枚举已安装列表的权限可以直接在Manifest中去除,存储权限直接去除会导致App无法正常工作,因为屏幕翻译的过程中需要保存截图用来上传,所以我们需要进行一定的修改。

去除权限后的Manifest如下图所示:

修改存储相关代码

Google在安卓设计规范中写明了,App应该尽可能地使用Android/data目录中分配给它使用的那块空间,每一个App内核都给它分配了一个独立的user,对这个目录具有读写权限,所有不需要交予用户的数据都应该放在这里,App被移除的时候这个目录会自动被删除。

该App和其他很多App一样,申请外置存储权限仅用来保存运行过程中的数据,由于安卓没有存储隔离,拥有外置存储权限的App可以随意读写用户的所有数据,这样使用体验很不好,所以我们来修改,让它符合安卓存储规范后将外置存储读写权限删除。

首先我们要确定存储相关代码的位置,代码风格良好的程序修改起来更加容易,只需要确定模块所在位置即可,否则可以在业务代码的范围内搜索getExternalStorageDirectory来逐个筛选关键代码。经过观察发现,我们需要修改的关键代码集中在com.foo.bar.utils.Save2DiscUtil里,阅读该类的反汇编smali代码,发现保存文件的根目录变量rootDir

寻找该变量的赋值代码,找到相应代码如下:


73行创建了一个StringBuilder对象,用来构造最终的目录字符串;77行获取外置存储路径;83行是Kotlin的检查,可以忽略;最终99行赋值路径字符串给rootDir变量。

注释掉以上代码,我们手写一段代码修改路径为符合安卓存储规范的目录:


这里需要注意的是,获取getExternalFilesDir需要先获取Context,一般开发者都会在基类中写一个方法用来获取全局Context,这里的101行既是利用了这个方法获取全局Context

112行构造StringBuilder,130行将构造的字符串赋值给rootDir

打包&签名

打包可以使用apktool b命令,也可以使用Android Killer自动完成打包和签名工作(实际上也是apktool),这一步apktool会自动检查smali语法的正确性。

总结

本次对软件的分析和修改虽然不涉及破解,但是过程和原理是一样的,破解修改的smali代码往往很少,大量的时间花费在处理加壳和定位关键代码上面。本篇主要讲述了一些问题的分析思路,另外涉及到一点手写smali的过程,对熟悉这门语言可能有帮助。

论坛新人的第一篇帖子,希望各位看官能看得开心,想和大伙一起学习研究!

1 Like