hjw45611 发表于 2020-1-21 19:38

Android手机辅助功能实现抢微信红包

本帖最后由 hjw45611 于 2020-2-11 21:24 编辑

19年下半年就没怎么写过文章了,因为10月份换了工作,开始做政企手机管控相关的工作,有太多需要学习的东西,自己本身就是个菜鸟,所以一直没时间写
最近都是开年会的开年会,聚餐的聚餐,相信大家都没少抢红包,我也利用最近所学做了一个小功能来抢红包,用的就是 辅助功能服务AccessibilityService,这个东西就算是纯粹的Android开发者也很少用,是Android自带的服务,目的在于帮助那些具有视觉、身体或年龄相关限制的用户更轻松的使用Android设备和应用,它一般提供了页面元素查找功能和元素点击功能。在我工作项目中就用于自动始终允许权限、自动允许使用情况访问权、自动允许显示在其他应用上层等权限
下面我们就开始通过AccessibilityService来做一个抢红包的APP,它和Xposed是有一定差别的,xposed是监听某个方法的执行后执行某些方法,辅助功能是监听到符合的页面元素变化后执行某些方法。严重声明
本文的意图只有一个就是通过分析app学习更多的逆向技术,如果有人利用本文知识和技术进行非法操作进行牟利,带来的任何法律责任都将由操作者本人承担,和本文作者无任何关系,最终还是希望大家能够秉着学习的心态阅读此文。
本文以微信6.6.7为例

声明服务
新建一个Service,继承自 AccessibilityService,主要逻辑都是在onAccessibilityEvent方法中进行,代表界面发生某些事件就会触发该方法,例如点击、长按、触摸、焦点、滑动等等事件
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() {

    }

}
在清单文件中注册
<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 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是什么类、文本内容、是否可点击,是否可选、是否有焦点、是否可滚动等等信息



代码实现点击红包消息
可以看到红包消息与其他消息的不同点是红包消息的底部有一个“微信红包”,可以以此来找到红包节点,而且未领取的红包上的文本是“领取红包”,可以以此避免重复点击红包
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();
}

打开红包



这一步直接点击就行,找到开的按钮进行点击
List<AccessibilityNodeInfo> childNodes = mRootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/c85");
if (childNodes != null && childNodes.size() != 0) {
    childNodes.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
}

红包详情页直接返回


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在微信各个版本是不一样的,只能后期自己再努力优化
明天就要放假了,今天上午才想着要实现功能,没想到中午领导通知直接放假了,只能晚上把整个逻辑捋了一下,小问题还是明显有很多的,年后再优化,给大家拜年了,希望大家新的一年顺顺利利,好运连连。希望多多评分

年后在家办公,找时间把代码逻辑优化了一下
完整服务类如下:
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,那么进行点击操作必然没有效果的,可以看它的父节点是否可点击,所以执行操作后,没有任何效果,不要着急,先打印一下执行结果,然后再分析为什么没有效果



wobzhidao 发表于 2020-1-21 21:07

这个确实不错的

罪缘 发表于 2020-1-23 09:25

我想着看有没有朋友圈提示然后转发到自己大号的功能,找了好多都是python的,利用的是web版本的不支持朋友圈,看到大佬的这个感觉还是我大Java好:keai

飞鱼来此报道 发表于 2020-1-21 20:01

感谢老哥的辛苦付出,一定努力拜读

HaLaiShi 发表于 2020-1-21 20:11

好厉害的样子,但是我还是抢不过5g....

流云丶风轻云淡 发表于 2020-1-21 20:20

还是抢不过那些开挂的

白晓生 发表于 2020-1-21 20:25

感谢楼主分享收藏一下,准备除夕玩玩,希望好运不是一分两分

薄荷星空糖 发表于 2020-1-21 20:26

感谢分享

hackgsl 发表于 2020-1-21 20:39

学习了,明天来试下

qiokio 发表于 2020-1-21 20:42

感谢楼主的分享,希望可以抢到更多红包:lol

miqi1314 发表于 2020-1-21 21:01

过年很需要!
页: [1] 2 3 4 5 6 7 8
查看完整版本: Android手机辅助功能实现抢微信红包