300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > Android检查手机是否Root以及应用是否获取Root权限

Android检查手机是否Root以及应用是否获取Root权限

时间:2023-12-09 06:39:19

相关推荐

Android检查手机是否Root以及应用是否获取Root权限

在Android中如何判断手机是否Root以及应用是否获取了Root权限,下面我们将对开源项目RootTools的源码进行分析。

RootTools的源码地址:/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 firsttry {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命令@Overridepublic 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);mandOutput(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) {@Overridepublic void commandOutput(int id, String line) {RootShell.log(line);result.add(line);mandOutput(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) {@Overridepublic 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);mandOutput(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 switchif (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);mandFinished();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 mandOutput 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") {@Overridepublic void commandOutput(int id, String line) {if (id == IAG) {ID.addAll(Arrays.asList(line.split(" ")));}mandOutput(id, line);}};Shell.startRootShell().add(command);commandWait(Shell.startRootShell(), command);//parse the useridfor (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

/topics/390885158

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。