AccessibilityService從入門到出軌

AccessibilityService從入門到出軌

AccessibilityService根據(jù)官方的介紹,是指開發(fā)者通過增加類似contentDescription的屬性,從而在不修改代碼的情況下,讓殘障人士能夠獲得使用體驗(yàn)的優(yōu)化,大家可以打開AccessibilityService來試一下,點(diǎn)擊區(qū)域,可以有語音或者觸摸的提示,幫助殘障人士使用App。

當(dāng)然,現(xiàn)在AccessibilityService已經(jīng)基本偏離了它設(shè)計的初衷,至少在國內(nèi)是這樣,越來越多的App借用AccessibilityService來實(shí)現(xiàn)了一些其它功能,甚至是灰色產(chǎn)品。

使用入門

老規(guī)矩,官網(wǎng)鎮(zhèn)樓
https://developer.android.com/guide/topics/ui/accessibility/services.html
https://developer.android.com/training/accessibility/service.html

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

繼承系統(tǒng)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() {
    }
}

其中有兩個必須實(shí)現(xiàn)的方法:onAccessibilityEvent和onInterrupt。

在onAccessibilityEvent中,我們可以接收所監(jiān)聽的事件。不熟悉這些事件的話,只需要使用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 為 用戶允許應(yīng)用的輔助功能的說明字符串,這里沒有指定所要輔助的應(yīng)用packageNames,當(dāng)沒有指定時,默認(rèn)輔助所有的應(yīng)用,建議大家在使用時,指定需要監(jiān)聽的包名(你可以通過|來進(jìn)行分隔),而不是所有的包名。typeAllMask是設(shè)置響應(yīng)事件的類型,feedbackGeneric是設(shè)置回饋給用戶的方式,有語音播出和振動。

注冊

在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具有很高的系統(tǒng)權(quán)限,所以,系統(tǒng)不會讓App直接設(shè)置是否啟用,需要用戶進(jìn)入設(shè)置-輔助功能中去手動啟用,這樣在一定程度上,保護(hù)了用戶數(shù)據(jù)的安全。

如何理解AccessibilityService

很多人可能對AccessibilityService了解的不是很深入,所以認(rèn)為AccessibilityService是在調(diào)用一些系統(tǒng)服務(wù)來自動執(zhí)行一些操作,實(shí)際上,這個理解不能算錯,當(dāng)然也不全對,我覺得你可以把AccessibilityService理解為——『按鍵精靈』。相信很多開發(fā)者都玩過PC上的這款軟件,他的作用,就是將你一次操作的整個記錄,錄制下來,然后就可以根據(jù)這個記錄,重復(fù)的執(zhí)行這些操作,例如:先點(diǎn)擊某個輸入框,再輸入XXXX,再輸入驗(yàn)證碼,最后點(diǎn)擊某按鈕,這些操作如果需要重復(fù)執(zhí)行,那么顯然是一套機(jī)械的步驟,那么通過按鍵精靈,記錄下這些操作后,直接通過腳本就可以完成這些操作。其實(shí)AccessibilityService跟這個是一樣的,我們記錄的,實(shí)際上就是我們的操作步驟,或者稱之為『腳本』,那么系統(tǒng)在監(jiān)控整個手機(jī)的各種AccessibilityService事件時,就會根據(jù)我們的邏輯來判斷該使用哪一個腳本。

因此,我們完全可以抽象出一個基類AccessibilityService,并抽象出一些腳本的事件,例如,根據(jù)Text查找對應(yīng)的View、點(diǎn)擊某個View、滑動、返回等等,所以,我在這里封裝了一個BaseAccessibilityService,這里就不貼具體的代碼了,大家可以參考我的Github:

https://github.com/xuyisheng/AccessibilityUtil

入門

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

免Root自動安裝

這個也許是能考證的最早利用AccessibilityService的使用場景了,最早在一些應(yīng)用市場中出現(xiàn),例如用戶一次下載了很多App,那么每個App下載完畢后都會彈出安裝界面,而且需要用戶手動去處理,確實(shí)體驗(yàn)不太好,所以后來就出現(xiàn)了利用Root權(quán)限來靜默安裝App的功能,但現(xiàn)在普通用戶Root的需求越來越少,所以,AccessibilityService來實(shí)現(xiàn)免Root自動安裝的黑科技,才走上了桌面。

那么按照我們前面的思路,要實(shí)現(xiàn)自動安裝,實(shí)際上就是把手動安裝的步驟腳本化。一般來說,我們要安裝一個App,會通過以下幾個步驟:

  1. 調(diào)用系統(tǒng)的安裝Intent
  2. 在安裝界面上尋找『安裝』、『下一步』這些操作按鈕
  3. 點(diǎn)擊『安裝』、『下一步』按鈕
  4. 完成安裝

那么這些流程化的操作,我們就完全可以通過腳本來實(shí)現(xiàn),下面就是一些簡單的代碼實(shí)現(xiàn)。

調(diào)用系統(tǒng)安裝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);
}

監(jiān)控安裝界面,并根據(jù)邏輯處理點(diǎn)擊:

@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);
        }
    }
}

代碼寫完才發(fā)現(xiàn),看似很牛逼的自動安裝,其實(shí)不過十幾行代碼。唯一復(fù)雜的,就是抽象化這些流程了。

搶紅包

搶紅包應(yīng)該是AccessibilityService火起來的最大因素。網(wǎng)上借助AccessibilityService來實(shí)現(xiàn)的搶紅包插件也是數(shù)不勝數(shù),又是一個看上去很牛逼的功能。那么我們再來分析下,你是怎么搶紅包的。

加入你現(xiàn)在在桌面,怎么知道有紅包了呢?哦,看通知欄,出現(xiàn)了『微信紅包』這幾個關(guān)鍵字,然后,你點(diǎn)擊這條通知進(jìn)去,點(diǎn)擊紅包的那條消息,然后再點(diǎn)擊拆紅包的按鈕,返回,回到桌面。

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

  1. 獲取通知欄通知事件
  2. 點(diǎn)擊通知欄消息
  3. 找到紅包消息
  4. 點(diǎn)擊
  5. 點(diǎn)擊拆紅包
  6. 返回

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

當(dāng)然,這個Demo同樣可以做的更完善一點(diǎn),例如,增加WakeLock和Keyguard,實(shí)現(xiàn)在鎖屏情況下的自動搶紅包等功能。

微信自動回復(fù)

在了解了微信搶紅包的方式之后,再看看微信自動回復(fù),是不是就更是小菜一碟了?我們只要把搶紅包的流程稍微改一下,就完成了整個功能的實(shí)現(xiàn),不相信?

  1. 獲取通知欄通知事件
  2. 點(diǎn)擊通知欄消息
  3. 找到紅包消息 ——> 輸入自動回復(fù)的消息
  4. 點(diǎn)擊 ——> 點(diǎn)擊發(fā)送
  5. 點(diǎn)擊拆紅包 ——> 不需要了
  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對應(yīng)的App。

實(shí)際上,我們能做的事情還有很多,當(dāng)我們拿到對應(yīng)的聊天信息時,可以通過聊天對象的篩選,來實(shí)現(xiàn)對『特別對象的監(jiān)控』,例如你離開的時候,可以設(shè)置給你的老婆自動回復(fù)『親愛的我在忙呢,等等哈』,而對其它人自動回復(fù)『滾,LZ忙』。再例如,可以對聊天信息進(jìn)行分詞、識別,從而實(shí)現(xiàn)對內(nèi)容的精準(zhǔn)回復(fù),當(dāng)然,這里還需要使用到一些第三方的語言分析軟解,這里就不詳解了,總之,沒有想不到。

檢查微信好友

那么再比如去年比較火的一個方法,通過拉好友進(jìn)群組來檢查是否還有好友關(guān)系。PC、Chrome上已經(jīng)有很多軟件來做這個檢查了,其核心原理,都是通過拉群組的方式來做。那么在手機(jī)上,同樣可以通過這種方式來實(shí)現(xiàn),如果現(xiàn)在你還不知道該怎么做,那么后面的文章就沒有看的必要了……

進(jìn)程清理

大家應(yīng)該都用過馮老師的『綠色守護(hù)』,這個App的最基本無Root功能,就是通過在應(yīng)用管理界面『結(jié)束進(jìn)程』的方式來停止一個后臺運(yùn)行的App,大家都知道天朝的App,基本都是全家桶,所以這種方式對釋放系統(tǒng)資源確實(shí)還是有一定的幫助的,那么我們就來看看簡單的實(shí)現(xiàn)。

核心原理非常簡單,在應(yīng)用詳情頁面,通過停止服務(wù)來禁止App服務(wù)。OK,那么我們要做的,實(shí)際上,就是下面的流程:

  1. 通過Intent打開對應(yīng)App的管理詳情信息頁面
  2. 點(diǎn)擊停止運(yùn)行
  3. 返回,處理下一個

流程要比搶紅包什么的簡單多了,下面列出2個關(guān)鍵代碼,大家應(yīng)用詳情界面:

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);
    }
}

監(jiān)控詳情頁面,進(jìn)行停止操作:

@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("強(qiáng)行停止");
            if (info.isEnabled()) {
                performViewClick(info);
            } else {
                performBackClick();
            }
        }
        if (className.equals("android.app.AlertDialog")) {
            clickTextViewByText("確定");
            performBackClick();
        }
    }
}

這個App唯一的難點(diǎn),應(yīng)該就剩下怎么把UI做的好看一點(diǎn)了。

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

判斷應(yīng)用當(dāng)前狀態(tài)

借助AccessibilityService同樣可以做一些比較有用的事情,例如監(jiān)控App當(dāng)前的狀態(tài),例如前臺、后臺的切換,通過TYPE_WINDOW_STATE_CHANGED即可進(jìn)行判斷,特別是在5.0以上,原先的getRunningTasks這個方法被升級到系統(tǒng)權(quán)限。

當(dāng)然,AccessibilityService或多或少會存在一些性能問題,所以現(xiàn)在并不推薦使用這種方式來監(jiān)控應(yīng)用狀態(tài),更多的是通過activitylifecyclecallbacks來實(shí)現(xiàn)對App狀態(tài)的跟蹤與監(jiān)控。

出軌

其實(shí)一旦我們了解了AccessibilityService的使用原理,那么就很難做到不逾矩,畢竟這里的誘惑太大了,當(dāng)我寫到這里時,甚至有種不寒而栗的感覺,所以這里申明:
本文所有內(nèi)容僅供學(xué)習(xí)、技術(shù)交流,由此產(chǎn)生的各種問題,均與本人無關(guān)。

防卸載

據(jù)我所知,已經(jīng)有些App或者稱之為惡意軟件實(shí)現(xiàn)了這樣的功能,這個功能難嗎,不難,估計都不超過20行代碼,但確實(shí)很惡心,特別是對一些普通、小白用戶,壓根都不知道AccessibilityService是什么,莫名其妙你讓我啟用,寫的可能比較好看,什么幫助你清理系統(tǒng),優(yōu)化資源,但實(shí)際上,在后面做一些見不得人的事情。

我們來分析下如何實(shí)現(xiàn),當(dāng)用戶想要卸載你的App的時候,一般會來到設(shè)置界面,找到你的App然后選擇卸載,那么如果我們監(jiān)控這個頁面,如果發(fā)現(xiàn)是自己的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("應(yīng)用程序信息");
            if (nodeInfo != null && findViewByText(mDefenseName) != null) {
                performBackClick();
            }
        }
    }
}

那么有人要說了,如果是用的一些第三方ROM,直接在桌面就能卸載呢?同樣的,只不過會稍微麻煩點(diǎn),需要判斷的東西更多了,要處理的兼容性更復(fù)雜了而已。

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

瀏覽器劫持

實(shí)際上并不局限于瀏覽器,各種App都能被劫持,因?yàn)锳ccessibilityService監(jiān)控的是全局App,良心點(diǎn)的可能會指定包名進(jìn)行監(jiān)控。所以,我們可以監(jiān)控任意一個App,例如瀏覽器,一旦打開,我們就輸入指定的網(wǎng)址,或者是一打開一些App,就輸入一些查詢內(nèi)容,這里我以鄙司的滬江網(wǎng)校為例,進(jìn)入后直接進(jìn)行搜索。

算了代碼還是不貼了,完全都是Copy前面的內(nèi)容。

監(jiān)控密碼框

呵呵呵,這個你還真是想多了,系統(tǒng)再天真也不會把這個權(quán)限開放給你,所有的設(shè)置為password類型的EditText都是無法被監(jiān)控的,系統(tǒng)還算有點(diǎn)良心。

這里我只列舉了一些非常簡單的Hack方式,但實(shí)際上,還有很多,例如通過拉取指定網(wǎng)站的內(nèi)容后自動安裝App并模擬點(diǎn)擊等,當(dāng)然,AccessibilityService也可以用在自動化測試中,這完全就是一把雙刃劍,是利是弊,完全取決于使用他的人。

跳過用戶授權(quán)

一般來說,AccessibilityService是需要用戶手動操作授權(quán)才可以執(zhí)行的,但是,如果是在Root的情況下,或者是在ADB連接PC的情況下,甚至都不用用戶授權(quán),就可以完成AccessibilityService的授權(quán)操作。

Root的情況就不說了,通過修改Setting的數(shù)據(jù)庫就可以更改這個設(shè)置了,當(dāng)然,有Root的情況下,就根本不需要AccessibilityService了。

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

http://www.freebuf.com/articles/terminal/114045.html

我這里就不多說了,大家看看就懂了,并沒有太多的技術(shù)含量,應(yīng)該算是系統(tǒng)的一個小的漏洞。

AccessibilityService一般分析步驟

前面我們分析了那么多AccessibilityService好的不好的使用方法,實(shí)際上,總結(jié)下就這么幾步。

  1. 分析操作的流程,拆解成單步可實(shí)現(xiàn)的過程
  2. 通過UIAutomator和adb shell dumpsys來查看對應(yīng)的UI控件ID、文本或者是具體的Activity
  3. 通過邏輯組合進(jìn)行代碼編寫
  4. 調(diào)試、兼容性處理

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

https://github.com/xuyisheng/AccessibilityUtil

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容