结合init源码剖析Android root提权漏洞(CVE-2010-EASY)

结合Android init进程源码深入分析CVE-2010-EASY漏洞的热拔插信息处理流程。

这篇文章是上一篇博客的后续分析,主要介绍向init进程发送热拔插信息后init进程的处理流程。

首先我们来了解一个数据结构,uevent,如下:

struct uevent {                                                                                
    const char *action;                                                                        
    const char *path;                                                                          
    const char *subsystem;                                                                     
    const char *firmware;                                                                      
    int major;                                                                                 
    int minor;                                                                                 
};  

 
内核收到的信息如下:

ACTION=add
DEVPATH=/../data/local/tmp
SUBSYSTEM=firmware
FIRMWARE=../../../data/local/tmp/hotplug


通过如下函数parse_event进行解析:

static void parse_event(const char *msg, struct uevent *uevent) 
{
 
    while(*msg) {                                                                              
        if(!strncmp(msg, "ACTION=", 7)) {                                                      
            msg += 7;                                                                          
            uevent->action = msg;                                                              
        } else if(!strncmp(msg, "DEVPATH=", 8)) {                                              
            msg += 8;                                                                          
            uevent->path = msg;                                                                
        } else if(!strncmp(msg, "SUBSYSTEM=", 10)) {                                           
            msg += 10;                                                                         
            uevent->subsystem = msg;                                                           
        } else if(!strncmp(msg, "FIRMWARE=", 9)) {                                             
            msg += 9;                                                                          
            uevent->firmware = msg;                                                            
        } else if(!strncmp(msg, "MAJOR=", 6)) {                                                
            msg += 6;                                                                          
            uevent->major = atoi(msg);                                                         
        } else if(!strncmp(msg, "MINOR=", 6)) {                                                
            msg += 6;                                                                          
            uevent->minor = atoi(msg);                                                         
        }
         while(*msg++);
    }           
}

经过解析之后,uevent的结构为:

Action="add"
path="/../data/local/tmp"
subsystem="firmware"
firmware="../../../data/local/tmp/hotplug"

 
之后来到处理firmware的核心函数:

static void process_firmware_event(struct uevent *uevent)
{
    l = asprintf(&root, SYSFS_PREFIX"%s/", uevent->path);
    //root为/sys/../data/local/tmp/=/data/local/tmp/
    
    l = asprintf(&loading, "%sloading", root);
    //loading为/data/local/tmp/loading
    
    l = asprintf(&data, "%sdata", root);
    //data为/data/local/tmp/data   其内容为指向/proc/sys/kernel/hotplug的符号链接
    
    l = asprintf(&file, FIRMWARE_DIR"/%s", uevent->firmware);
    //file为/etc/firmware/../../../data/local/tmp/hotplug=/data/local/tmp/hotplug
    
    loading_fd = open(loading, O_WRONLY);
    
    data_fd = open(data, O_WRONLY); 
    
    fw_fd = open(file, O_RDONLY);
    
    load_firmware(fw_fd, loading_fd, data_fd);
}
 
最后来到load_firmware函数,把hotplug中的数据写到/proc/sys/kernel/hotplug中
其内容变为/data/local/tmp/exploid

static int load_firmware(int fw_fd, int loading_fd, int data_fd)
{
    while (len_to_copy > 0) {
        char buf[PAGE_SIZE];
 
 
        nr = read(fw_fd, buf, sizeof(buf));
                                                                         
        len_to_copy -= nr;                                                                     
        while (nr > 0) {                                                                       
                                                                                               
            nw = write(data_fd, buf + nw, nr);                                                 
 
            nr -= nw;
        }
    }
}

 
最后/proc/sys/kernel/hotplug中写入了我们的恶意程序,只要再次受到如wifi打开、usb插入等热拔插信息,内核就会以root权限加载我们的程序再一次执行,从而达到提权的目的。

漏洞修复

修复方法很简单,只需要给system/core/init/devices.c文件打个补丁。

具体内容及注释如下:

   static int open_uevent_socket(void)                                                            
{ 
+    setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
//在open_uevent_socket对套接字增加一个选项 SO_PASSCRED,这样可以让套接字增加一个认证,让接收者可以知道发送者的uid和gid :-)
 
}
 
void handle_device_fd(int fd)
{
+    for(;;) {
+        char msg[UEVENT_MSG_LEN+2];
+        char cred_msg[CMSG_SPACE(sizeof(struct ucred))];
+        struct iovec iov = {msg, sizeof(msg)};
+        struct sockaddr_nl snl;
+        struct msghdr hdr = {&snl, sizeof(snl), &iov, 1, cred_msg, sizeof(cred_msg), 0};
+
+        ssize_t n = recvmsg(fd, &hdr, 0);
+        if (n <= 0) {
+            break;
+        }
 
-    while((n = recv(fd, msg, UEVENT_MSG_LEN, 0)) > 0) {
-        struct uevent uevent;
+        if ((snl.nl_groups != 1) || (snl.nl_pid != 0)) {
+            /* 如果不是内核的多播信息则抛弃 */
+            continue;
+        }
+
+        struct cmsghdr * cmsg = CMSG_FIRSTHDR(&hdr);
+        if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
+            /* 如果发送者的认证没有则抛弃 */
+            continue;
+        }
+
+        struct ucred * cred = (struct ucred *)CMSG_DATA(cmsg);
+        if (cred->uid != 0) {
+            /* 消息不是来自于root用户则抛弃 */
+            continue;
+        }
}
}