前面讲解了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;}
}
@Overridepublic 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检测并禁止相关软件安装;