1. 程式人生 > >Android root檢測方法小結

Android root檢測方法小結

出於安全原因,我們的應用程式不建議在已經root的裝置上執行,所以需要檢測是否裝置已經root,以提示使用者若繼續使用會存在風險。

那麼root了會有什麼風險呢,為什麼不root就沒有風險,又怎麼來檢查手機是否root了?

我們先來了解下Android安全機制:

Android安全架構是基於Linux多使用者機制的訪問控制。應用程式在預設的情況下不可以執行其他應用程式,包括讀或寫使用者的私有資料(如聯絡人資料或email資料),讀或寫另一個應用程式的檔案。
一個應用程式的程序就是一個安全的沙盒(在受限的安全環境中執行應用程式,在沙盒中的所有改動對作業系統不會造成任何危害)。它不能干擾其它應用程式,除非顯式地聲明瞭“permissions”,以便它能夠獲取基本沙盒所不具備的額外的能力。
每一個Android應用程式都會在安裝時就分配一個獨有的Linux使用者ID,這就為它建立了一個沙盒,使其不能與其他應用程式進行接觸。這個使用者ID會在安裝時分配給它,並在該裝置上一直保持同一個數值。
所有的Android應用程式必須用證書進行簽名認證,而這個證書的私鑰是由開發者保有的。該證書可以用以識別應用程式的作者。簽名影響安全性的最重要的方式是通過決定誰可以進入基於簽名的permisssions,以及誰可以share 使用者IDs。通過這樣的機制,在不考慮root使用者的情況下,每個應用都是相互隔離的,實現了一定的安全。

為什麼要把root排除在外,才能說應用的隔離是安全的呢?

在Linux作業系統中,root的許可權是最高的,也被稱為超級許可權的擁有者。
在系統中,每個檔案、目錄和程序,都歸屬於某一個使用者,沒有使用者許可其它普通使用者是無法操作的,但對root除外。

root使用者的特權性還表現在:root可以超越任何使用者和使用者組來對檔案或目錄進行讀取、修改或刪除(在系統正常的許可範圍內);對可執行程式的執行、終止;對硬體裝置的新增、建立和移除等;也可以對檔案和目錄進行屬主和許可權進行修改,以適合系統管理的需要(因為root是系統中許可權最高的特權使用者);root是超越任何使用者和使用者組的,基於使用者ID的許可權機制的沙盒是隔離不了它的。

接下來了解下root的方式

通常可以分為2種:
1,不完全Root
2,完全Root
目前獲取Android root 許可權常用方法是通過各種系統漏洞,替換或新增SU程式到裝置,獲取Root許可權,而在獲取root許可權以後,會裝一個程式用以提醒使用者是否給予程式最高許可權,可以一定程度上防止惡意軟體,通常會使用Superuser或者 SuperSU ,這種方法通常叫做“不完全Root”。
而 “完全ROOT”是指,替換裝置原有的ROM,以實現取消secure設定。

root檢測的方法

下面介紹下root檢測的各種方法:

1,檢視系統是否測試版

我們可以檢視釋出的系統版本,是test-keys(測試版),還是release-keys(釋出版)。
可以先在adb shell中執行下命令檢視:

root@android:/ # cat /system/build.prop | grep ro.build.tags
ro.build.tags=release-keys

這個返回結果“release-keys”,代表此係統是正式釋出版。
在程式碼中的檢測方法如下:

    public static boolean checkDeviceDebuggable(){
        String buildTags = android.os.Build.TAGS;
        if (buildTags != null && buildTags.contains("test-keys")) {
            Log.i(LOG_TAG,"buildTags="+buildTags);
            return true;
        }
        return false;
    }

若是非官方釋出版,很可能是完全root的版本,存在使用風險。
可是在實際情況下,我遇到過某些廠家的正式釋出版本,也是test-keys,可能大家對這個標識也不是特別注意吧。所以具體是否使用,還要多考慮考慮呢。也許能解決問題,也許會給自己帶來些麻煩。

2,檢查是否存在Superuser.apk

Superuser.apk是一個被廣泛使用的用來root安卓裝置的軟體,所以可以檢查這個app是否存在。
檢測方法如下:

    public static boolean checkSuperuserApk(){
        try {
            File file = new File("/system/app/Superuser.apk");
            if (file.exists()) {
                Log.i(LOG_TAG,"/system/app/Superuser.apk exist");
                return true;
            }
        } catch (Exception e) { }
        return false;
    }

3,檢查su命令

su是Linux下切換使用者的命令,在使用時不帶引數,就是切換到超級使用者。通常我們獲取root許可權,就是使用su命令來實現的,所以可以檢查這個命令是否存在。
有三個方法來測試su是否存在:
1)檢測在常用目錄下是否存在su

    public static boolean checkRootPathSU()
    {
        File f=null;
        final String kSuSearchPaths[]={"/system/bin/","/system/xbin/","/system/sbin/","/sbin/","/vendor/bin/"};
        try{
            for(int i=0;i<kSuSearchPaths.length;i++)
            {
                f=new File(kSuSearchPaths[i]+"su");
                if(f!=null&&f.exists())
                {
                    Log.i(LOG_TAG,"find su in : "+kSuSearchPaths[i]);
                    return true;
                }
            }
        }catch(Exception e)
        {
            e.printStackTrace();
        }
        return false;
    }

這個方法是檢測常用目錄,那麼就有可能漏過不常用的目錄。
所以就有了第二個方法,直接使用shell下的命令來查詢。

2)使用which命令檢視是否存在su
which是linux下的一個命令,可以在系統PATH變數指定的路徑中搜索某個系統命令的位置並且返回第一個搜尋結果。
這裡,我們就用它來查詢su。

    public static boolean checkRootWhichSU() {
        String[] strCmd = new String[] {"/system/xbin/which","su"};
        ArrayList<String> execResult = executeCommand(strCmd);
        if (execResult != null){
            Log.i(LOG_TAG,"execResult="+execResult.toString());
            return true;
        }else{
            Log.i(LOG_TAG,"execResult=null");
            return false;
        }
    }

其中呼叫了一個函式 executeCommand(),是執行linux下的shell命令。具體實現如下:

    public static ArrayList<String> executeCommand(String[] shellCmd){
        String line = null;
        ArrayList<String> fullResponse = new ArrayList<String>();
        Process localProcess = null;
        try {
            Log.i(LOG_TAG,"to shell exec which for find su :");
            localProcess = Runtime.getRuntime().exec(shellCmd);
        } catch (Exception e) {
            return null;
        }
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(localProcess.getOutputStream()));
        BufferedReader in = new BufferedReader(new InputStreamReader(localProcess.getInputStream()));
        try {
            while ((line = in.readLine()) != null) {
                Log.i(LOG_TAG,"–> Line received: " + line);
                fullResponse.add(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.i(LOG_TAG,"–> Full response was: " + fullResponse);
        return fullResponse;
    }

然而,這個方法也存在一個缺陷,就是需要系統中存在which這個命令。我在測試過程中,就遇到有的Android系統中沒有這個命令,所以,這也不是一個完全有保障的方法,倒是可以和上一個方法(在常用路徑下查詢)進行組合,能提升成功率。
這種查詢命令的方式,還有一種缺陷,就是可能系統中存在su,但是已經失效的情況。例如,我曾經root過,後來又取消了,就可能出現這種情況:有su這個檔案,但是當前裝置不是root的。

3)執行su,看能否獲取到root許可權
由於上面兩種查詢方法都存在可能查不到的情況,以及有su檔案與裝置root的差異,所以,有這第三中方法:我們執行這個命令su。這樣,系統就會在PATH路徑中搜索su,如果找到,就會執行,執行成功後,就是獲取到真正的超級許可權了。
具體程式碼如下:

 public static synchronized boolean checkGetRootAuth()
    {
        Process process = null;
        DataOutputStream os = null;
        try
        {
            Log.i(LOG_TAG,"to exec su");
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.writeBytes("exit\n");
            os.flush();
            int exitValue = process.waitFor();
            Log.i(LOG_TAG, "exitValue="+exitValue);
            if (exitValue == 0)
            {
                return true;
            } else
            {
                return false;
            }
        } catch (Exception e)
        {
            Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        } finally
        {
            try
            {
                if (os != null)
                {
                    os.close();
                }
                process.destroy();
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

這種檢測su的方法,應該是最靠譜的,不過,也有個問題,就是在已經root的裝置上,會彈出提示框,請求給app開啟root許可權。這個提示不太友好,可能使用者會不喜歡。
如果想安靜的檢測,可以用上兩種方法的組合;如果需要儘量安全的檢測到,還是執行su吧。

4,執行busybox

Android是基於Linux系統的,可是在終端Terminal中操作,會發現一些基本的命令都找不到。這是由於Android系統為了安全,將可能帶來風險的命令都去掉了,最典型的,例如su,還有find、mount等。對於一個已經獲取了超級許可權的人來講,這是很不爽的事情,所以,便要想辦法加上自己需要的命令了。一個個新增命令也麻煩,有一個很方便的方法,就是使用被稱為“嵌入式Linux中的瑞士軍刀”的Busybox。簡單的說BusyBox就好像是個大工具箱,它整合壓縮了 Linux 的許多工具和命令。
所以若裝置root了,很可能Busybox也被安裝上了。這樣我們執行busybox測試也是一個好的檢測方法。

   public static synchronized boolean checkBusybox()
    {
        try
        {
            Log.i(LOG_TAG,"to exec busybox df");
            String[] strCmd = new String[] {"busybox","df"};
            ArrayList<String> execResult = executeCommand(strCmd);
            if (execResult != null){
                Log.i(LOG_TAG,"execResult="+execResult.toString());
                return true;
            }else{
                Log.i(LOG_TAG,"execResult=null");
                return false;
            }
        } catch (Exception e)
        {
            Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        }
    }

5,訪問/data目錄,檢視讀寫許可權

在Android系統中,有些目錄是普通使用者不能訪問的,例如 /data、/system、/etc 等。
我們就已/data為例,來進行讀寫訪問。本著謹慎的態度,我是先寫入一個檔案,然後讀出,檢視內容是否匹配,若匹配,才認為系統已經root了。

public static synchronized boolean checkAccessRootData()
    {
        try
        {
            Log.i(LOG_TAG,"to write /data");
            String fileContent = "test_ok";
            Boolean writeFlag = writeFile("/data/su_test",fileContent);
            if (writeFlag){
                Log.i(LOG_TAG,"write ok");
            }else{
                Log.i(LOG_TAG,"write failed");
            }

            Log.i(LOG_TAG,"to read /data");
            String strRead = readFile("/data/su_test");
            Log.i(LOG_TAG,"strRead="+strRead);
            if(fileContent.equals(strRead)){
                return true;
            }else {
                return false;
            }
        } catch (Exception e)
        {
            Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        }
    }

上面的程式碼,呼叫了兩個函式:writeFile()寫檔案,readFile()讀檔案,下面是具體實現:

//寫檔案
    public static Boolean writeFile(String fileName,String message){
        try{
            FileOutputStream fout = new FileOutputStream(fileName);
            byte [] bytes = message.getBytes();
            fout.write(bytes);
            fout.close();
            return true;
        }
        catch(Exception e){
            e.printStackTrace();
            return false;
        }
    }
//讀檔案
    public static String readFile(String fileName){
        File file = new File(fileName);
        try {
            FileInputStream fis= new FileInputStream(file);
            byte[] bytes = new byte[1024];
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len;
            while((len=fis.read(bytes))>0){
                bos.write(bytes, 0, len);
            }
            String result = new String(bos.toByteArray());
            Log.i(LOG_TAG, result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

這裡說句題外話,我最初是想使用shell命令來寫檔案:

echo "test_ok" > /data/su_test

可是使用executeCommand()來呼叫執行這個命令,結果連檔案都沒有創建出來。在多次失敗後才想到:應該是這個shell命令涉及到了重定向(例如本例中,將本來應該螢幕輸出的資訊轉而寫入檔案中),才導致的失敗。這個重定向應該是需要寫程式碼獲取資料流來自己實現。不過,既然要寫程式碼使用資料流,那麼我可以更簡單的直接寫檔案,就沒有去嘗試用程式碼來實現重定向了。

小結:

由於每種方法各有其特色與缺陷,所以我最終將這些方法加起來了。注意,檢查su的3種方法,不必都使用上,可以選第一二種查詢的方法,或者選第三種執行的方法。
組合呼叫的程式碼如下:

    private static String LOG_TAG = CheckRoot.class.getName();
    public static boolean isDeviceRooted() {
        if (checkDeviceDebuggable()){return true;}//check buildTags
        if (checkSuperuserApk()){return true;}//Superuser.apk
        //if (checkRootPathSU()){return true;}//find su in some path
        //if (checkRootWhichSU()){return true;}//find su use 'which'
        if (checkBusybox()){return true;}//find su use 'which'
        if (checkAccessRootData()){return true;}//find su use 'which'
        if (checkGetRootAuth()){return true;}//exec su

        return false;
    }

參考:

相關推薦

Android root檢測方法小結

出於安全原因,我們的應用程式不建議在已經root的裝置上執行,所以需要檢測是否裝置已經root,以提示使用者若繼續使用會存在風險。 那麼root了會有什麼風險呢,為什麼不root就沒有風險,又怎麼來檢查手機是否root了? 我們先來了解下Android安

Android ViewPager使用方法小結

nco col tile 情況 谷歌 obj wpa sla 釋放 android-support-v4.jar 是谷歌提供給我們的一個兼容低版本安卓設備的軟件包,裏面包囊了只有在 Android 3.0 以上可用的API。而 ViewPager 就是其中之一。利用它,我們

Android PopupWindow使用方法小結

ring hub tps enter offset [] 註意 外部 現在 前幾天要用到PopupWindow,一時竟想不起來怎麽用,趕緊上網查了查,自己寫了個demo,並在此記錄一下PopupWindow的用法。 使用場景 PopupWindow,顧名思義,就是彈窗,在很

通用的的android root方法

現在root android主要是使用漏洞,但是漏洞不好找,也不利用,還很容易 被補掉,所以不是一個好的root方法。 最好用的root的方法還是刷機,但是市場上的手機都加密了,把bl鎖住了,不能使用fastboot刷機,為了能刷市場上的加密的手機,需要先在淘寶上買一個加密狗 例如淘寶裡搜尋

檢測Android模擬器的方法和程式碼實現

專自:https://bbs.pediy.com/thread-225717.htm 剛剛看了一些關於Detect Android Emulator的開源專案/文章/論文, 我看的這些其實都是13年14年提出的方法, 方法裡大多是檢測一些環境屬性, 檢查一些檔案這樣, 但實際上檢測的思路並不侷限

小結】常見漏洞檢測方法

1、SQL注入 bp抓取請求資料包,儲存至txt文件中,如1.txt,藉助sqlmap進行測試,sqlmap命令: sqlmap.py -r 1.txt --risk 3 --current-db --batch 2、XSS漏洞 bp抓取請求資料包,檢測方法如下: 一種藉助bp的scan模

目標檢測方法效果小結

one stage 檢測演算法 R-CNN: 使用selective search方法先產生region proposals,再使用淺層CNN網路進行特徵提取,最後使用svm進行分類。這篇論文裡提及的一個點,就是關於bbox的迴歸方法。由於使用selective search方法提取的每一個

Android實現延遲的幾種方法小結

本文例項總結了Android實現延遲的幾種方法。分享給大家供大家參考,具體如下: 一、通過Thread new Thread(){ public void run(){ sleep(***); } }.start(); 通過ProgressDialog的使用來

asp.net(c#)上傳檔案時檢測檔案型別方法小結

using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using Syst

Android程式設計實現WebView自適應全屏方法小結

本文例項講述了Android程式設計實現WebView自適應全屏的方法。分享給大家供大家參考,具體如下: 第一種: settings.setUseWideViewPort(true); settings.setLoadWithOverviewMode(t

Android獲取Root許可權方法

1、把ADB解壓後,隨便放在任一磁碟下,最好把目錄名改短點,不然DOS下進比較麻煩。5 U6 n) i, D2 w6 t3 b( C4 B3 z) w 2、把SU檔案解壓,放到卡上,最好是根目錄下2 v1 ]; G6 p# G" u- L 3、把V5 連線上電腦 ,電腦會

Android SDK提供的獲取檔案路徑方法小結

Android提供了兩種獲取檔案路徑的方式: 1、通過Environment獲取,常用方法如下圖所示: 這些方法都是靜態方法。 通過Environment獲取的檔案路徑都是公共的,是所有app都可以訪問的,其中我們最常用的就是Environmen.

Android模擬器檢測常用方法

在Android開發過程中,防作弊一直是老生常談的問題,而模擬器的檢測往往是防作弊中的重要一環,接下來有關於模擬器的檢測方法,和大家進行一個簡單的分享。 1.傳統的檢測方法。 傳統的檢測方法主要是對模擬器的IMSI、IDS、預設檔案等幾個方面進行檢測。 (1)預設號碼:

Android 判斷Root方法

方法一:(不彈框)     private final static int kSystemRootStateUnknow = -1;     private final static int kSystemRootStateDisable = 0;     private

Android mac地址獲取的方法小結及可能出現的問題

     這段時間專案遇到個問題,客戶把移動裝置回廠修理後再安裝我們的專案,執行會報錯。後來經過我仔細排查發現一個很詭異的問題,就是無法獲取mac地址了。於是我仔細把獲取mac地址的一些資料看了看,加上一些除錯,總算是解決了這個問題。現在決定把這塊小結下,以免再次遇到問題。

Android TCP真正有效的斷鏈檢測方法

1. 利用socket提供的isConnected()與isClosed()方法來判斷。但這種方式只是本地判斷,只是本地操作connect()或close()方法後儲存的一個狀態,對於遠端伺服器主動斷開就沒有用了。 2. 利用socket中的sendUrgentData()

Android的一些方法和屬性

註意 odin 技術 drawable button display round clas class 1.Activity常用的方法   View findViewById(int id) //根據組件的ID取得組件對象   setContentView(int

java中獲取各種上下文路徑的方法小結

取出 resin pri nbsp ont row span user ade 一、獲得都是當前運行文件在服務器上的絕對路徑在servlet裏用:this.getServletContext().getRealPath(); 在struts用:this.getServlet

Android configChanges使用方法

port sca 顯示效果 style pos nbsp 初始化 重復調用 rac 1. 在manifest文件裏使用activity的默認屬性。橫屏豎屏時,惠重復調用onDestory和onCreate 造成不必要的開銷。Android默認如此應該是為了適配不

使用新版Android Studio檢測內存泄露和性能

qq空間 fun selection book 檢測 內存 lips info ava http://www.jianshu.com/p/216b03c22bb8 內存泄露,是Android開發者最頭疼的事。可能一處小小的內存泄露,都可能是毀於千裏之堤的蟻穴。怎麽