Android检查手机是否Root以及应用是否获取Root权限
在Android中如何判断手机是否Root以及应用是否获取了Root权限,下面我们将对开源项目RootTools的源码进行分析。
RootTools的源码地址:https://github.com/Stericson/RootTools
一、RootTools.isRootAvailable()判断手机是否已经Root
下面RootTools这个类中,RootTools.isRootAvailable()可以判断手机是否已经Root,下面我们来看看它的源码。
public static boolean isRootAvailable(){ return RootShell.isRootAvailable();}
它实质调用的是RootShell.isRootAvailable()函数,我们接着来看看这个函数的源码。
public static boolean isRootAvailable() { return (findBinary("su")).size() > 0;}
从上面的代码中我们可以看到它实际是在查找su这个执行文件是否存在,我们知道,我们切换到root用户下,使用的就是su命令,如果这个文件存在,就基本可以知道手机已经Root,因为我们可以运行su命令切换到root用户下。
下面我们来看看RootShell.findBinary(“su”)这个函数。
public static List<String> findBinary(final String binaryName) { return findBinary(binaryName, null);}public static List<String> findBinary(final String binaryName, List<String> searchPaths) { final List<String> foundPaths = new ArrayList<String>(); boolean found = false; // 1、如果搜索路径为空,得到环境变量PATH路径 if(searchPaths == null) { searchPaths = RootShell.getPath(); } RootShell.log("Checking for " + binaryName); // 2、使用了两种方法来检查 // 2.1、遍历所有路径,尝试使用stat命令来查看su文件信息 //Try to use stat first try { for (String path : searchPaths) { if(!path.endsWith("/")) { path += "/"; } final String currentPath = path; Command cc = new Command(0, false, "stat " + path + binaryName) { // 对执行stat命令后的输出信息进行判断 // 如果包含了"File: "和"su"就表示这个路径存在su命令 @Override public void commandOutput(int id, String line) { if (line.contains("File: ") && line.contains(binaryName)) { foundPaths.add(currentPath); RootShell.log(binaryName + " was found here: " + currentPath); } RootShell.log(line); super.commandOutput(id, line); } }; RootShell.getShell(false).add(cc); commandWait(RootShell.getShell(false), cc); } found = !foundPaths.isEmpty(); } catch (Exception e) { RootShell.log(binaryName + " was not found, more information MAY be available with Debugging on."); } // 2.2、如果第一种方法没有找到,就使用下面这种方法 // 遍历所有路径,使用ls命令查看su文件是否存在 if (!found) { RootShell.log("Trying second method"); for (String path : searchPaths) { if(!path.endsWith("/")) { path += "/"; } if (RootShell.exists(path + binaryName)) { RootShell.log(binaryName + " was found here: " + path); foundPaths.add(path); } else { RootShell.log(binaryName + " was NOT found here: " + path); } } } Collections.reverse(foundPaths); return foundPaths;}
1、如果搜索路径为空,得到环境变量PATH路径
这一步的关键就是RootShell.getPath()这个函数。我们来重点看看这个函数。
public static List<String> getPath() { return Arrays.asList(System.getenv("PATH").split(":"));}
我们可以很容易的看出,它首先获取环境变量PATH的值,然后得到PATH里面配置的所有路径。
2、针对上面得到的路径,这些路径可能就是su文件所在的路径,进行遍历,主要提供了两种方法。
2.1、遍历所有路径,尝试使用stat命令来查看su文件信息
(1)使用for循环依次得到每个路径,然后将su文件拼接在后面。例如我们得到一个/system/bin/路径,然后将su拼接在后面就是/system/bin/su。
(2)创建一个”stat /system/bin/su”的Command命令。并且重写了Command的commandOutput方法,这个方法可以得到执行”stat /system/bin/su”的输出信息,然后对输出信息进行比较判断,如果输出信息包含了”File: “和”su”,那么就说明这个路径下面包含了su命令,就将它添加到foundPaths列表中,最终如果foundPaths为空,表示没有找到,如果不为空就表示找到了,如果为空,就使用第二种方法接着查找。
2.2、遍历所有路径,使用ls命令查看su文件是否存在
它的核心就是RootShell.exists(path + binaryName)这个检查函数。
public static boolean exists(final String file) { return exists(file, false);}public static boolean exists(final String file, boolean isDir) { final List<String> result = new ArrayList<String>(); String cmdToExecute = "ls " + (isDir ? "-d " : " "); Command command = new Command(0, false, cmdToExecute + file) { @Override public void commandOutput(int id, String line) { RootShell.log(line); result.add(line); super.commandOutput(id, line); } }; try { //Try without root... RootShell.getShell(false).add(command); commandWait(RootShell.getShell(false), command); } catch (Exception e) { return false; } for (String line : result) { if (line.trim().equals(file)) { return true; } } result.clear(); try { RootShell.getShell(true).add(command); commandWait(RootShell.getShell(true), command); } catch (Exception e) { return false; } //Avoid concurrent modification... List<String> final_result = new ArrayList<String>(); final_result.addAll(result); for (String line : final_result) { if (line.trim().equals(file)) { return true; } } return false;}
可以看到,这个逻辑跟上面2.1的基本一样,就是执行的Command命令不一样,2.1执行的是stat命令,这里执行的是ls命令。
既然这两种方法的思想是一样的,那么下面,我们来看看这个Command命令是如何执行的。
1、创建Command命令
Command cc = new Command(0, false, "stat " + path + binaryName) { @Override public void commandOutput(int id, String line) { if (line.contains("File: ") && line.contains(binaryName)) { foundPaths.add(currentPath); RootShell.log(binaryName + " was found here: " + currentPath); } RootShell.log(line); super.commandOutput(id, line); }};
2、执行RootShell.getShell(false)
public static Shell getShell(boolean root) throws IOException, TimeoutException, RootDeniedException { return RootShell.getShell(root, 0);}public static Shell getShell(boolean root, int timeout) throws IOException, TimeoutException, RootDeniedException { return getShell(root, timeout, Shell.defaultContext, 3);}public static Shell getShell(boolean root, int timeout, Shell.ShellContext shellContext, int retry) throws IOException, TimeoutException, RootDeniedException { if (root) { return Shell.startRootShell(timeout, shellContext, retry); } else { return Shell.startShell(timeout); }}
我们可以看到,最终执行的是Shell.startShell(timeout)函数。
public static Shell startShell(int timeout) throws IOException, TimeoutException { try { if (Shell.shell == null) { RootShell.log("Starting Shell!"); Shell.shell = new Shell("/system/bin/sh", ShellType.NORMAL, ShellContext.NORMAL, timeout); } else { RootShell.log("Using Existing Shell!"); } return Shell.shell; } catch (RootDeniedException e) { //Root Denied should never be thrown. throw new IOException(); }}
如果shell为空,就创建一个shell,我们来看看这个创建过程new Shell(“/system/bin/sh”, ShellType.NORMAL, ShellContext.NORMAL, timeout)。
private Shell(String cmd, ShellType shellType, ShellContext shellContext, int shellTimeout) throws IOException, TimeoutException, RootDeniedException { RootShell.log("Starting shell: " + cmd); RootShell.log("Context: " + shellContext.getValue()); RootShell.log("Timeout: " + shellTimeout); this.shellType = shellType; this.shellTimeout = shellTimeout > 0 ? shellTimeout : this.shellTimeout; this.shellContext = shellContext; // 1、这里会执行cmd命令,就是上面传过来的"/system/bin/sh" if (this.shellContext == ShellContext.NORMAL) { this.proc = Runtime.getRuntime().exec(cmd); } else { String display = getSuVersion(false); String internal = getSuVersion(true); //only done for root shell... //Right now only SUPERSU supports the --context switch if (isSELinuxEnforcing() && (display != null) && (internal != null) && (display.endsWith("SUPERSU")) && (Integer.valueOf(internal) >= 190)) { cmd += " --context " + this.shellContext.getValue(); } else { RootShell.log("Su binary --context switch not supported!"); RootShell.log("Su binary display version: " + display); RootShell.log("Su binary internal version: " + internal); RootShell.log("SELinuxEnforcing: " + isSELinuxEnforcing()); } this.proc = Runtime.getRuntime().exec(cmd); } // 2、得到执行cmd命令之后的输入流、输出流和错误流 this.inputStream = new BufferedReader(new InputStreamReader(this.proc.getInputStream(), "UTF-8")); this.errorStream = new BufferedReader(new InputStreamReader(this.proc.getErrorStream(), "UTF-8")); this.outputStream = new OutputStreamWriter(this.proc.getOutputStream(), "UTF-8"); //3、启动一个Worker线程去不断的读取输入流 Worker worker = new Worker(this); worker.start(); try { worker.join(this.shellTimeout); if (worker.exit == -911) { try { this.proc.destroy(); } catch (Exception e) { } closeQuietly(this.inputStream); closeQuietly(this.errorStream); closeQuietly(this.outputStream); throw new TimeoutException(this.error); } /** * Root access denied? */ else if (worker.exit == -42) { try { this.proc.destroy(); } catch (Exception e) { } closeQuietly(this.inputStream); closeQuietly(this.errorStream); closeQuietly(this.outputStream); throw new RootDeniedException("Root Access Denied"); } /** * Normal exit */ else { // 4、启动一个线程,向输入流中输入命令 Thread si = new Thread(this.input, "Shell Input"); si.setPriority(Thread.NORM_PRIORITY); si.start(); // 5、启动一个线程,得到执行命令之后输出流的信息 Thread so = new Thread(this.output, "Shell Output"); so.setPriority(Thread.NORM_PRIORITY); so.start(); } } catch (InterruptedException ex) { worker.interrupt(); Thread.currentThread().interrupt(); throw new TimeoutException(); }}
1、执行”/system/bin/sh”命令
this.proc = Runtime.getRuntime().exec(cmd);
2、得到执行命令之后的输入流、输出流、错误流
this.inputStream = new BufferedReader(new InputStreamReader(this.proc.getInputStream(), "UTF-8"));this.errorStream = new BufferedReader(new InputStreamReader(this.proc.getErrorStream(), "UTF-8"));this.outputStream = new OutputStreamWriter(this.proc.getOutputStream(), "UTF-8");
3、启动一个Worker线程去不断的读取输入流
protected static class Worker extends Thread { public int exit = -911; public Shell shell; private Worker(Shell shell) { this.shell = shell; } public void run() { try { shell.outputStream.write("echo Started\n"); shell.outputStream.flush(); // 这里有一个死循环,不断的读取输入流 while (true) { String line = shell.inputStream.readLine(); if (line == null) { throw new EOFException(); } else if ("".equals(line)) { continue; } else if ("Started".equals(line)) { this.exit = 1; setShellOom(); break; } shell.error = "unknown error occurred."; } } catch (IOException e) { exit = -42; if (e.getMessage() != null) { shell.error = e.getMessage(); } else { shell.error = "RootAccess denied?."; } } }}
4、启动一个线程,向输入流中输入命令
我们来看看input这个传入的Runnable参数
private Runnable input = new Runnable() { public void run() { try { while (true) { synchronized (commands) { while (!close && write >= commands.size()) { isExecuting = false; commands.wait(); } } if (write >= maxCommands) { while (read != write) { RootShell.log("Waiting for read and write to catch up before cleanup."); } /** * Clean up the commands, stay neat. */ cleanCommands(); } // 向outputStream中写入一条命令,也就是sh执行了一条命令 if (write < commands.size()) { isExecuting = true; Command cmd = commands.get(write); cmd.startExecution(); RootShell.log("Executing: " + cmd.getCommand() + " with context: " + shellContext); outputStream.write(cmd.getCommand()); String line = "\necho " + token + " " + totalExecuted + " $?\n"; outputStream.write(line); outputStream.flush(); write++; totalExecuted++; } else if (close) { isExecuting = false; outputStream.write("\nexit 0\n"); outputStream.flush(); RootShell.log("Closing shell"); return; } } } catch (IOException e) { RootShell.log(e.getMessage(), RootShell.LogLevel.ERROR, e); } catch (InterruptedException e) { RootShell.log(e.getMessage(), RootShell.LogLevel.ERROR, e); } finally { write = 0; closeQuietly(outputStream); } }};
5、启动一个线程,得到执行命令之后输出流的信息
我们来看看output这个传入的Runnable参数
private Runnable output = new Runnable() { public void run() { try { Command command = null; while (!close || inputStream.ready() || read < commands.size()) { isReading = false; // 从输入流中读出一行 String outputLine = inputStream.readLine(); isReading = true; if (outputLine == null) { break; } if (command == null) { if (read >= commands.size()) { if (close) { break; } continue; } command = commands.get(read); } int pos = -1; pos = outputLine.indexOf(token); // 最终调用我们Command重新的那个函数,用来处理输出信息 if (pos == -1) { command.output(command.id, outputLine); } else if (pos > 0) { command.output(command.id, outputLine.substring(0, pos)); } if (pos >= 0) { outputLine = outputLine.substring(pos); String fields[] = outputLine.split(" "); if (fields.length >= 2 && fields[1] != null) { int id = 0; try { id = Integer.parseInt(fields[1]); } catch (NumberFormatException e) { } int exitCode = -1; try { exitCode = Integer.parseInt(fields[2]); } catch (NumberFormatException e) { } if (id == totalRead) { processErrors(command); int iterations = 0; while (command.totalOutput > command.totalOutputProcessed) { if(iterations == 0) { iterations++; RootShell.log("Waiting for output to be processed. " + command.totalOutputProcessed + " Of " + command.totalOutput); } try { synchronized (this) { this.wait(2000); } } catch (Exception e) { RootShell.log(e.getMessage()); } } RootShell.log("Read all output"); command.setExitCode(exitCode); command.commandFinished(); command = null; read++; totalRead++; continue; } } } } try { proc.waitFor(); proc.destroy(); } catch (Exception e) { } while (read < commands.size()) { if (command == null) { command = commands.get(read); } if(command.totalOutput < command.totalOutputProcessed) { command.terminated("All output not processed!"); command.terminated("Did you forget the super.commandOutput call or are you waiting on the command object?"); } else { command.terminated("Unexpected Termination."); } command = null; read++; } read = 0; } catch (IOException e) { RootShell.log(e.getMessage(), RootShell.LogLevel.ERROR, e); } finally { closeQuietly(outputStream); closeQuietly(errorStream); closeQuietly(inputStream); RootShell.log("Shell destroyed"); isClosed = true; isReading = false; } }};
二、RootTools.isAccessGiven()判断app是否被授予root权限
public static boolean isAccessGiven(){ return RootShell.isAccessGiven();}
可以看到它实际调用了RootShell.isAccessGiven()方法,所以我们来看看它的源码。
public static boolean isAccessGiven() { final Set<String> ID = new HashSet<String>(); final int IAG = 158; try { RootShell.log("Checking for Root access"); Command command = new Command(IAG, false, "id") { @Override public void commandOutput(int id, String line) { if (id == IAG) { ID.addAll(Arrays.asList(line.split(" "))); } super.commandOutput(id, line); } }; Shell.startRootShell().add(command); commandWait(Shell.startRootShell(), command); //parse the userid for (String userid : ID) { RootShell.log(userid); if (userid.toLowerCase().contains("uid=0")) { RootShell.log("Access Given"); return true; } } return false; } catch (Exception e) { e.printStackTrace(); return false; }}
可以看到这里的思想跟上面2.1和2.2的也基本相同,都是执行Command命令,这里执行的是id这个命令。它的原理就是如果使用id得到所有的id信息,然后对这些id信息进行判断,如果里面包含”uid=0”就表示应用获取到了Root权限。
参考文章:
检查Android是否已经获取root权限
Android中判断手机是否已经Root
http://bbs.csdn.net/topics/390885158
更多相关文章
- Linux中source命令,在Android build 中的应用
- android 源码下java文件的路径
- Android中不同包路径下Activity跳转的实现(解决ActivityNotFoundE
- Android 抽象回调函数以及接口回调更新UI
- eclipse android或者Java应用查看jdk路径和版本与android studio
- Android 依赖注入函数库Roboguice(一)
- android linux 命令