【OSG】CVE-2016-6187 Exploiting Linux kernel heap off-by-one 利用堆大小差一错误爆破Linux内核(下)

原文作者:Vitaly Nikolenko 译者:rodster 校对:song
原文链接:https://cyseclabs.com/blog/cve-2016-6187-heap-off-by-one-exploit

Target object

因为目标对象,我使用了struct subprocess_info 结构,正是96字节大小。为了触发这个对象的分配,下面的套接字操作可以使用一个随机的协议家族:

socket(22, AF_INET, 0);

套接字族22不存在但是模块自动加载会触发到内核中下面的函数:

int call_usermodehelper(char *path, char **argv, char **envp, int wait)
{
        struct subprocess_info *info;
        gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
  
        info = call_usermodehelper_setup(path, argv, envp, gfp_mask,    [6]
                                         NULL, NULL, NULL);
        if (info == NULL)
                return -ENOMEM;
  
        return call_usermodehelper_exec(info, wait);                    [7]
}

call_usermodehelper_setup [6] 然后会分配对象和初始化它的字段:

struct subprocess_info *call_usermodehelper_setup(char *path, char **argv,
                char **envp, gfp_t gfp_mask,
                int (*init)(struct subprocess_info *info, struct cred *new),
                void (*cleanup)(struct subprocess_info *info),
                void *data)
{
        struct subprocess_info *sub_info;
        sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask);
        if (!sub_info)
                goto out;
  
        INIT_WORK(⊂_info->work, call_usermodehelper_exec_work);
        sub_info->path = path;
        sub_info->argv = argv;
        sub_info->envp = envp;
  
        sub_info->cleanup = cleanup;
        sub_info->init = init;
        sub_info->data = data;
  out:  
        return sub_info;
}

一旦对象被初始化,这将绕过 call_usermodehelper_exec in[7]:

int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait)
{
        DECLARE_COMPLETION_ONSTACK(done);
        int retval = 0;
  
        if (!sub_info->path) {                                          [8]
                call_usermodehelper_freeinfo(sub_info);
                return -EINVAL;
        }
...
}

如果路径变量为null[8],然后 cleanup 函数被执行并且对象被释放:

static void call_usermodehelper_freeinfo(struct subprocess_info *info)
{
        if (info->cleanup)
                (*info->cleanup)(info);
        kfree(info);
}

如果我们覆盖了 cleanup 函数指针(记住对象现在在用户空间被分配),然后我们随着CPL=0就有了任意代码执行。仅有的一个问题是subprocess_info 对象分配和释放在同样的路径。在 info->cleanup)(info) 被调用并且设置函数指针到我们的权限提升payload之前修改对象函数指针的一个方法是以某种方法停止执行。我本可以找到其他同样大小的因为分配和函数触发的两种“分开”路径,但是我需要一个理由去尝试 userfaultfd() 和这个页面分裂的想法。

Userfaultfd系统调用能够被用来处理用户空间中的页面错误。我们可以在用户空间分配一个页面并且设置一个处理器(当做一个分线程);当这个页面因为读或写被访问,执行会被转移到用户空间处理器去处理页面错误。这里没有新鲜的并且这是被Jann Hornh所提到的。

SLUB分配器在被分配之前访问对象(首8个字节去更新缓存freelist指针)。因此,这个主意就是分离开subprocess_info 对象到两个连续的页面以便所有对象字段除了说这最后一个(如 void *data)将会被放在同样的页:

然后我们会设置用户空间页面错误处理器去处理在第二页的PF。当call_usermodehelper_setup 去设定sub_info->data ,代码被转移到用户空间PF处理器(在那里我们可以改变先前设定的sub_info->cleanup 函数指针)。如果目标被kmalloc 所分配,这个方法会起作用。不像kmallockzalloc 在分配之后使用memset(..., 0, size(...)) 归零对象。不想glibc,内核的杂类函数实现是十分简洁直接的(例如设置连续化的单个字节):

void *memset(void *s, int c, size_t count)
{
        char *xs = s;
  
        while (count--)
                *xs++ = c;
        return s;
}
EXPORT_SYMBOL(memset);

这意味着设置在第二页的用户空间PF处理器将不再起作用,因为一个PF将会被杂项函数触发。然而,这仍然有可能被束缚用户空间页面错误所绕过:

  1. 分配两个连续页面,分割对象到这两个页面(如之前的)并且为第二个页面设置页面处理器。
  2. 当用户空间PF被杂项函数触发,为第一页设置另一个用户空间PF处理器。
  3. 当对象变量在 call_usermodehelper_setup 中初始化了,那么接下来的用户空间PF将会触发。这时候设置为第二个页面设置另一个PF。
  4. 最终,最后一个的用户空间PF处理器可以修改cleanup 函数指针(通过设置它指向我们的权限提升payload或者ROP链)并且设置path 成员为0(因为这些成员在第一页被分配并且已经初始化了)。
    因为“页面错误”的页面能够通过再次去除内存页映射/映射这些页实现,设置用户空间PF处理器。并且然后传递它们到userfaultfd()。4.5.1版本的POC能够在这里被找到。尽管对于内核版本没有什么特殊的(它应该可以工作在所有含有漏洞的内核)。这里没有权限提升payload但是这POC会在用户空间地址0xdeadbeef 执行指令。

Conclusion

这有可能是更容易利用此漏洞的方法,但是我仅仅想我只想让我发现的目标对象随着userfaultfd “工作”。清理机制缺失是因为我们是分配IPC msg对象,这不是非常重要并且有一些简单的方法稳固系统。

Update - 18/10/2016

Qihoo 360反应他们的工作是独立的并且他们从来没有引用过公共资源。我想他们说的应该没错。

看到这些文章,心里就是觉得:ox:b,可是怎么上手操作下呢。。。