2009-07-09 78 views
218

我的應用程序具有某種功能,只能在可用根的設備上運行。當使用這個功能(並向用戶顯示一個合適的錯誤信息)時,而不是讓這個功能失效,我寧願有一種能力來靜靜地檢查root是否可用,如果不是,首先隱藏相應的選項。確定是否在根設備上運行

有沒有辦法做到這一點?

+8

沒有可靠的方法來做到這一點;下面的答案會檢查常見的特徵,但是給定的設備可能不會以通用的方式紮根。如果檢查根目錄變得普遍,根解決方案可能會開始努力隱藏自己。由於他們可以修改操作系統行爲,因此他們有很多選擇。 – 2012-08-13 13:47:08

回答

9

用於此目的的一些修改版本用於設置system propertyro.modversion。事情似乎已經開始了;我的構建從TheDude幾個月前有這樣的:

[email protected]:~$ adb -d shell getprop |grep build 
[ro.build.id]: [CUPCAKE] 
[ro.build.display.id]: [htc_dream-eng 1.5 CUPCAKE eng.TheDudeAbides.20090427.235325 test-keys] 
[ro.build.version.incremental]: [eng.TheDude.2009027.235325] 
[ro.build.version.sdk]: [3] 
[ro.build.version.release]: [1.5] 
[ro.build.date]: [Mon Apr 20 01:42:32 CDT 2009] 
[ro.build.date.utc]: [1240209752] 
[ro.build.type]: [eng] 
[ro.build.user]: [TheDude] 
[ro.build.host]: [ender] 
[ro.build.tags]: [test-keys] 
[ro.build.product]: [dream] 
[ro.build.description]: [kila-user 1.1 PLAT-RC33 126986 ota-rel-keys,release-keys] 
[ro.build.fingerprint]: [tmobile/kila/dream/trout:1.1/PLAT-RC33/126986:user/ota-rel-keys,release-keys] 
[ro.build.changelist]: [17615# end build properties] 

從1.5 SDK,另一方面,運行1.5圖像的模擬器,也有根,可能是類似於Android Dev Phone 1(你大概要允許),並具有這樣的:

[email protected]:~$ adb -e shell getprop |grep build 
[ro.build.id]: [CUPCAKE] 
[ro.build.display.id]: [sdk-eng 1.5 CUPCAKE 148875 test-keys] 
[ro.build.version.incremental]: [148875] 
[ro.build.version.sdk]: [3] 
[ro.build.version.release]: [1.5] 
[ro.build.date]: [Thu May 14 18:09:10 PDT 2009] 
[ro.build.date.utc]: [1242349750] 
[ro.build.type]: [eng] 
[ro.build.user]: [android-build] 
[ro.build.host]: [undroid16.mtv.corp.google.com] 
[ro.build.tags]: [test-keys] 
[ro.build.product]: [generic] 
[ro.build.description]: [sdk-eng 1.5 CUPCAKE 148875 test-keys] 
[ro.build.fingerprint]: [generic/sdk/generic/:1.5/CUPCAKE/148875:eng/test-keys] 

對於零售版本,我沒有一個出手,但site:xda-developers.com在各種搜索是內容豐富。這裏是G1 in the Netherlands,你可以看到ro.build.tags沒有test-keys,我認爲這可能是最可靠的使用屬性。

+0

看起來很有趣,但是:雖然仿真器(和ADP)確實允許root本身,但它們不允許應用程序使用它,即: $ su app_29 $ su su:uid 10029不允許爲su – miracle2k 2009-07-09 10:08:10

+0

啊,我想他們不會......你可以把它與檢查ro.build.host(而不是)以google.com結尾,然後,如果他們是唯一有測試密鑰但阻止su沒有問用戶。取決於新設備的構建主機,非手機的東西......並不容易。 – 2009-07-09 10:27:37

3

兩個額外的想法,如果你想檢查設備是否能夠從你的應用程序根目錄:

  1. 檢查爲「蘇」二進制現有:運行「裏面蘇」從Runtime.getRuntime().exec()
  2. /system/app/Superuser.apk位置
4

這裏查找SuperUser.apk是基於一些答案,在這裏我的代碼:

/** 
    * Checks if the phone is rooted. 
    * 
    * @return <code>true</code> if the phone is rooted, <code>false</code> 
    * otherwise. 
    */ 
    public static boolean isPhoneRooted() { 

    // get from build info 
    String buildTags = android.os.Build.TAGS; 
    if (buildTags != null && buildTags.contains("test-keys")) { 
     return true; 
    } 

    // check if /system/app/Superuser.apk is present 
    try { 
     File file = new File("/system/app/Superuser.apk"); 
     if (file.exists()) { 
     return true; 
     } 
    } catch (Throwable e1) { 
     // ignore 
    } 

    return false; 
    } 
46

的RootTools庫提供簡單的方法來檢查根:

RootTools.isRootAvailable() 

https://github.com/Stericson/RootTools

+6

isRootAvailable()只是檢查路徑和其他硬編碼目錄中是否存在su。我聽說有些沒有根據的工具會讓你失望,所以這會帶來誤判。 – 2011-11-02 19:24:18

+12

RootTools.isAccessGiven()不僅會檢查root,還會請求root權限;所以一個無根設備總是會用這種方法返回false。 – aggregate1166877 2013-04-17 09:40:25

+2

@ aggregate116​​6877,你是對的,但它不夠好,如果我問我時不需要root權限怎麼辦?我只想知道它是否已經紮根,但目前我不需要root權限。 – neevek 2013-06-27 06:46:12

205

這裏是一個類,將檢查根的三種方式之一。

/** @author Kevin Kowalewski */ 
public class RootUtil { 
    public static boolean isDeviceRooted() { 
     return checkRootMethod1() || checkRootMethod2() || checkRootMethod3(); 
    } 

    private static boolean checkRootMethod1() { 
     String buildTags = android.os.Build.TAGS; 
     return buildTags != null && buildTags.contains("test-keys"); 
    } 

    private static boolean checkRootMethod2() { 
     String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", 
       "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"}; 
     for (String path : paths) { 
      if (new File(path).exists()) return true; 
     } 
     return false; 
    } 

    private static boolean checkRootMethod3() { 
     Process process = null; 
     try { 
      process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" }); 
      BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); 
      if (in.readLine() != null) return true; 
      return false; 
     } catch (Throwable t) { 
      return false; 
     } finally { 
      if (process != null) process.destroy(); 
     } 
    } 
} 
+7

如果兩個問題保證相同的答案,然後他們有99%的時間是重複的,所以標記爲愚蠢,而不是在兩者上發佈相同的答案。謝謝。 – Kev 2011-11-11 23:27:43

+0

它不是100%相同。我提供的答案包含代碼以實際嘗試根源命令,這不會向用戶提示超級用戶請求。檢查測試密鑰不適用於所有根設備(不適用於我的設備)。測試SuperUser.apk也不完美,用戶可以在獲得root權限後卸載它。 – 2011-11-14 19:08:50

+2

這可能是這樣,但我只是讓你知道,確切的重複答案是社區**標記**。你應該定製你的答案並解決OP問題的具體問題。複製和粘貼答案面臨風險,可能會吸引downvotes。 – Kev 2011-11-14 19:30:49

10

而不是使用isRootAvailable(),您可以使用isAccessGiven()。從RootTools wiki直接:

if (RootTools.isAccessGiven()) { 
    // your app has been granted root access 
} 

RootTools.isAccessGiven()不但檢查該設備是植根,它 還要求蘇爲您的應用程序,請求允許,如果 您的應用程序被成功授予返回true根權限。這可以用作 作爲您應用中的第一次檢查,以確保您在需要時可以獲得 訪問權限。

https://code.google.com/p/roottools/

38

在我的應用程序,如果設備是根植或不執行「蘇」命令,我被檢查。但是今天我刪除了這部分代碼。爲什麼?

因爲我的應用程序成爲一個記憶殺手。怎麼樣?讓我告訴你我的故事。

有人抱怨我的應用程序正在減慢設備(當然,我認爲這不可能是真的)。我試圖找出原因。所以我使用MAT來獲得堆轉儲和分析,並且一切看起來都很完美。但在重新啓動我的應用多次後,我意識到該設備真的變慢了,停止我的應用程序並沒有讓它更快(除非我重新啓動設備)。當設備非常慢時,我再次分析轉儲文件。但是對於轉儲文件來說,一切都是完美的 然後,我做了必須做的第一件事。我列出了流程。

$ adb shell ps 

Surprize;我的應用程序有很多流程(我的應用程序的流程標籤位於清單)。其中一些是殭屍,其中一些不是。

通過一個具有單個Activity且僅執行「su」命令的示例應用程序,我意識到每次啓動應用程序時都會創建一個殭屍進程。起初,這些殭屍分配0KB,但比事情發生和殭屍進程持有幾乎相同的知識產權作爲我的應用程序的主要過程,他們成爲標準過程。

對於bugs.sun.com上的同一問題有一個錯誤報告:http://bugs.sun.com/view_bug.do?bug_id=6474073這解釋了是否找不到命令殭屍將使用exec()方法創建。但我仍然不明白他們爲什麼以及如何成爲標準流程並擁有重要的知識庫。 (這不是一直在發生)

如果您想要下面的代碼示例,您可以嘗試;

String commandToExecute = "su"; 
executeShellCommand(commandToExecute); 

簡單的命令執行方法;

private boolean executeShellCommand(String command){ 
    Process process = null;    
    try{ 
     process = Runtime.getRuntime().exec(command); 
     return true; 
    } catch (Exception e) { 
     return false; 
    } finally{ 
     if(process != null){ 
      try{ 
       process.destroy(); 
      }catch (Exception e) { 
      } 
     } 
    } 
} 

綜上所述;我沒有任何建議可以確定設備是否已植根。但如果我是你,我不會使用Runtime.getRuntime()。exec()。

順便說一下; RootTools.isRootAvailable()會導致同樣的問題。

+5

這非常令人擔憂。我有一個根深蒂固的設備檢測類,做了同樣的事情 - 在閱讀本文後,我確認了上面詳述的愛琴海。偶爾的殭屍進程被拋在後面,設備變慢等等...... – AWT 2013-09-03 13:49:14

4

繼@Kevins答案,而用他的系統,我最近發現,Nexus的7.1發還false所有三種方法 - 無which命令,沒有test-keysSuperSU中沒有安裝/system/app

我加了這一點:

public static boolean checkRootMethod4(Context context) { 
    return isPackageInstalled("eu.chainfire.supersu", context);  
} 

private static boolean isPackageInstalled(String packagename, Context context) { 
    PackageManager pm = context.getPackageManager(); 
    try { 
     pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES); 
     return true; 
    } catch (NameNotFoundException e) { 
     return false; 
    } 
} 

這是略在某些情況下非常有用(如果你需要保證的root訪問權限),因爲它是完全有可能在不具有SU設備安裝SuperSU訪問。

但是,由於可能有SuperSU安裝和運行,但而不是/system/app目錄中,這種額外的情況將根(哈哈)出這種情況。

1

確實這是一個有趣的問題,至今沒有人應得獎。我使用下面的代碼:

boolean isRooted() { 
     try { 
       ServerSocket ss = new ServerSocket(81); 
       ss.close(); 
            return true; 
      } catch (Exception e) { 
       // not sure 
      } 
    return false; 
    } 

的代碼肯定不是防彈的,因爲網絡可以是不可用,所以你得到一個異常。如果此方法返回true,那麼可以肯定99%,否則只有50%不是。網絡許可也可能破壞解決方案。

15

http://code.google.com/p/roottools/

如果你不想使用的jar文件只使用代碼:

public static boolean findBinary(String binaryName) { 
     boolean found = false; 
     if (!found) { 
      String[] places = { "/sbin/", "/system/bin/", "/system/xbin/", 
        "/data/local/xbin/", "/data/local/bin/", 
        "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/" }; 
      for (String where : places) { 
       if (new File(where + binaryName).exists()) { 
        found = true; 

        break; 
       } 
      } 
     } 
     return found; 
    } 

計劃將試圖找到蘇文件夾:

private static boolean isRooted() { 
     return findBinary("su"); 
    } 

示例:

if (isRooted() == true){ 
textView.setText("Device Rooted"); 

} 
else{ 
textView.setText("Device Unrooted"); 
} 
-2

使用我的庫在rootbox,這很容易。檢查下面所需的代碼:

//Pass true to <Shell>.start(...) call to run as superuser 
    Shell shell = null; 
    try { 
      shell = Shell.start(true); 
    } catch (IOException exception) { 
      exception.printStackTrace(); 
    } 
    if (shell == null) 
      // We failed to execute su binary 
      return; 
    if (shell.isRoot()) { 
      // Verified running as uid 0 (root), can continue with commands 
      ... 
    } else 
      throw Exception("Unable to gain root access. Make sure you pressed Allow/Grant in superuser prompt."); 
3
public static boolean isRootAvailable(){ 
      Process p = null; 
      try{ 
       p = Runtime.getRuntime().exec(new String[] {"su"}); 
       writeCommandToConsole(p,"exit 0"); 
       int result = p.waitFor(); 
       if(result != 0) 
        throw new Exception("Root check result with exit command " + result); 
       return true; 
      } catch (IOException e) { 
       Log.e(LOG_TAG, "Su executable is not available ", e); 
      } catch (Exception e) { 
       Log.e(LOG_TAG, "Root is unavailable ", e); 
      }finally { 
       if(p != null) 
        p.destroy(); 
      } 
      return false; 
     } 
private static String writeCommandToConsole(Process proc, String command, boolean ignoreError) throws Exception{ 
      byte[] tmpArray = new byte[1024]; 
      proc.getOutputStream().write((command + "\n").getBytes()); 
      proc.getOutputStream().flush(); 
      int bytesRead = 0; 
      if(proc.getErrorStream().available() > 0){ 
       if((bytesRead = proc.getErrorStream().read(tmpArray)) > 1){ 
        Log.e(LOG_TAG,new String(tmpArray,0,bytesRead)); 
        if(!ignoreError) 
         throw new Exception(new String(tmpArray,0,bytesRead)); 
       } 
      } 
      if(proc.getInputStream().available() > 0){ 
       bytesRead = proc.getInputStream().read(tmpArray); 
       Log.i(LOG_TAG, new String(tmpArray,0,bytesRead)); 
      } 
      return new String(tmpArray); 
     } 
0
if [[ "`adb shell which su | grep -io "permission denied"`" != "permission denied" ]]; then 
    echo "Yes. Rooted device." 
else 
    echo "No. Device not rooted. Only limited tasks can be performed. Done." 
    zenity --warning --title="Device Not Rooted" --text="The connected Android Device is <b>NOT ROOTED</b>. Only limited tasks can be performed." --no-wrap 
fi 
17

如果您已經使用光纖/ Crashlytics你叫

CommonUtils.isRooted(this) 

這是當前實現該方法的:

public static boolean isRooted(Context context) { 
    boolean isEmulator = isEmulator(context); 
    String buildTags = Build.TAGS; 
    if(!isEmulator && buildTags != null && buildTags.contains("test-keys")) { 
     return true; 
    } else { 
     File file = new File("/system/app/Superuser.apk"); 
     if(file.exists()) { 
      return true; 
     } else { 
      file = new File("/system/xbin/su"); 
      return !isEmulator && file.exists(); 
     } 
    } 
} 
21

Root che在Java級別的ck不是一個安全的解決方案。如果您的應用有安全問題在Rooted設備上運行,請使用此解決方案。

凱文的答案是有效的,除非手機也有類似RootCloak的應用程序。這種應用程序有一個處理Java API的手機根植,他們嘲笑這些API返回手機沒有紮根。

我已經寫了一個基於凱文答案的本地代碼,它甚至可以與RootCloak一起使用!此外它不會導致任何內存泄漏問題。

#include <string.h> 
#include <jni.h> 
#include <time.h> 
#include <sys/stat.h> 
#include <stdio.h> 
#include "android_log.h" 
#include <errno.h> 
#include <unistd.h> 
#include <sys/system_properties.h> 

JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod1(
     JNIEnv* env, jobject thiz) { 


    //Access function checks whether a particular file can be accessed 
    int result = access("/system/app/Superuser.apk",F_OK); 

    ANDROID_LOGV("File Access Result %d\n", result); 

    int len; 
    char build_tags[PROP_VALUE_MAX]; // PROP_VALUE_MAX from <sys/system_properties.h>. 
    len = __system_property_get(ANDROID_OS_BUILD_TAGS, build_tags); // On return, len will equal (int)strlen(model_id). 
    if(strcmp(build_tags,"test-keys") == 0){ 
     ANDROID_LOGV("Device has test keys\n", build_tags); 
     result = 0; 
    } 
    ANDROID_LOGV("File Access Result %s\n", build_tags); 
    return result; 

} 

JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod2(
     JNIEnv* env, jobject thiz) { 
    //which command is enabled only after Busy box is installed on a rooted device 
    //Outpput of which command is the path to su file. On a non rooted device , we will get a null/ empty path 
    //char* cmd = const_cast<char *>"which su"; 
    FILE* pipe = popen("which su", "r"); 
    if (!pipe) return -1; 
    char buffer[128]; 
    std::string resultCmd = ""; 
    while(!feof(pipe)) { 
     if(fgets(buffer, 128, pipe) != NULL) 
      resultCmd += buffer; 
    } 
    pclose(pipe); 

    const char *cstr = resultCmd.c_str(); 
    int result = -1; 
    if(cstr == NULL || (strlen(cstr) == 0)){ 
     ANDROID_LOGV("Result of Which command is Null"); 
    }else{ 
     result = 0; 
     ANDROID_LOGV("Result of Which command %s\n", cstr); 
     } 
    return result; 

} 

JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod3(
     JNIEnv* env, jobject thiz) { 


    int len; 
    char build_tags[PROP_VALUE_MAX]; // PROP_VALUE_MAX from <sys/system_properties.h>. 
    int result = -1; 
    len = __system_property_get(ANDROID_OS_BUILD_TAGS, build_tags); // On return, len will equal (int)strlen(model_id). 
    if(len >0 && strstr(build_tags,"test-keys") != NULL){ 
     ANDROID_LOGV("Device has test keys\n", build_tags); 
     result = 0; 
    } 

    return result; 

} 

在Java代碼中,你需要創建包裝類RootUtils使本地電話

public boolean checkRooted() { 

     if(rootUtils.checkRootAccessMethod3() == 0 || rootUtils.checkRootAccessMethod1() == 0 || rootUtils.checkRootAccessMethod2() == 0) 
      return true; 
     return false; 
    } 
23

許多這裏列出的答案有其固有的問題:

  • 檢查測試 - 鍵與根訪問相關,但不一定保證它
  • 「PATH」目錄應該從實際的「PATH」環境中派生變量而不是硬編碼
  • 「su」可執行文件的存在並不一定意味着該設備已被植入
  • 「which」可執行文件可能安裝也可能不安裝,您應該讓系統解析它路徑如果可能的話
  • 只是因爲超級用戶的應用程序安裝在設備上,並不意味着該設備具有root訪問權限尚未

從Stericson的RootTools庫似乎更合理檢查根。它還有很多額外的工具和實用程序,所以我強烈推薦它。但是,沒有解釋如何專門檢查root,它可能比大多數應用程序真正需要的要重一點。

我已經制作了一些鬆散地基於RootTools庫的實用程序方法。如果你只是想檢查,如果「蘇」的可執行文件,你可以使用下面的方法在設備上:

public static boolean isRootAvailable(){ 
    for(String pathDir : System.getenv("PATH").split(":")){ 
     if(new File(pathDir, "su").exists()) { 
      return true; 
     } 
    } 
    return false; 
} 

此方法僅通過,如果在「路徑」環境變量中列出的目錄,並檢查環路一「 su「文件存在於其中之一。

爲了真正檢查根訪問權限,實際上必須運行「su」命令。如果安裝了像SuperUser這樣的應用程序,那麼此時它可能會要求提供根訪問權限,或者如果已經授予/拒絕了某個Toast,可能會顯示是否允許/拒絕訪問。一個好的命令是「id」,這樣你就可以驗證用戶ID實際上是0(root)。

這裏有一個樣品的方法來確定的root訪問權限是否已授予:

public static boolean isRootGiven(){ 
    if (isRootAvailable()) { 
     Process process = null; 
     try { 
      process = Runtime.getRuntime().exec(new String[]{"su", "-c", "id"}); 
      BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); 
      String output = in.readLine(); 
      if (output != null && output.toLowerCase().contains("uid=0")) 
       return true; 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } finally { 
      if (process != null) 
       process.destroy(); 
     } 
    } 

    return false; 
} 

據實際測試運行「蘇」命令,因爲有些仿真器有「蘇」可執行預裝是重要的,但只允許某些用戶像adb外殼一樣訪問它。

在嘗試運行它之前檢查「su」可執行文件的存在也很重要,因爲android已知不能正確處理嘗試運行缺少命令的進程。這些幻影進程可能會隨着時間的推移消耗內存。

5

RootBeer是由Scott和Matthew檢查Android庫的根。 它使用各種檢查來指示設備是否生根。

的Java檢查

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

本機檢查

我們打電話給我們的本機根檢查器來運行它自己的一些 檢查。原生檢查通常比較難隱形,因此某些根節點應用只會阻止加載包含 某些關鍵字的本機庫。

  • checkForSuBinary
1

用C++與NDK是即使用戶正在使用該隱藏他根如RootCloak應用以檢測​​根的最佳方法。我使用RootCloak測試了這些代碼,即使用戶試圖隱藏它,我也能夠檢測到根。 所以,你的CPP文件想:

#include <jni.h> 
#include <string> 


/** 
* 
* function that checks for the su binary files and operates even if 
* root cloak is installed 
* @return integer 1: device is rooted, 0: device is not 
*rooted 
*/ 
extern "C" 
JNIEXPORT int JNICALL 


Java_com_example_user_root_1native_rootFunction(JNIEnv *env,jobject thiz){ 
const char *paths[] ={"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", 
         "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", 
         "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"}; 

int counter =0; 
while (counter<9){ 
    if(FILE *file = fopen(paths[counter],"r")){ 
     fclose(file); 
     return 1; 
    } 
    counter++; 
} 
return 0; 
} 

你要給從Java代碼的功能如下

public class Root_detect { 



    /** 
    * 
    * function that calls a native function to check if the device is 
    *rooted or not 
    * @return boolean: true if the device is rooted, false if the 
    *device is not rooted 
    */ 
    public boolean check_rooted(){ 

     int checker = rootFunction(); 

     if(checker==1){ 
      return true; 
     }else { 
      return false; 
     } 
    } 
    static { 
    System.loadLibrary("cpp-root-lib");//name of your cpp file 
    } 

    public native int rootFunction(); 
} 
13

更新2017年

你可以用Google Safetynet API現在就這樣做。 SafetyNet API提供了Attestation API,可幫助您評估應用程序運行的Android環境的安全性和兼容性。

該證明可以幫助確定特定設備是否被篡改或以其他方式修改。

認證API返回這樣

{ 
    "nonce": "R2Rra24fVm5xa2Mg", 
    "timestampMs": 9860437986543, 
    "apkPackageName": "com.package.name.of.requesting.app", 
    "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the 
            certificate used to sign requesting app"], 
    "apkDigestSha256": "base64 encoded, SHA-256 hash of the app's APK", 
    "ctsProfileMatch": true, 
    "basicIntegrity": true, 
} 

一個JWS響應解析這種反應可以幫助您確定設備是植根與否

解鎖裝置似乎引起ctsProfileMatch =假。

你可以在客戶端做,但建議在服務器端解析響應。 與安全網API一個基本的客戶端服務器杜彥武看起來就像這樣: -

enter image description here

相關問題