AccessibilityService 從入門到出軌

xuyisheng發表於2016-12-13

AccessibilityService從入門到出軌

AccessibilityService根據官方的介紹,是指開發者通過增加類似contentDescription的屬性,從而在不修改程式碼的情況下,讓殘障人士能夠獲得使用體驗的優化,大家可以開啟AccessibilityService來試一下,點選區域,可以有語音或者觸控的提示,幫助殘障人士使用App。

當然,現在AccessibilityService已經基本偏離了它設計的初衷,至少在國內是這樣,越來越多的App借用AccessibilityService來實現了一些其它功能,甚至是灰色產品。

使用入門

老規矩,官網鎮樓
developer.android.com/guide/topic…
developer.android.com/training/ac…

要使用AccessibilityService實際上非常簡單,一般來說,只需要以下三步即可。

繼承系統AccessibilityService

public class MyAccessibility extends AccessibilityService {

    private static final String TAG = "xys";

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.d(TAG, "onAccessibilityEvent: " + event.toString());
    }

    @Override
    public void onInterrupt() {
    }
}複製程式碼

其中有兩個必須實現的方法:onAccessibilityEvent和onInterrupt。

在onAccessibilityEvent中,我們可以接收所監聽的事件。不熟悉這些事件的話,只需要使用toString把這些資訊打出來,自己多看幾個Log,就大概能夠了解了。

新建配置檔案

在資源目錄res下新建xml資料夾,新建accessibility.xml檔案,寫入:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accessibilityEventTypes="typeAllMask"
                       android:accessibilityFeedbackType="feedbackSpoken"
                       android:canRetrieveWindowContent="true"
                       android:notificationTimeout="1000"/>複製程式碼

裡面有一些比較簡單的配置。

其中 description 為 使用者允許應用的輔助功能的說明字串,這裡沒有指定所要輔助的應用packageNames,當沒有指定時,預設輔助所有的應用,建議大家在使用時,指定需要監聽的包名(你可以通過|來進行分隔),而不是所有的包名。typeAllMask是設定響應事件的型別,feedbackGeneric是設定回饋給使用者的方式,有語音播出和振動。

註冊

在AndroidMainifest中註冊:

<service
    android:name=".MyAccessibility"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService"/>
    </intent-filter>

    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility"/>
</service>複製程式碼

完成以上步驟後,一個AccessibilityService就可以使用了,你要知道的是,AccessibilityService具有很高的系統許可權,所以,系統不會讓App直接設定是否啟用,需要使用者進入設定-輔助功能中去手動啟用,這樣在一定程度上,保護了使用者資料的安全。

如何理解AccessibilityService

很多人可能對AccessibilityService瞭解的不是很深入,所以認為AccessibilityService是在呼叫一些系統服務來自動執行一些操作,實際上,這個理解不能算錯,當然也不全對,我覺得你可以把AccessibilityService理解為——『按鍵精靈』。相信很多開發者都玩過PC上的這款軟體,他的作用,就是將你一次操作的整個記錄,錄製下來,然後就可以根據這個記錄,重複的執行這些操作,例如:先點選某個輸入框,再輸入XXXX,再輸入驗證碼,最後點選某按鈕,這些操作如果需要重複執行,那麼顯然是一套機械的步驟,那麼通過按鍵精靈,記錄下這些操作後,直接通過指令碼就可以完成這些操作。其實AccessibilityService跟這個是一樣的,我們記錄的,實際上就是我們的操作步驟,或者稱之為『指令碼』,那麼系統在監控整個手機的各種AccessibilityService事件時,就會根據我們的邏輯來判斷該使用哪一個指令碼。

因此,我們完全可以抽象出一個基類AccessibilityService,並抽象出一些指令碼的事件,例如,根據Text查詢對應的View、點選某個View、滑動、返回等等,所以,我在這裡封裝了一個BaseAccessibilityService,這裡就不貼具體的程式碼了,大家可以參考我的Github:

github.com/xuyisheng/A…

入門

不知道從什麼時候開始,AccessibilityService突然從一個殘障人士使用的輔助服務,一躍變成了各種App的黑科技,利用AccessibilityService來做的事情,也越來越偏離了AccessibilityService設計的初衷,各種安全問題也隨之暴露出來,Google的理想是好的,願天下都是安分守己的程式設計師。

免Root自動安裝

這個也許是能考證的最早利用AccessibilityService的使用場景了,最早在一些應用市場中出現,例如使用者一次下載了很多App,那麼每個App下載完畢後都會彈出安裝介面,而且需要使用者手動去處理,確實體驗不太好,所以後來就出現了利用Root許可權來靜默安裝App的功能,但現在普通使用者Root的需求越來越少,所以,AccessibilityService來實現免Root自動安裝的黑科技,才走上了桌面。

那麼按照我們前面的思路,要實現自動安裝,實際上就是把手動安裝的步驟指令碼化。一般來說,我們要安裝一個App,會通過以下幾個步驟:

  1. 呼叫系統的安裝Intent
  2. 在安裝介面上尋找『安裝』、『下一步』這些操作按鈕
  3. 點選『安裝』、『下一步』按鈕
  4. 完成安裝

那麼這些流程化的操作,我們就完全可以通過指令碼來實現,下面就是一些簡單的程式碼實現。

呼叫系統安裝Intent:

public void autoInstall(View view) {
    String apkPath = Environment.getExternalStorageDirectory() + "/test.apk";
    Uri uri = Uri.fromFile(new File(apkPath));
    Intent localIntent = new Intent(Intent.ACTION_VIEW);
    localIntent.setDataAndType(uri, "application/vnd.android.package-archive");
    startActivity(localIntent);
}複製程式碼

監控安裝介面,並根據邏輯處理點選:

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    super.onAccessibilityEvent(event);
    if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED &&
            event.getPackageName().equals("com.android.packageinstaller")) {
        AccessibilityNodeInfo nodeInfo = findViewByText("安裝", true);
        if (nodeInfo != null) {
            performViewClick(nodeInfo);
        }
    }
}複製程式碼

程式碼寫完才發現,看似很牛逼的自動安裝,其實不過十幾行程式碼。唯一複雜的,就是抽象化這些流程了。

搶紅包

搶紅包應該是AccessibilityService火起來的最大因素。網上藉助AccessibilityService來實現的搶紅包外掛也是數不勝數,又是一個看上去很牛逼的功能。那麼我們再來分析下,你是怎麼搶紅包的。

加入你現在在桌面,怎麼知道有紅包了呢?哦,看通知欄,出現了『微信紅包』這幾個關鍵字,然後,你點選這條通知進去,點選紅包的那條訊息,然後再點選拆紅包的按鈕,返回,回到桌面。

這樣一看,搶紅包完全是一個體力活啊,如果有個機器人能幫助我完成上面的動作,根本不用我搶啊,對的,這個機器人就是AccessibilityService,我們同樣把搶紅包流程化。

  1. 獲取通知欄通知事件
  2. 點選通知欄訊息
  3. 找到紅包訊息
  4. 點選
  5. 點選拆紅包
  6. 返回

這每個步驟,也都不難啊,我們的工具類中,所有的方法都實現了,唯一要做的,就是寫幾個ifelse把邏輯拼起來就行了,具體程式碼就不貼了,畢竟是微信嚴打的一件事,大家適可而止就好了。

當然,這個Demo同樣可以做的更完善一點,例如,增加WakeLock和Keyguard,實現在鎖屏情況下的自動搶紅包等功能。

微信自動回覆

在瞭解了微信搶紅包的方式之後,再看看微信自動回覆,是不是就更是小菜一碟了?我們只要把搶紅包的流程稍微改一下,就完成了整個功能的實現,不相信?

  1. 獲取通知欄通知事件
  2. 點選通知欄訊息
  3. 找到紅包訊息 ——> 輸入自動回覆的訊息
  4. 點選 ——> 點選傳送
  5. 點選拆紅包 ——> 不需要了
  6. 返回

是不是非常簡單?唯一一個有價值的程式碼如下:

private void notifyWechat(AccessibilityEvent event) {
    if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
        Notification notification = (Notification) event.getParcelableData();
        String content = notification.tickerText.toString();
        String[] msg = content.split(":");
        name = msg[0].trim();
        text = msg[1].trim();
        PendingIntent pendingIntent = notification.contentIntent;
        try {
            pendingIntent.send();
        } catch (PendingIntent.CanceledException e) {
            e.printStackTrace();
        }
    }
}複製程式碼

一個簡單的Trick而已,借用notification.contentIntent來喚起Notification對應的App。

實際上,我們能做的事情還有很多,當我們拿到對應的聊天資訊時,可以通過聊天物件的篩選,來實現對『特別物件的監控』,例如你離開的時候,可以設定給你的老婆自動回覆『親愛的我在忙呢,等等哈』,而對其它人自動回覆『滾,LZ忙』。再例如,可以對聊天資訊進行分詞、識別,從而實現對內容的精準回覆,當然,這裡還需要使用到一些第三方的語言分析軟解,這裡就不詳解了,總之,沒有想不到。

檢查微信好友

那麼再比如去年比較火的一個方法,通過拉好友進群組來檢查是否還有好友關係。PC、Chrome上已經有很多軟體來做這個檢查了,其核心原理,都是通過拉群組的方式來做。那麼在手機上,同樣可以通過這種方式來實現,如果現在你還不知道該怎麼做,那麼後面的文章就沒有看的必要了……

程式清理

大家應該都用過馮老師的『綠色守護』,這個App的最基本無Root功能,就是通過在應用管理介面『結束程式』的方式來停止一個後臺執行的App,大家都知道天朝的App,基本都是全家桶,所以這種方式對釋放系統資源確實還是有一定的幫助的,那麼我們就來看看簡單的實現。

核心原理非常簡單,在應用詳情頁面,通過停止服務來禁止App服務。OK,那麼我們要做的,實際上,就是下面的流程:

  1. 通過Intent開啟對應App的管理詳情資訊頁面
  2. 點選停止執行
  3. 返回,處理下一個

流程要比搶紅包什麼的簡單多了,下面列出2個關鍵程式碼,大家應用詳情介面:

public void cleanProcess(View view) {
    for (String mPackage : mPackages) {
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", mPackage, null);
        intent.setData(uri);
        startActivity(intent);
    }
}複製程式碼

監控詳情頁面,進行停止操作:

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED &&
            event.getPackageName().equals("com.android.settings")) {
        CharSequence className = event.getClassName();
        if (className.equals("com.android.settings.applications.InstalledAppDetailsTop")) {
            AccessibilityNodeInfo info = findViewByText("強行停止");
            if (info.isEnabled()) {
                performViewClick(info);
            } else {
                performBackClick();
            }
        }
        if (className.equals("android.app.AlertDialog")) {
            clickTextViewByText("確定");
            performBackClick();
        }
    }
}複製程式碼

這個App唯一的難點,應該就剩下怎麼把UI做的好看一點了。

另外,還有一個相容性的問題,大家都懂的,國內各種第三方的ROM廠家,經常會修改一些系統的Activity,甚至不同系統版本同一個功能的Activity都有可能不一樣,所以,使用AccessibilityService的一個比較大的麻煩就是相容性的處理,需要使用dumpsys和uiautomator這些工具來進行詳細的分析,這些工具的使用以及分析方法,在我的新書《Android群英傳:神兵利器》中都有詳細的講解,想深入瞭解的開發者可以參考下。

判斷應用當前狀態

藉助AccessibilityService同樣可以做一些比較有用的事情,例如監控App當前的狀態,例如前臺、後臺的切換,通過TYPE_WINDOW_STATE_CHANGED即可進行判斷,特別是在5.0以上,原先的getRunningTasks這個方法被升級到系統許可權。

當然,AccessibilityService或多或少會存在一些效能問題,所以現在並不推薦使用這種方式來監控應用狀態,更多的是通過activitylifecyclecallbacks來實現對App狀態的跟蹤與監控。

出軌

其實一旦我們瞭解了AccessibilityService的使用原理,那麼就很難做到不逾矩,畢竟這裡的誘惑太大了,當我寫到這裡時,甚至有種不寒而慄的感覺,所以這裡申明:
本文所有內容僅供學習、技術交流,由此產生的各種問題,均與本人無關。

防解除安裝

據我所知,已經有些App或者稱之為惡意軟體實現了這樣的功能,這個功能難嗎,不難,估計都不超過20行程式碼,但確實很噁心,特別是對一些普通、小白使用者,壓根都不知道AccessibilityService是什麼,莫名其妙你讓我啟用,寫的可能比較好看,什麼幫助你清理系統,優化資源,但實際上,在後面做一些見不得人的事情。

我們來分析下如何實現,當使用者想要解除安裝你的App的時候,一般會來到設定介面,找到你的App然後選擇解除安裝,那麼如果我們監控這個頁面,如果發現是自己的App,就直接退出,這樣不就無法解除安裝了嗎?是的,程式碼如下,沒幾行程式碼:

private String mDefenseName = "微信";

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    super.onAccessibilityEvent(event);
    if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED &&
            event.getPackageName().equals("com.android.settings")) {
        CharSequence className = event.getClassName();
        if (className.equals("com.android.settings.SubSettings")) {
            AccessibilityNodeInfo nodeInfo = findViewByText("應用程式資訊");
            if (nodeInfo != null && findViewByText(mDefenseName) != null) {
                performBackClick();
            }
        }
    }
}複製程式碼

那麼有人要說了,如果是用的一些第三方ROM,直接在桌面就能解除安裝呢?同樣的,只不過會稍微麻煩點,需要判斷的東西更多了,要處理的相容性更復雜了而已。

這裡不得不說,雖然國內各種第三方ROM百花齊放、肆意妄為,但這也給AccessibilityService造成了很大的相容性處理難題,所以對一些惡意的使用AccessibilityService的App也形成了很大的限制。

瀏覽器劫持

實際上並不侷限於瀏覽器,各種App都能被劫持,因為AccessibilityService監控的是全域性App,良心點的可能會指定包名進行監控。所以,我們可以監控任意一個App,例如瀏覽器,一旦開啟,我們就輸入指定的網址,或者是一開啟一些App,就輸入一些查詢內容,這裡我以鄙司的滬江網校為例,進入後直接進行搜尋。

算了程式碼還是不貼了,完全都是Copy前面的內容。

監控密碼框

呵呵呵,這個你還真是想多了,系統再天真也不會把這個許可權開放給你,所有的設定為password型別的EditText都是無法被監控的,系統還算有點良心。

這裡我只列舉了一些非常簡單的Hack方式,但實際上,還有很多,例如通過拉取指定網站的內容後自動安裝App並模擬點選等,當然,AccessibilityService也可以用在自動化測試中,這完全就是一把雙刃劍,是利是弊,完全取決於使用他的人。

跳過使用者授權

一般來說,AccessibilityService是需要使用者手動操作授權才可以執行的,但是,如果是在Root的情況下,或者是在ADB連線PC的情況下,甚至都不用使用者授權,就可以完成AccessibilityService的授權操作。

Root的情況就不說了,通過修改Setting的資料庫就可以更改這個設定了,當然,有Root的情況下,就根本不需要AccessibilityService了。

在沒有Root的情況下,如果PC通過ADB發出指令,同樣是可以自動完成授權的,這個可以參考360的一篇文章:

www.freebuf.com/articles/te…

我這裡就不多說了,大家看看就懂了,並沒有太多的技術含量,應該算是系統的一個小的漏洞。

AccessibilityService一般分析步驟

前面我們分析了那麼多AccessibilityService好的不好的使用方法,實際上,總結下就這麼幾步。

  1. 分析操作的流程,拆解成單步可實現的過程
  2. 通過UIAutomator和adb shell dumpsys來檢視對應的UI控制元件ID、文字或者是具體的Activity
  3. 通過邏輯組合進行程式碼編寫
  4. 除錯、相容性處理

通過上面的這些方式,基本就可以實現一些固定流程的操作自動化了。關於AccessibilityService的工具類,我放到了Github上,雖然功能已經比較全了,但還沒有經過很多的相容性測試,同時,礙於時間和精力的關係,給出的Demo示例也不多,希望大家可以多提PR,共同完善。

github.com/xuyisheng/A…

歡迎大家關注我的微信公眾號:

AccessibilityService 從入門到出軌

相關文章