AccessibilityService防御

avatar
Android @奇舞团Android团队

前面讲解了AccessibilityService知多少,详细描述了使用方法已经内部的原理,这节主要是防御手段。在网上也找到了很多资料,作为参考。下面就简单的说一说。

1、检测辅助模式的开启

之前提到过AccessibilityService类使用的是观察者模式,通过Binder机制在系统App1 view层->os->App2Service进行事件传递。由AccessibilityManagerService注册AccessibilityService,那如何检测到安装并启用辅助模式App2呢?系统提供了如下方法:

@Override
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
    synchronized (mLock) 
        // We treat calls from a profile as if made by its parent as profiles
        // share the accessibility state of the parent. The call below
        // performs the current profile parent resolution.
        final int resolvedUserId = mSecurityPolicy
        .resolveCallingUserIdEnforcingPermissionsLocked(userId);
        // The automation service is a fake one and should not be reported to clients as being installed - it really is not.
        UserState userState = getUserStateLocked(resolvedUserId);
        if (userState.mUiAutomationService != null) {
          List<AccessibilityServiceInfo> installedServices = new ArrayList<>();
          installedServices.addAll(userState.mInstalledServices);
          installedServices.remove(userState.mUiAutomationService.mAccessibilityServiceInfo);
          return installedServices;
        }
       return userState.mInstalledServices;
    }
}

@Override
    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType,
            int userId) {
        List<AccessibilityServiceInfo> result = null;
        synchronized (mLock) {
            // We treat calls from a profile as if made by its parent as profiles
            // share the accessibility state of the parent. The call below
            // performs the current profile parent resolution.
            final int resolvedUserId = mSecurityPolicy
                    .resolveCallingUserIdEnforcingPermissionsLocked(userId);

            // The automation service can suppress other services.
            UserState userState = getUserStateLocked(resolvedUserId);
            if (userState.isUiAutomationSuppressingOtherServices()) {
                return Collections.emptyList();
            }

            result = mEnabledServicesForFeedbackTempList;
            result.clear();
            List<Service> services = userState.mBoundServices;
            while (feedbackType != 0) {
                final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType));
                feedbackType &= ~feedbackTypeBit;
                final int serviceCount = services.size();
                for (int i = 0; i < serviceCount; i++) {
                    Service service = services.get(i);
                    // Don't report the UIAutomation (fake service)
                    if (!sFakeAccessibilityServiceComponentName.equals(service.mComponentName)
                            && (service.mFeedbackType & feedbackTypeBit) != 0) {
                        result.add(service.mAccessibilityServiceInfo);
                    }
                }
            }
        }
        return result;
    }

这个方法remove了UiAutomationService,还是很贴心的。

返回值AccessibilityServiceInfo是一些我们使用的AccessibilityService的配置信息,包括packageNames(AccessibilityService 监控哪些package发出的Event),如下:

java

AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
serviceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
serviceInfo.packageNames = new String[]{"com.tencent.mm"};
serviceInfo.notificationTimeout=100;
setServiceInfo(serviceInfo);

xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged|typeWindowsChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault|flagRequestEnhancedWebAccessibility"
    android:canRetrieveWindowContent="true"
    android:description="@string/app_name"
    android:notificationTimeout="100"
    android:packageNames="com.tencent.mm"
    android:canRequestEnhancedWebAccessibility="true"
    />

值得注意的是AccessibilityManagerService,属于com.android.server.accessibility包下,也就是系统内部类,不能直接用。

那应该怎么做呢?可以通过AccessibilityManager间接的操作AccessibilityManagerService,由上次分析系统源码可知,系统内部利用Binder机制调用了AccessibilityManagerService,拿到这个列表后遍历出自己的应用正在被谁监控或“辅助”了。

看一下怎么施工,向下看,

private List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(String targetPackage) {
   List<AccessibilityServiceInfo> result = new ArrayList<>();
   AccessibilityManager accessibilityManager = (AccessibilityManager) getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
   if (accessibilityManager == null) {
       return result;
   }
   List<AccessibilityServiceInfo> infoList = accessibilityManager.getInstalledAccessibilityServiceList();
   if (infoList == null || infoList.size() == 0) {
       return result;
   }
   for (AccessibilityServiceInfo info : infoList) {
       if (info.packageNames == null) {
           result.add(info);
        } else {
           for (String packageName : info.packageNames) {
               if (targetPackage.equals(packageName)) {
                   result.add(info);
               }
           }
       }
    }
    return result;
}

知识点:当info.packageNames为null时,表示监控所有包名。

2、干扰执行逻辑

AccessibilityServices在监控目标App发出的AccessibilityEvent时,对应的作出某些事件操作。比如,AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED。

某些微信红包插件会监控Notification的弹出,那么我们是否可以随意发送这样的Event出来,从而混干扰外挂插件的运行逻辑,比如

textView.sendAccessibilityEvent(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);

大部分的外挂插件对特定类型的事件并不是特别感兴趣,他们仅在收到Event后检查页面上是否有某些特定的元素,从而决定是否进行下一步操作。

3、屏蔽文案检查

我们知道系统内部原理就是调用TextView的findViewsWithText方法,我们需要重写这个方法就可以

public class QTextView extends android.support.v7.widget.AppCompatTextView {
     @Override
     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
        outViews.remove(this);
    }
}

这样AccessibilityServices文案检查将会在这个View上失效。

4、屏蔽点击事件

AccessibilityServices执行点击事件,最终会去调用View的OnClickListener监听事件,那我们就利用onTouch代替onClick即可。

总结

检测并禁止相关App开启辅助模式;

重写TextView 的 findViewsWithText方法,屏蔽文案检查;

onTouch替换onClick,屏蔽View的点击事件;

随机发送AccessibilityEvent,使外挂执行逻辑错误;

通过PackageManager检测并禁止相关软件安装;

古人云:道高一尺,魔高一丈;下篇见Xposed相关文章。

关注微信公众号,最新技术干货实时推送

image