在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

更多相关文章

  1. Linux中source命令,在Android build 中的应用
  2. android 源码下java文件的路径
  3. Android中不同包路径下Activity跳转的实现(解决ActivityNotFoundE
  4. Android 抽象回调函数以及接口回调更新UI
  5. eclipse android或者Java应用查看jdk路径和版本与android studio
  6. Android 依赖注入函数库Roboguice(一)
  7. android linux 命令

随机推荐

  1. 读取seekbar的值
  2. Errors during Android(安卓)Kernel buil
  3. Android(安卓)vibrate+ 振动
  4. 圆角ImageView的几种实现方法
  5. Android(安卓)RecyclerView设置分割线
  6. android app develop utils
  7. Android(安卓)调用系统分享,分享到Faceboo
  8. android Junit demo
  9. Android:使用webview上传文件(支持相册和
  10. Android适配器总结