支付宝蚂蚁森林能量自动收取插件开发原理解析
一、前言
关于支付宝的蚂蚁森林能量收取这个非常有重大意义的东西,本人表示非常认同,这个网上也是一直有人觉得马云是一个非常有远见的男人,的确虽然很多人觉得马云不会技术但是很能说,但是他说的都慢慢实现了,先不管这个种树到底能不能落实,马云的确在带领着阿里帮助世界地球变得越来越美好,而隔壁的企鹅公司暂时不评论。我们如果玩过能量收取都知道他有一个弊端就是每天早起收能量有时候会发现自己的能量被别人全部偷完了很不爽,而且自己在收取的时候也会发现非常的慢和难受,特别在好友比较多的时候。所以针对这么多问题就开发了一个自动收取的插件,还记得当初偷菜插件吗?和那个类似可以设置指定时间自动收取和偷取被人的能量的一款插件。
二、寻找突破口
不多说了接下来就开始分析如何编写这个插件,我们现在写插件都是基于Xposed的,而之前说过支付宝有防止Xposed的功能,所以我们得先把这个功能给干掉,不了解的同学可以点击这里进行查看,那有的同学就好奇了为什么不用辅助功能做模拟点击呢?这个后面分析你就知道了,没法用辅助功能进行模拟点击的。接下来我们先找突破口,这个没的说一切从我们肉眼获取,分析界面信息:
这里看到了吗?非常奇怪的是页面非常简单,在我们没有写这个插件之前我以为这个页面很简单,是一个列表而已,但是不是。就是一个View,其实到后面会知道他是一个WebView而已。所以这里就解释了之前有的同学说用辅助功能做模拟点击,这里可以发现完全做不了的。所以我们得换个思路找突破口。因为这个页面不管是一个什么样的View,我们只要找到一个核心点即可。那么这里在给大家介绍一个寻找突破口的方法了,因为这里没有太多的字符串信息,也没法用搜索大法了。那么可以查看当前调用了哪些方法来找突破口,很多人想到了我之前写的icodetools工具了,但是那个工具这里不适合因为支付宝的包太大了,如果用那个工具会发现页面完全卡死了,那么该怎么办呢?
这个可以借助DDMS工具自带的方法调用栈信息了,不过可惜的是这个获取的前提是这个应用出于可调试状态,也就是要在DDMS列表中出现。那么支付宝肯定是不可以调试的,所以让他出于可调试的我们可以这么做反编译支付宝之后修改xml的android:debuggable="true"属性,然后回编译即可。这里有两个问题在我实际操作的过程中,一个是支付宝反编译修改添加都没有问题,但是回编译可能有问题。那么可以直接用AXMLEditor工具进行xml的二进制文件修改,然后签名,可惜的是支付内部有校验,这里用了kstools么有效果,可能是他内部用了其他的签名校验逻辑。到这里我们难道还需要先破解他的签名校验吗?感觉写个插件得解决很多问题。不过这里在告诉他家一个小工具就是可以临时修改设备的系统调试状态值mprop工具,这个我在之前脱360壳的时候介绍过了,不过这个工具现在有更新版本了。后面给出下载地址。工具使用非常方便:
在目录下找到指定cpu型号的mprop文件拷贝到设备目录下,然后修改权限为可执行的,然后直接运行三条命令即可:
# 修改init进程中的ro属性判断逻辑
/data/local/tmp# ./mprop
# 然后修改系统调试属性值
/data/local/tmp# setprop ro.debuggable 1
# 恢复init默认逻辑,之后就无法再修改ro属性了
/data/local/tmp# ./mprop -r
执行这三个命令之后就可以了,然后在查看这个属性值:
这里已经成功修改了系统的调试状态为1了,那么设备中所有的应用都可以出于调试状态了,即使没有android:debuggable="true"属性的应用。所以可以看到这个小工具的作用是非常的大了,而且也看到在逆向操作中如果有一些辅助小工具的话,效率是非常高的,这个工具大家也一定要记住他,在你以后逆向过程中肯定还会用到。然后我们在打开DDMS就可以看到支付宝主进程就出现了:
然后我们选择左上角那个可以获取当前方法执行的堆栈信息,然后弹出对话框我们选择下面一个,就是实时的每个方法都获取,避免丢失关键方法,但是这个会有很多大的效率问题也就是点击确定之后你的支付宝应用会卡死,所以这里操作的时候是在进入蚂蚁森林的页面那一刻才点击OK,然后等页面加载完成就立即点击结束也是右上角那个按钮即可:
点击结束之后就生成了trace文件,这个文件路径在标题栏中可以看到:
我们可以保存这个trace文件,然后利用Tracer.jar工具直接打开查看:
这里也是可以直接分析方法栈信息调用的,这个工具是一个同学给我的,我觉得可能没有DDMS中自带的好用,但是看个人习惯吧,这个工具也会在后面给出。继续回到DDMS的界面分析:
这里有一个类似于网络请求的接口,首先到这里我们通过这些类的字样知道那个页面应该是H5页面,也就是不是原始本地的页面,而是一个WebView页面,通过方法栈来看可能是UCWebView,这个是UC浏览器自定义的一个内核webview,因为UC被阿里收购了用UC也是无可厚非的。那么先不管他是什么Webview了,我们之前去看这个方法。
三、逆向分析代码
我们用jadx直接打开支付宝应用,可惜会发现直接卡死,这里我用Mac都是的,因为支付宝比微信还狠,已经8个dex文件了,所以还是得分包打开,最后找到了:
到这里我们会发现一个点就是支付宝内部有一个打印日志的类H5Log,他应该封装了系统打印日志的,我们先不管他内部怎么实现的,直接hook这个类的这个方法,通过一些日志来快速分析行为和得到我们想要的:
然后然后我们通过打印日志看看效果,因为这里会很多日志信息,所以通过它的tag作为过滤条件,这里的tag是H5RpcUtil:
这里又发现adb logcat 命令还是那么好用,Windows中用findstr进行过滤即可,在打印信息中会发现一些特殊信息,比如每次执行完成之后的response是一个json数据:
而且发现了一个重要的信息就是我们肉眼在页面看到的好友排行榜信息,先不管把这些信息json格式化一下:
到这里感觉特别激动,通过信息发现正是好友列表信息,而且有一个关键字段canCollectEnergy表示这个用户是否有能量可以收取的,所以到这里我们发现仅仅通过日志信息就可以找到突破口了,还找到了关键信息。所以对于一个线上的应用每家公司应该在最后的发包阶段对应用中所有的日志代码全部删除,这里是全部删除而不是仅仅加上一些开关来控制。只有删除才是最安全的,现在有些安全公司就是这么做的,内部有一个发包审核和自动删除应用所有打印日志代码,从这里看如果没有日志我们还是需要去分析这个方法的:
这个方法最后是返回了一个H5Response类。那么到这里我们已经找到返回结果了,那么这个返回结果执行前是有什么信息呢?比如参数等,而且这里看到好友列表默认是分页,每页是20个用户信息,所以后面我们肯定得获取所有的好友信息,应该有个参数来进行操作,所以为了更加直观看数据,直接hook这个H5RpcUtil.rpcCall方法,这个方法有十几个参数,其中有一个坑就是JSONObject格式的,其实他是阿里的fastjson包的对象,导致操作过程中始终没hook成功:
所以这里如果有的同学在使用Xposed进行hook一个方法,但是没有hook成功,最快找到原因就是把这个hook的代码外面加一个try...catch,然后catch到异常信息打印出来即可。如果有错误直接就可以看到原因了,不然很费劲去找hook失败的原因。然后我们在运行看看结果:
我们进行下拉获取更多好友列表信息发现打印的参数信息,里面的第二个参数的确是携带了分页信息,每页是20条,又每页的起始点,而且第一个参数其实是命令类型,后面还会有获取用户所有可以收取的能量以及收取能量的命令。那么到这里我们已经成功的获取到所有好友信息,并且可以通过解析response的json数据获取哪些用户可以有能量收取,那么怎么获取到这个用户所有的可以收取的能量信息呢?因为有的同学可以有很多能量收取的,这个直接点击这个用户进入他的蚂蚁森林页面即可,这时候依然是通过观察日志信息,首先还是入口参数信息:
这里看到每次进入一个用户的能量页面,都会执行这四条命令,而且其中有三条命令会携带当前用户id,那么用户的id信息我们在之前列表返回的json中可以拿到了。再看看能量返回的json数据:
还是进行json格式化:
果不其然返回的json中会表示这个用户所有的能量信息,其中的collectStatus字段表示这个能量是否可以收取,那么我们把这个json进行解析保存可以收取的能量id信息,接下来有了这些id信息,那么就开始收取了,我们在手动的收取一个能量看看打印的参数信息和返回结果信息:
入口参数中的确包含了当前收取的能量id和当前用户信息,而这两个参数我们现在已经都能获取到了,看看收取之后的返回信息:
依然去json格式化:
返回信息中有是否成功的字段和成功收取的能量数值,这个我们可以解析来标识此次收取是否成功了。
好了到上面我们已经全部分析完了能量获取流程,我们写的插件大致操作流程也很简单,第一次进入蚂蚁森林页面就会获取第一页的好友信息,然后我们通过修改页数起始点进行分页获取所有好友信息,然后解析字段获取有能量收取的用户id信息进行保存,然后在依次通过用户id信息获取每个用户可以收取的能量id信息进行保存,最后在利用之前的用户id和能量id信息进行收取能量即可。整个过程全部通过调用上面的那个关键方法:H5RpcUtil.rpcCall,因为这个方法是static的,所以调用它很简单,只要构造具体参数信息即可,而在上面的三个操作中获取好友列表信息、获取指定用户的可收取能量信息、收取能量;这三个步骤打印的参数来看,现在唯一需要搞定的是那个自定义类型的H5PageImpl类:
我们看看怎么才能获取到这个对象实例,而且我们也发现这个对象实例在每次进入一个新的页面都是不一样的,全局搜这个类:
这里发现了很多地方调用,但是有一个H5Fragment最值得怀疑,为什么呢?因为我们可以看到只有Activity和Fragment可以展示页面的载体。我们现在看到的是蚂蚁森林页面,又在H5Fragment中搜到他了,当然最值得怀疑和查看了,点击去查看即可:
到这里我们就要有点应用开发技巧了,Android中的Fragment是无法单独展示的,一般的用法都是一个Activity中的FragmentManager进行管理展示的,因为可能会有多个Fragment的,所以我们可以先看看当前activity是啥,直接用命令 adb shell dumpsys activity top:
这个Activity是H5Activity,可以看到他的当前Fragment的构造参数信息,我们去查看这个activity信息:
果不其然有一个类似于FragmentManager的类,点进去进行查看:
这里是每次添加新的H5Fragment类的方法,我们直接hook然后把当前的H5Fragment打印出来看看:
通过打印日志发现每次进入一个页面这个H5Fragment都是一个新对象,那么我们知道上面调用rpcCall方法的参数H5PageImpl可以在H5Fragment中获取到,所以这里我们就需要这么做了,就是拦截这个pushFragment方法,然后保存当前最顶端的页面H5Fragment,然后用反射获取对应的H5PageImpl对象,最后在调用rpcCall方法执行命令即可,具体这几个类的关系是什么样的,继续看一下代码:
首先是H5Fragment类中有一个变量是H5ViewHolder类型的变量a:
然后是H5ViewHolder类中的有个变量h是保存当前的H5PageImpl类型:
而H5PageImpl内部有一个H5WebView类型变量f:
看到这里设置了很多WebView的回调接口信息,所以到这里就大致清楚蚂蚁森林或者说支付宝其他H5页面的层级关系是:H5Activity.fragmentManager->H5Fragment->H5ViewHolder->H5PageImpl->H5WebView;然后利用webview进行本地交互操作了。
四、编写Hook代码
有了上面的分析,这里绘制了一张图片,全世界唯独一份的,以后大家分析支付宝H5页面都可以按照这个流程来即可:
有了这张图我们对于写插件那就很简单了,下面就简单来分析一下代码吧,首先我们第一次进入会有两个返回信息,一个是当前第一页的好友信息,还有一个是自己的能量信息,所以需要解析两个,而对于自己的能量直接收取即可,我们可以hook上面的rcpCall方法来获取每次页面返回的response信息:
然后自动获取所有的好友列表信息进行保存:
这里需要分页请求到所有的好友信息保存,请求完成了然后在遍历所有用户获取可以收取的能量信息,发送获取分页好友信息的命令也很简单:
查看指定给用户可以收取能量的命令:
开始自动收取能量的命令:
我们利用之前分析的那几个类关系,保存当前的H5Fragment:
然后利用反射获取对应的H5PageImpl对象,最后调用rpcCall方法发送命令请求:
还有部分代码这里不在详解了,整个流程贯通之后接下来就是见证奇迹的时刻:
看到了只需要几秒钟就可以把所有好友的所有能量一偷殆尽,不给自己留下一丝丝的遗憾:
而有了这个之后,我们还需要写个工具就是经过多年偷取能量经验,每天偷取能量的时间段最佳是早上7.30,中午12.00,晚上5.30,所以我们在这三个时间段自动打开蚂蚁森林页面即可。代码这里不在详细介绍了。到这里我们就把插件开发完成了。
五、技术总结
从上面的分析可以看到,本文涉及到的技术还是很多的,所以这里就来总结一下,有时候写一个插件结果不重要,重要的是在这个过程中我们发现了什么学到了什么:
- 第一、在使用以往的突破口方法(字符串搜索大法,UI界面元素分析等)方法无果的时候,记住一个最新的方法就是获取当前应用执行片段的所有方法堆栈信息来获取突破口方法更加便捷。
- 第二、因为需要使用DDMS获取当前应用执行片段的方法堆栈信息,但是需要这个应用处于调试状态,为了不进行反编译和回编译操作繁琐,可以利用mprop工具直接修改系统的调试状态值。
- 第三、任何一个应用如果利用页面H5进行展示功能,那么他肯定有一个和本地交互的JS对应方法,一般最直接的方法是找到这个页面的WebView,然后hook他的addJavascriptInterface方法获取本地JS交互的对象,或者是hook每次都会加载url的几个回调方法比如WebViewClient接口,或者是WebViewChromeClient等。
- 第四、支付宝可能为了安全起见或者是性能更或者是动态更新的原因来把很多页面做成H5了,不得不佩服他把H5页面做的和本地页面几乎无差别还是很厉害的,至少我一开始因为是本地页面。所以以后分析支付宝的其他页面如果发现当前的activity是H5Activity的话,那么就是H5页面进行加载的。
还是那句话每次写插件是为了更好的学习更多逆向技巧和思路,有时候结果不重要,重要的是过程,而写了这么多插件唯独感觉这个插件最有实际用途,现在每天用插件收取能量真的是太方便了。