首先来说一下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); 后成功切换用户。