欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]cve-2016-3842分析

[原创]cve-2016-3842分析

static const struct {

unsigned int cmd;

kgsl_ioctl_func_t func;

unsigned int flags;

} kgsl_ioctl_funcs[] = {

KGSL_IOCTL_FUNC(IOCTL_KGSL_DEVICE_GETPROPERTY,

kgsl_ioctl_device_getproperty,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_DEVICE_WAITTIMESTAMP,

kgsl_ioctl_device_waittimestamp,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID,

kgsl_ioctl_device_waittimestamp_ctxtid,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_RINGBUFFER_ISSUEIBCMDS,

kgsl_ioctl_rb_issueibcmds, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_SUBMIT_COMMANDS,

kgsl_ioctl_submit_commands, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_READTIMESTAMP,

kgsl_ioctl_cmdstream_readtimestamp,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID,

kgsl_ioctl_cmdstream_readtimestamp_ctxtid,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP,

kgsl_ioctl_cmdstream_freememontimestamp,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP_CTXTID,

kgsl_ioctl_cmdstream_freememontimestamp_ctxtid,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_DRAWCTXT_CREATE,

kgsl_ioctl_drawctxt_create,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_DRAWCTXT_DESTROY,

kgsl_ioctl_drawctxt_destroy,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_MAP_USER_MEM,

kgsl_ioctl_map_user_mem, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FROM_PMEM,

kgsl_ioctl_map_user_mem, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FREE,

kgsl_ioctl_sharedmem_free, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FLUSH_CACHE,

kgsl_ioctl_sharedmem_flush_cache, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_ALLOC,

kgsl_ioctl_gpumem_alloc, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_CFF_SYNCMEM,

kgsl_ioctl_cff_syncmem, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_CFF_USER_EVENT,

kgsl_ioctl_cff_user_event, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_TIMESTAMP_EVENT,

kgsl_ioctl_timestamp_event,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_SETPROPERTY,

kgsl_ioctl_device_setproperty,

KGSL_IOCTL_LOCK),

KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_ALLOC_ID,

kgsl_ioctl_gpumem_alloc_id, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_FREE_ID,

kgsl_ioctl_gpumem_free_id, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_GET_INFO,

kgsl_ioctl_gpumem_get_info, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_SYNC_CACHE,

kgsl_ioctl_gpumem_sync_cache, 0),

KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_SYNC_CACHE_BULK,

kgsl_ioctl_gpumem_sync_cache_bulk, 0),

};




1.首先,我们来看漏洞的形成原因。

$1

 漏洞产生代码 和 利用方案主要都在 都在kgsl_ioctl_gpumem_alloc_id函数 里面


  首先,我们先看看漏洞产生的函数.

  result = kgsl_mem_entry_attach_process(entry, dev_priv);   // 这个函数产生了漏洞

 

现在我们来看看这里面都有些什么内容。

static int

kgsl_mem_entry_attach_process(struct kgsl_mem_entry *entry,

  struct kgsl_device_private *dev_priv)

{

...  这里添加到了idr中

id = idr_alloc(&process->mem_idr, entry, 1, 0, GFP_NOWAIT);

...

在调用 kgsl_mmu_map 之前  把entry释放掉 造成UAF?

ret = kgsl_mmu_map(pagetable, &entry->memdesc);

...

}

上面是我打印出来的几个和漏洞相关的重要函数

首先是第一个 漏洞参数主要在这个函数内。 首先来看看 idr_alloc  

这个函数 主要是给管理对象,会给对象分配一个编号,然后我们可以再次通过这个编号找到要管理的对象。

                        重要的是这个编号是按照顺序排序的。第一个对方存放进去的时候 返回的 ID 将会是1  这个很重要。销毁对象的时候我们将会用到

我们再来看看销毁对象是怎么做的。

销毁对象的方式有很多,但是其中有一个  IOCTL_KGSL_GPUMEM_FREE_ID 他是根据idr 编号找到对象,然后进行销毁。

释放内存 造成漏洞形成

ioctl(fd,IOCTL_KGSL_GPUMEM_FREE_ID, &arg_free);

通过调用路径我们找到 最终会调用下面的这个函数来销毁对象 ,我们再来具体分析

static long kgsl_ioctl_gpumem_free_id(struct kgsl_device_private *dev_priv,

unsigned int cmd, void *data)

{

struct kgsl_gpumem_free_id *param = data;

struct kgsl_process_private *private = dev_priv->process_priv;

struct kgsl_mem_entry *entry = NULL;

entry = kgsl_sharedmem_find_id(private, param->id);

         ...

/*

* First kgsl_mem_entry_put is for the reference that we took in

* this function when calling kgsl_sharedmem_find_id, second one is

* to free the memory since this is a free ioctl

*/

kgsl_mem_entry_put(entry);

return 0;

}

entry = kgsl_sharedmem_find_id(private, param->id);注意这个函数

                        他是根据ID 来获取到对象的,而我们已经知道idr_alloc的编号是按顺序发放的

                        struct kgsl_gpumem_free_id *param = data; 中的data 使我们从用户空间传入的 

                        那么就是说 param->id 我们可以直接赋值 1的话,他会返回我们kgsl_mem_entry_attach_process函数中

                        管理的entry对象 最后销毁entry对象 

                        

这个调用过程,单线程跑的话。是不会有问题的。

但是如果我们假设用双线程跑到某种特殊的情况下又会是怎么样呢?


                        现在我们来假设一下情况。

首先第一个线程

kgsl_mem_entry_attach_process(struct kgsl_mem_entry *entry,

  struct kgsl_device_private *dev_priv)

{

...  

id = idr_alloc(&process->mem_idr, entry, 1, 0, GFP_NOWAIT);

线程1:跑到这里,然后创建了ID,保存了对象。 ID ret 1

                                这时候 cpu切换到线程2,开始执行线程2的代码

    ...

}

第二个线程

static long kgsl_ioctl_gpumem_free_id(struct kgsl_device_private *dev_priv,

unsigned int cmd, void *data)

{

entry = kgsl_sharedmem_find_id(private, param->id);

kgsl_mem_entry_put(entry);

线程2:执行完这个函数。根据ID 找到了 entry 对象,最后然后释放了entry

}

然后cpu切换到线程1再继续往下执行的话。我们发现 线程1所用的 entry 对象已经被线程2释放掉了。

这样漏洞就被触发了.

2,漏洞利用

刚刚我们弄清了漏洞触发的原理,那接下来,就得说说如何利用漏洞提权了。

首先如何利用漏洞提权,我们先要知道 entry 对象 是怎么样来的。

kgsl_ioctl_gpumem_alloc(struct kgsl_device_private *dev_priv,

unsigned int cmd, void *data)

{

struct kgsl_process_private *private = dev_priv->process_priv;

struct kgsl_gpumem_alloc *param = data;

struct kgsl_mem_entry *entry = NULL;

int result;

param->flags &= ~KGSL_MEMFLAGS_USE_CPU_MAP;

result = _gpumem_alloc(dev_priv, &entry, param->size, param->flags);

if (result)

return result;

result = kgsl_mem_entry_attach_process(entry, dev_priv); //这个是漏洞函数

if (result != 0)

goto err;

kgsl_process_add_stats(private, entry->memtype, param->size);

trace_kgsl_mem_alloc(entry);

param->gpuaddr = entry->memdesc.gpuaddr;

param->size = entry->memdesc.size;

param->flags = entry->memdesc.flags;

return result;

err:

kgsl_sharedmem_free(&entry->memdesc);

kfree(entry);

return result;

}

从代码可以看到 kgsl_mem_entry_attach_process 是漏洞函数

而他的参数 entry 是从 _gpumem_alloc 中得到的

展开后发现,里面有一句代码

entry = kgsl_mem_entry_create();

static inline struct kgsl_mem_entry *

kgsl_mem_entry_create(void)

{

struct kgsl_mem_entry *entry = kzalloc(sizeof(*entry), GFP_KERNEL);

if (!entry)

KGSL_CORE_ERR("kzalloc(%d) failed\n", sizeof(*entry));

else

kref_init(&entry->refcount);

return entry;

}

我们发现 最终 entry 是通过 kzalloc 来申请内存的

内存分配算法什么的就不细说了,本人也不是太熟悉。

        但是我们要了解到几个关键信息。

         1.申请的空间,会根据申请的大小,来确定返回某段内存.

         2.释放后的空间,是会被重复利用的。(也就是说我们再次申请内存的话,是很有可能申请到刚刚释放的那一段内存)

知道了这个内存的机制。那么我们接下来就要找到一个系统调用函数,可以申请到这片空间。

而这个函数必须要满足几个条件

{

1,申请的大小 必须和 entry 差不多。

2,内容必须是可控的,这样才能让程序完全执行,不会崩溃

}

经过一段时间的找寻,终于找到了一个函数。pipe

可以通过write来进行写入

再来漏洞利用的完整逻辑就是这样的

首先第一个线程

kgsl_mem_entry_attach_process(struct kgsl_mem_entry *entry,

  struct kgsl_device_private *dev_priv)

{

...  

id = idr_alloc(&process->mem_idr, entry, 1, 0, GFP_NOWAIT);

//线程1:创建了ID,保存 entry 对象。 索引 == 1

                                //这时候 cpu切换到线程2,开始执行线程2的代码

...

...

if (entry->memdesc.gpuaddr) {

//如果线程2到这里已经执行完毕了的话,那么 entry 的内容已经被改写 data

// ret 将会返回错误值

ret = kgsl_mmu_map(process->pagetable, &entry->memdesc);

if (ret)

kgsl_mem_entry_detach_process(entry);

}

return ret;

...

}

第二个线程

ioctl(fd,IOCTL_KGSL_GPUMEM_FREE_ID, &arg_free);

                                        // arg_free.id = 1 释放掉了entry 内存。

write(pipe[1],data,XX);

//再用pipe 写入

                                        // entry 已经变成我们改写的内容 data 了

 cpu继续切换到线程1

kgsl_mem_entry_attach_process 返回错误后

static long

kgsl_ioctl_gpumem_alloc(struct kgsl_device_private *dev_priv,

unsigned int cmd, void *data)

{

...

result = kgsl_mem_entry_attach_process(entry, dev_priv); // 返回错误 跳转到err: 执行

if (result != 0)

goto err;

...

err:

kgsl_sharedmem_free(&entry->memdesc);

kfree(entry);

return result;

}

因为ret 返回错误 代码会跳转到 err 执行

void kgsl_sharedmem_free(struct kgsl_memdesc *memdesc)

{

if (memdesc == NULL || memdesc->size == 0)

return;

if (memdesc->gpuaddr) {

kgsl_mmu_unmap(memdesc->pagetable, memdesc);

kgsl_mmu_put_gpuaddr(memdesc->pagetable, memdesc);

}

if (memdesc->ops && memdesc->opsza?e ->free)

memdesc->ops->free(memdesc);

                        //entry 已经被修改成data的内容

                        // &entry->memdesc->ops->free(memdesc) 这个函数地址我们是可以控制的了.

kgsl_sg_free(memdesc->sg, memdesc->sglen_alloc);

memset(memdesc, 0, sizeof(*memdesc));

}

这样 &entry->memdesc->ops->free 设置成自己的函数地址后。就可以进行提权操作了。

但是测试之后发现机器还是会崩溃。经过调试发现

kgsl_sharedmem_free(&entry->memdesc);

kfree(entry);

导致崩溃的原因是因为 kfree(entry);

猜测应该是因为entry 已经被销毁了,然后这里再执行了一次,从而导致了崩溃。

这样的话。我们可以在 &entry->memdesc->ops->free 中 通过栈,找到保存了kgsl_ioctl_gpumem_alloc上级函数的返回地址,直接返回

                跳过 kfree(entry),这一句指令的调用,即可避免崩溃

这样整个漏洞利用提权思路分析完毕。


注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功