必威体育Betway必威体育官网
当前位置:首页 > IT技术

superuser root 原理详细分析

时间:2019-09-27 09:14:18来源:IT技术作者:seo实验室小编阅读:61次「手机版」
 

superuser

Root 方法:

通过fastboot 刷入指定的recovery.img, 替换了系统原生的recovery, 进入recovery,刷入root相关文件,以达到root目的。

目前市面常见的 root 管理工具为:

supersu、superuser、kingroot 等,因为 supersu、 kingroot 不开源, 逆向分析其底层实现 较为困难,所以本次是以 开源项目 superuser 进行分析的。

Root 原理:

root 的主要原理,是将apk层传入的本应放在shell进程中执行的命令,放到daemonsu 创建 进程sush中执行。 其中Daemonsu 为开机时启动的su 守护进程(user为root)。

这个过程最重要的为:apk、su、daemonsu、sush、superuser 之间的通信。

通信过程大概为:

1、三方进程调用su,su 通过socket 与 daemonsu 通信,

2、daemonsu 创建sush,

3、sush 通过 am 启动 superuser apk ,让用户选择是否授予其root权限。

4、superuser 通过socket 告知 sush 用户选择的结果

5、sush 根据 apk 传过来的结果,选择继续执行或中断执行

大概为

apk <———-> su ———-> daemonsu———>sush <———->superuser

apk<———->su: 三方进程通过runtime 与process 与 su进程通信

su —————–> daemonsu: su 进程与 daemonsu 通过socket 进行通信,daemonsu 进程会创建sush,进行下一步操作。

sush <——–>superuser : sush 进程通过am 启动 superuser apk,让用户选择是否允许授予root权限,之后superuser 将 用户选择的结果,通过 socket 告知 sush,如果apk进程被允许的话,sush 执行 apk的命令,否则中断执行。

详细代码的流程为:

daemonsu 的启动:

daemonsu 用于创建三方apk 执行命令的root进程。通过刷机添加或修改系统的init.rc 文件以达开机启动的目的,superuser 是刷入了一个init.superuser.rc 文件,在开机时执行:

service su_daemon /system/xbin/su --daemon

命令来自启,此命令会走到su的main 方法中:

int main(int argc, char *argv[]) {                           
    return su_main(argc, argv, 1);
}
int su_main(int argc, char *argv[], int need_client) {
    if (argc == 2 && strcmp(argv[1], "--daemon") == 0) {
        return run_daemon();
    }
………….

然后会调用到daemon.c中:

 int run_daemon() {
    int fd;
    struct sockaddr_un sun;

    fd = socket(AF_local, SOCK_STREAM, 0);

    if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) {
        PLOGE("daemon bind");
        goto err;
    }

    if (listen(fd, 10) < 0) {
        PLOGE("daemon listen");
        goto err;
    }     while ((client = accept(fd, NULL, NULL)) > 0) {
     if (fork_zero_fucks() == 0) {
         close(fd);
         return daemon_accept(client);
     }
。。。。。。。。。。

此过程是创建一个socket,等待和申请root 的进程进行通信,

apk <———-> su 通信的流程为:

这个流程较为简单,通过Runtime与Process 即可:

 Process process = Runtime.getRuntime().exec("su");
 BufferedReader bufferedReader = new BufferedReader(new inputstreamreader(process.getInputStream()));
 Dataoutputstream outPutStream = new DataOutputStream(process.getOutputStream());

之后通过 bufferedReader 获取底层的执行结果, 通过 outPutStream将命令传入底层执行。

su —————-> daemonsu

当apk进程调用su 来获取root时,会走到su的main方法中,对应的代码为:

int main(int argc, char *argv[]) { 
    return su_main(argc, argv, 1);
}
int su_main(int argc, char *argv[], int need_client) {
。。。。。。。
    if (need_client) {
        if ((geteuid() != AID_ROOT && getuid() != AID_ROOT) ||
            (get_api_version() >= 18 && getuid() == AID_SHELL) ||
            get_api_version() >= 19) {
            return connect_daemon(argc, argv, ppid);
        }
    }
。。。。。。。

main调用 su_main,并传入 1, 那么 肯定会走到 if分支,然后调用 connect_daemon去连接daemonsu进程,并将 命令 与ppid 传入。

int connect_daemon(int argc, char *argv[], int ppid) {

    struct sockaddr_un sun;
    int socketfd = socket(AF_LOCAL, SOCK_STREAM, 0);
    memset(&sun, 0, sizeof(sun));
    sun.sun_family = AF_LOCAL;
    sprintf(sun.sun_path, "%s/server", requestOR_DAEMON_PATH);

    memset(sun.sun_path, 0, sizeof(sun.sun_path));
    memcpy(sun.sun_path, "\0" "SUPERUSER", strlen("SUPERUSER") + 1);

    if (0 != connect(socketfd, (struct sockaddr*)&sun, sizeof(sun))) 
       PLOGE("connect");
       exit(-1);
    }
    write_int(socketfd, getpid());
    write_string(socketfd, pts_slave);
    write_int(socketfd, uid);
    write_int(socketfd, ppid);
。。。。。。
    int code = read_int(socketfd);
    close(socketfd);
    LOGD("client exited %d", code);

    return code;                      
}

在此方法中,主要是穿件创建socket并连接到daemonsu,将当前的数据写入,等待对端完成。su与daemonsu 通信所使用的 sockaddr 的sun.sun_path 是相同的。

daemonsu———>sush

su通过socket 与daemonsu 通信,daemonsu在接收到连接后,会fork出一个sush子进程进行后续操作,父进程在子进程完成后,向socket 写入数据,通知一下 对端(su),然后等待下一个连接的到来:

int run_daemon() {
。。。。。。。。。     while ((client = accept(fd, NULL, NULL)) > 0) {
     if (fork_zero_fucks() == 0) {
         close(fd);
         return daemon_accept(client);
     }
。。。。。。。。。。 

在daemonsu接受到root申请调用 daemon_accept,在此方法中,会fork出 sush,

static int daemon_accept(int fd) {

    is_daemon = 1;
    int pid = read_int(fd);
    char *pts_slave = read_string(fd);
    daemon_from_uid = read_int(fd);
    daemon_from_pid = read_int(fd);

    int child = fork();

    if (child != 0) {
        if (write(fd, &code, sizeof(int)) != sizeof(int)) {
            PLOGE("unable to write exit code");
        }
        。。。。。。
        return code;
    }
 return run_daemon_child(infd, outfd, errfd, argc, argv);

到此将来用于执行命令的进程sush 就创建完毕了。接下来是 sush 与上层apk superuser的通信,来让用户确认是否授予 申请者 root权限。

sush <———->superuser

sush 是一个底层的进程,与superuser 没有直接的关系,要与上层的superuser apk通信这里是使用的是am 命令,并在调用am命令时,将sush的sock传入,以便达到sush 与superuser的双向通信。ps: supersu 也是使用am 命令,

之前fork出子进程sush,并调用 了run_daemon_child

static int run_daemon_child(int infd, int outfd, int errfd, int argc, char** argv) {
  。。。。。
    return su_main(argc, argv, 0);                                                           
}

注意此处又走到su 里面的su_main方法,并且传入的值为0,

因为本次need_client 传入的为0,那么 将跳过if( need_client)分支,直接往下走,往下会调用到 database_check来检查 superuser 数据库中是否允许 apk进程申请root,会出现桑格返回值:

INTERACTIVE:交互式,需要用户确认是否授予权限,返回此值,会继续执行

ALLOW:允许apk进程申请root,返回此值,会调用 allow, 执行apk的命令

DENY: 不允许apk进程申请root,返回此值,会调用 deny,收尾一下,然后结束。

int su_main(int argc, char *argv[], int need_client) {
   。。。。
    if (need_client) {
    。。。。
    }
    。。。。
    dballow = database_check(&ctx);
    switch (dballow) {
        case INTERACTIVE:
            break;
        case ALLOW:
            allow(&ctx);    
        case DENY:
        default:
            deny(&ctx);  
    }

本次要说的是 INTERACTIVE 型, 如果是此类型,会创建一个socket ,并调用send_request方法使用am命令去建立与上层superuser的 socket联系,之后上层通过socket 获取到 ,请求 root进程的相关信息,然后弹窗等待上层 用户的选择,然后通过 socket获取,结果,根据结果去选择 allow 或deny

     socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path));                                                                     
     if (send_request(&ctx) < 0) {
        deny(&ctx);
    }
    //等待上层建立socket 连接
    fd = socket_accept(socket_serv_fd);
    if (fd < 0) {
        deny(&ctx);
    }
    //将请求root进程的进程信息,通过socket 告诉superuser.apk
    if (socket_send_request(fd, &ctx)) {
        deny(&ctx);
    }
   //获取superuser.apk  中用户的选择结果
    if (socket_receive_result(fd, buf, sizeof(buf))) {
        deny(&ctx);
    }
    //根据结果 选择是否执行
    if (!strcmp(result, "DENY")) {
        deny(&ctx);
    } else if (!strcmp(result, "ALLOW")) {                                 
        allow(&ctx);
    } else {
        deny(&ctx);
    }

send_request 方法中使用am 与上层建立socket 联系的代码为:

int send_request(struct su_context *ctx) {
    char *request_command[] = {
        AM_PATH,
        ACTION_REQUEST,
        "--es",
        "socket",
        ctx->sock_path,
        user[0] ? "--user" : NULL,
        user,
        NULL
    };
    return silent_run(request_command);
}                                                                               
在此方法中拼凑出要执行的命令  request_command,在此处可以看到 通过 am的—es 参数,将soket 传入,上层接受时,会获取此soket,以此达到了,底层与上层的soket 通信,其中 AM_PATH与 ACTION_REQUEST的值为:
#define AM_PATH "/system/bin/APP_process", "/system/bin", "com.Android.commands.am.Am"
#define ACTION_REQUEST "start", "-n", REQUESTOR "/" REQUESTOR_PREFIX ".RequestActivity"

slient_run 方法中比较简单,fork 出一个子进程,执行am命令,父进程返回0。

到此就会走到上层apk superuser 的 RequestActivity.java类中, 他做的操作也比较简单,就是通过intent 获取传过来的socket,使用socket 获取 底层传过来的 请求root 进程的信息,弹窗让用户选择,然后将用户的选择结果,写入数据库,并通过socket 回传给sush。

socket_send_request:底层sush 将请求root 进程的相关信息通过socket 传输给superuser.apk

socket_receive_result: 获取用户选择的结果

allow: 执行用户指定的命令,并收尾

deny: 不执行,直接收尾。

Ps:

用户申请到root 权限后,仅仅代表 他可以切换到root 去执行一些 操作,

如果不去切换,依然没有权限执行例如,在同样申请到root的情况下:

先通过su,切换到root,然后执行后续dumpsys power命令,执行成功,可以读取到有效信息

Process process = Runtime.getRuntime().exec("su");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
DataOutputStream outPutStream = new DataOutputStream(process.getOutputStream());
outPutStream.writeBytes("dumpsys power\n");
outPutStream.flush();
String line = bufferedReader.readLine();

即使在apk,已经被授权root 的情况下,直接执行 dumpsys power 命令,依然是执行失败的 (因为没有切换到root,相当于没有授权的情况去执行),

读取到的信息为: permission Denial: can’t dump PowerManager from from pid****

Process process = Runtime.getRuntime().exec("dumpsys power");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = bufferedReader.readLine();

遗留疑问: 请求root 的apk进程,后续 通过 process 怎么与 sush 进行通信 执行之后命令的,还需要继续查一下。

process.java 与sush 通信的 的原理参考:

堪称经典 的pipe fork exec 的理解和综合使用

http://blog.chinaunix.net/uid-20395453-id-3264826.html

superuser 的源码为:https://github.com/koush/Superuser

相关阅读

Access denied for user 'root'@'localhost'问题

问题场景在阿里云上部署了一台服务器,CentOS6.8系统环境,安装了MySql+Nginx+Git+vsftpd等软件,在本地Linux终端以非root账户SSH远程

ROOT大师怎么用?

ROOT大师是刷机大师团队潜心研究开发的用于pc端一键获取安卓手机root权限的神器,可完美支持三星、HTC、索尼、华为、中兴、联想、

修改mysql root密码

环境:基于mysql 5.6.41(下载免安装版的zip文件) 下载地址:https://dev.mysql.com/downloads/mysql/5.6.html#downloads 将下载好的z

教你轻松解决海信电视开机广告问题,无需root就能成功

目前很多智能电视都带有很严重的开机广告,这让很多用户非常的苦恼。很多商家更明确表示,开机广告是无法去除的。经过网友的一番测试

小米root权限获取很容易!如何获取root权限

小米作为国产手机的代表,拥有安卓操作系统,是一个很有代表性的安卓手机,选择root权限管理手机,强力一键root,不仅功能实用,而且操作简单

分享到:

栏目导航

推荐阅读

热门阅读