本帖最后由 hjw45611 于 2020-2-11 21:24 编辑
19年下半年就没怎么写过文章了,因为10月份换了工作,开始做政企手机管控相关的工作,有太多需要学习的东西,自己本身就是个菜鸟,所以一直没时间写
最近都是开年会的开年会,聚餐的聚餐,相信大家都没少抢红包,我也利用最近所学做了一个小功能来抢红包,用的就是 辅助功能服务AccessibilityService,这个东西就算是纯粹的Android开发者也很少用,是Android自带的服务,目的在于帮助那些具有视觉、身体或年龄相关限制的用户更轻松的使用Android设备和应用,它一般提供了页面元素查找功能和元素点击功能。在我工作项目中就用于自动始终允许权限、自动允许使用情况访问权、自动允许显示在其他应用上层等权限
下面我们就开始通过AccessibilityService来做一个抢红包的APP,它和Xposed是有一定差别的,xposed是监听某个方法的执行后执行某些方法,辅助功能是监听到符合的页面元素变化后执行某些方法。严重声明
本文的意图只有一个就是通过分析app学习更多的逆向技术,如果有人利用本文知识和技术进行非法操作进行牟利,带来的任何法律责任都将由操作者本人承担,和本文作者无任何关系,最终还是希望大家能够秉着学习的心态阅读此文。
本文以微信6.6.7为例
声明服务
新建一个Service,继承自 AccessibilityService,主要逻辑都是在onAccessibilityEvent方法中进行,代表界面发生某些事件就会触发该方法,例如点击、长按、触摸、焦点、滑动等等事件
[Java] 纯文本查看 复制代码 public class HandleAccessService extends AccessibilityService {
private static final String TAG = "HandleAccessService";
private Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext = this.getApplicationContext();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
public void onInterrupt() {
}
}
在清单文件中注册
[XML] 纯文本查看 复制代码 <service
android:name=".service.HandleAccessService"
android:label="@string/accessibility_control_service_label"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
label是服务名,permission代表需要辅助功能服务,meta-data中的resource指服务的配置,下面看一下accessibility_service_config
[XML] 纯文本查看 复制代码 <?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagRequestTouchExplorationMode|flagRequestEnhancedWebAccessibility|flagReportViewIds|flagRequestFilterKeyEvents"
android:canRetrieveWindowContent="true"
android:packageNames="com.tencent.mm"
android:description="@string/accessibility_service_control_description"
android:notificationTimeout="100" />
android:description :辅助功能描述,描述该辅助功能用来干嘛的
android:packageNames :指定辅助功能监听的包名,不指定表示监听所有应用
android:accessibilityEventTypes:辅助功能处理事件类型,一般配置为typeAllMask表示接收所有事件
android:accessibilityFlags:辅助功能查找截点方式,一般配置为flagDefault默认方式。
android:accessibilityFeedbackType:操作相应按钮以后辅助功能给用户的反馈类型,包括声音,震动等。
android:notificationTimeout:相应时间设置
android:canRetrieveWindowContent:是否允许辅助功能获得窗口的节点信息,为了能正常实用辅助功能,请务必保持该项配置为true
-->主要方法
以下列举的只是我刚接触到的常用方法,还是有很多很重要的方法的,可以查官方文档进行学习
getRootInActiveWindow
获取窗体中的节点信息,会获取到当前窗体的所有view节点
findAccessibilityNodeInfosByViewId
通过ViewID找到节点信息
findAccessibilityNodeInfosByText
通过文本找到节点信息
getParent()
获取该节点的父节点
performAction
对该节点执行对应操作,例如点击、获取焦点、设置文本,注意该方法是有返回boolean值的,就是执行结果(成功或失败)
分析
辅助服务来抢红包就是对界面元素分析后,针对未抢的红包节点View进行模拟点击,以此来实现用于开发辅助的重要工具就是Android Device Monitor,可以在AndroidSDK->tools->monitor.bat打开,手机开启USB调试后,可以看到Devices中多出一个设备
截屏图像
点击上图右上角图标后,可以得到当前手机视图层次结构图,可以看到是可以进行view树的分析的,可以查看节点树的嵌套信息、选中view的详细信息,详细信息中就有该view是什么类、文本内容、是否可点击,是否可选、是否有焦点、是否可滚动等等信息
收到红包
代码实现点击红包消息
可以看到红包消息与其他消息的不同点是红包消息的底部有一个“微信红包”,可以以此来找到红包节点,而且未领取的红包上的文本是“领取红包”,可以以此避免重复点击红包
[Java] 纯文本查看 复制代码 AccessibilityNodeInfo mRootNode = getRootInActiveWindow();
//从窗口获取根节点信息
if (mRootNode != null) {
List<AccessibilityNodeInfo> luckyMoneyNode = mRootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/afp");
if (luckyMoneyNode != null && !luckyMoneyNode.isEmpty()) {
for (AccessibilityNodeInfo accessibilityNodeInfo : luckyMoneyNode) {
if (accessibilityNodeInfo.getText().toString().equals("微信红包")) {
List<AccessibilityNodeInfo> getNode = accessibilityNodeInfo.getParent().getParent().findAccessibilityNodeInfosByText("领取红包");
if (getNode != null && !getNode.isEmpty()) {
accessibilityNodeInfo.getParent().getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
}
}
}
mRootNode.recycle();
}
打开红包
打开红包
这一步直接点击就行,找到开的按钮进行点击
[Java] 纯文本查看 复制代码 List<AccessibilityNodeInfo> childNodes = mRootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/c85");
if (childNodes != null && childNodes.size() != 0) {
childNodes.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
红包详情页直接返回
红包详情
[Java] 纯文本查看 复制代码 List<AccessibilityNodeInfo> toolbarTextNodes = mRootNode.findAccessibilityNodeInfosByViewId("android:id/text1");
Log.e(TAG, "onAccessibilityEvent: "+ (toolbarTextNodes != null)+"=="+(toolbarTextNodes != null?toolbarTextNodes.size():""));
if (toolbarTextNodes != null && toolbarTextNodes.size() != 0) {
Log.e(TAG, "onAccessibilityEvent: "+ (toolbarTextNodes != null)+"=="+(toolbarTextNodes != null?toolbarTextNodes.size():""));
if ("红包详情".equals(toolbarTextNodes.get(0).getText().toString())) {
toolbarTextNodes.get(0).getParent().getParent().findAccessibilityNodeInfosByViewId("com.tencent.mm:id/ht").get(0).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
使用
打包后直接安装到手机,然后在设置中的智能辅助-无障碍中找到该服务名,打开辅助就可以了
效果
效果
可以看到在收到红包的一瞬间就被打开并领取成功,但因为某些原因还是关闭后又被重新打开,我个人猜测是由于在红包详情页点击返回时因为“领取红包”变为“红包已领取“的间隔内接收到了事件,导致红包被点击,所以弹出红包已领取,所以应该在代码中加一秒的时间差,自动点击领取后的一秒内不要自动点击,给控件一定的反应时间
总结
使用辅助服务还是很简单的,我自己也是看了同事写的自动允许服务的功能才联想到在未root的手机上可以通过辅助服务来实现控件节点的自动模拟点击的,网上可能也有现成的,但我习惯自己来实现,还是有很多相关方法没看到,也有很多问题有待解决,比如服务总是自动被关闭、viewid在微信各个版本是不一样的,只能后期自己再努力优化
明天就要放假了,今天上午才想着要实现功能,没想到中午领导通知直接放假了,只能晚上把整个逻辑捋了一下,小问题还是明显有很多的,年后再优化,给大家拜年了,希望大家新的一年顺顺利利,好运连连。希望多多评分
年后在家办公,找时间把代码逻辑优化了一下
完整服务类如下:
[Java] 纯文本查看 复制代码 public class GetLuckMoney extends AccessibilityService {
private static final String TAG = GetLuckMoney.class.getName();
private long clickTime;
private boolean bOpen;
@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
Log.e(TAG, "onAccessibilityEvent: "+accessibilityEvent.getPackageName());
AccessibilityNodeInfo mRootNode=getRootInActiveWindow();
if (mRootNode != null) {
//点击红包消息
List<AccessibilityNodeInfo> luckyMoneyNode = mRootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/afp");
if (luckyMoneyNode != null && !luckyMoneyNode.isEmpty()) {
for (AccessibilityNodeInfo accessibilityNodeInfo : luckyMoneyNode) {
if ("微信红包".equals(accessibilityNodeInfo.getText().toString()) && accessibilityNodeInfo.getParent()!=null && accessibilityNodeInfo.getParent().getParent().findAccessibilityNodeInfosByText("领取红包").size()>0) {
if ( System.currentTimeMillis()-clickTime>1000 && !bOpen) {
clickTime = System.currentTimeMillis();
Log.e(TAG, "onAccessibilityEvent: ACTION_CLICK");
accessibilityNodeInfo.getParent().getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
bOpen=true;
break;
}
}
}
}
//打开红包
List<AccessibilityNodeInfo> childNodes = mRootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/c85");
if (childNodes != null && childNodes.size() != 0 && bOpen) {
childNodes.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
bOpen=false;
}
//返回
List<AccessibilityNodeInfo> toolbarTextNodes = mRootNode.findAccessibilityNodeInfosByViewId("android:id/text1");
Log.e(TAG, "onAccessibilityEvent: "+ (toolbarTextNodes != null)+"=="+(toolbarTextNodes != null?toolbarTextNodes.size():""));
if (toolbarTextNodes != null && toolbarTextNodes.size() != 0) {
Log.e(TAG, "onAccessibilityEvent: "+ (toolbarTextNodes != null)+"=="+(toolbarTextNodes != null?toolbarTextNodes.size():""));
if ("红包详情".equals(toolbarTextNodes.get(0).getText().toString())) {
toolbarTextNodes.get(0).getParent().getParent().findAccessibilityNodeInfosByViewId("com.tencent.mm:id/ht").get(0).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
mRootNode.recycle();
}
}
@Override
public void onInterrupt() {
}
}
效果如下:
完善
可以看到,红包出现的一瞬间就被领取了,之前的bug也被修复了,代码上就是多了个时间变量进行控制,防止多次被点开,代码量很少,也很容易理解,只有两点需要注意:
1.理清楚view节点的分支关系,找到关键节点进行判断操作,如果多次getParent后不知道找没找对节点,可以获取节点的getClassName、getViewIdResourceName等字段打印一下,方便理清
2.对该节点进行对应操作,如果节点的clickable为false,那么进行点击操作必然没有效果的,可以看它的父节点是否可点击,所以执行操作后,没有任何效果,不要着急,先打印一下执行结果,然后再分析为什么没有效果
|