AccessibilityService防禦
前面講解了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相關文章。