android root原理以及超级superuser的管理

首先来说一下su的原理,su是用来在Linux中切换用户的一个bin文件, 其通过

if(setgid(gid) || setuid(uid)) {

        fprintf(stderr,"su: permission denied\n");

        return 1;

}

来设置用户的 uid 和 gid 这样做还不能切换到该用户上 还必须用过

execlp("/system/bin/sh", "sh", NULL);

执行/system/bin/sh 后就能切换到root 用户上。


而我们的superuser 是将一个经过特殊处理化的su 和我们 superuser的应用结合起来使用的。第三放应用需要使用到root 权限时都会调用到su 命令,这时会调用到su中的main函数,首先通过

static int from_init(struct su_initiator *from)
{
    char path[PATH_MAX], exe[PATH_MAX];
    char args[4096], *argv0, *argv_rest;
    int fd;
    ssize_t len;
    int i;
    int err;

    from->uid = getuid();
    from->pid = getppid();

    /* Get the command line */
    snprintf(path, sizeof(path), "/proc/%u/cmdline", from->pid);
    fd = open(path, O_RDONLY);
    if (fd < 0) {
        PLOGE("Opening command line");
        return -1;
    }
    len = read(fd, args, sizeof(args));
    err = errno;
    close(fd);
    if (len < 0 || len == sizeof(args)) {
        PLOGEV("Reading command line", err);
        return -1;
    }

    argv0 = args;
    argv_rest = NULL;
    for (i = 0; i < len; i++) {
        if (args[i] == '\0') {
            if (!argv_rest) {
                argv_rest = &args[i+1];
            } else {
                args[i] = ' ';
            }
        }
    }
    args[len] = '\0';

    if (argv_rest) {
        strncpy(from->args, argv_rest, sizeof(from->args));
        from->args[sizeof(from->args)-1] = '\0';
    } else {
        from->args[0] = '\0';
    }

    /* If this isn't app_process, use the real path instead of argv[0] */
    snprintf(path, sizeof(path), "/proc/%u/exe", from->pid);
    len = readlink(path, exe, sizeof(exe));
    if (len < 0) {
        PLOGE("Getting exe path");
        return -1;
    }
    exe[len] = '\0';
    if (strcmp(exe, "/system/bin/app_process")) {
        argv0 = exe;
    }

    strncpy(from->bin, argv0, sizeof(from->bin));
    from->bin[sizeof(from->bin)-1] = '\0';

    return 0;
}

static void read_options(struct su_context *ctx)
{
    char mode[12];
    FILE *fp;
    if ((fp = fopen(REQUESTOR_OPTIONS, "r"))) {
        fgets(mode, sizeof(mode), fp);
        if (strcmp(mode, "user\n") == 0) {
            ctx->user.owner_mode = 0;
        } else if (strcmp(mode, "owner\n") == 0) {
            ctx->user.owner_mode = 1;
        }
    }
}

static void user_init(struct su_context *ctx)
{
    if (ctx->from.uid > 99999) {
        ctx->user.userid = ctx->from.uid / 100000;
        if (!ctx->user.owner_mode) {
            snprintf(ctx->user.data_path, PATH_MAX, "/data/user/%d/%s",
                    ctx->user.userid, REQUESTOR);
            snprintf(ctx->user.store_path, PATH_MAX, "/data/user/%d/%s/files/stored",
                    ctx->user.userid, REQUESTOR);
            snprintf(ctx->user.store_default, PATH_MAX, "/data/user/%d/%s/files/stored/default",
                    ctx->user.userid, REQUESTOR);
        }
    }
}

这些代码用来获得su_context 的信息 包括用的 uid gid 等。

mkdir(REQUESTOR_CACHE_PATH, 0770);
if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) {
        deny(&ctx);
}

上面两行代码第一行会在/dev/ 创建一个文件夹 用于创建本地socket 如 /dev/com.xxx.xxx

之后会通过chown函数将该文件夹的用户组访问权限更改为使用su 命令的该组


static int socket_create_temp(char *path, size_t len)
{
    int fd;
    struct sockaddr_un sun;

    fd = socket(AF_LOCAL, SOCK_STREAM, 0);
    if (fd < 0) {
        PLOGE("socket");
        return -1;
    }
    if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
        PLOGE("fcntl FD_CLOEXEC");
        goto err;
    }

    memset(&sun, 0, sizeof(sun));
    sun.sun_family = AF_LOCAL;
    snprintf(path, len, "%s/.socket%d", REQUESTOR_CACHE_PATH, getpid());
    snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", path);

    /*
     * Delete the socket to protect from situations when
     * something bad occured previously and the kernel reused pid from that process.
     * Small probability, isn't it.
     */
    unlink(sun.sun_path);

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

    if (listen(fd, 1) < 0) {
        PLOGE("listen");if (send_intent(&ctx, INTERACTIVE, ACTION_REQUEST) < 0) {
        deny(&ctx);
    }
        goto err;
    }

    return fd;
err:
    close(fd);
    return -1;
}

int socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path));

之后通过创建一个本地socket 来等待链接


if (send_intent(&ctx, INTERACTIVE, ACTION_REQUEST) < 0) {
        deny(&ctx);
 }

socket创建成功后会发送一个广播通知superuser 有用户需要使用到su希望获得权限, 广播中保护用户uid gid  以及socket 的地址。

superuser 介绍到该广播后处理对应的需求 并通过该socket回复给 su

su 用过 setuid setgid 以及执行execlp("/system/bin/sh", "sh", NULL); 后成功切换用户。


你可能感兴趣的:(android开发)