首页
论坛
课程
招聘
【OSG】CVE-2016-6187 Exploiting Linux kernel heap off-by-one 利用堆大小差一错误爆破Linux内核(下)
2017-5-11 08:47 1648

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

2017-5-11 08:47
1648

 原文作者: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反应他们的工作是独立的并且他们从来没有引用过公共资源。我想他们说的应该没错。







[公告]《安卓APP加固攻与防》训练营!Android逆向分析技能,干货满满,报名免费赠送一部手机!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回