对linux kernel double-free类型漏洞的较通用利用方法

update Wed Nov 29 16:39:01 HKT 2017

linux kernel 4.14 released this month;

relevant PATCH 0: add a naive detection of double free or corruption

这个补丁, 在进行连续的kfree的时候会起作用终止当前进程. 但是如果在连续的kfree之间其他进程kfree了相邻的一些对象, 导致page->freelist改写, 补丁无作用.

relevant PATCH 1: add SLUB free list pointer obfuscation

这个补丁, 将写入对象首地址(s->offset=0)的数据进行异或, 在申请对象的时候进行逆运算得到下一个申请的对象的位置.

relevant PATCH 2: prefetch next freelist pointer in slab_alloc

这个补丁, 会在申请一个对象的时候, 得到下一个可以申请的对象的位置, 同时对下一个对象保存的异或数据进行逆运算, 然后检测那个位置是否合法.

这几个补丁, 对SLUB的freelist进行了加强. 导致此文的方法不再适用.

背景

由于系统在kfree一个对象时, 将前一个释放的空间的地址保存在将释放的空间的首地址.

如果执行如下代码:

kfree(ptr0);

kfree(ptr1);

kfree(ptr1);

kfree(ptr2);

那么会得到*(unsigned long *)ptr1 = ptr1; *(unsigned long *)ptr2 = ptr1;

两个可以被重新申请的空间的首地址数据相同.

kmalloc(size, ...);

kmalloc(size, ...);

...

可以得到指向同一空间的两个对象.

什么样的对象可以用来进行利用? 两个对象没有特定大小, 一个可以随意写入, 一个包含指针.

测试的所用的需要填充的slab对象为kmalloc-8192, 内核版本为3.10.x, cve-2017-2636, 测试POC Alexander Popov的文档

对象0: encrypted key

struct encrypted_key_payload {

struct rcu_head rcu;

char *format;/* datablob: format */

char *master_desc;/* datablob: master key name */

char *datalen;/* datablob: decrypted key length */

u8 *iv;/* datablob: iv */

u8 *encrypted_data;/* datablob: encrypted data */

unsigned short datablob_len;/* length of datablob */

unsigned short decrypted_datalen;/* decrypted data length */

unsigned short payload_datalen;/* payload data length */

unsigned short encrypted_key_format;/* encrypted key format */

u8 *decrypted_data;/* decrypted data */

u8 payload_data[0];/* payload data + datablob + hmac */

};

这个对象基础大小为0x48, 先看看对象如何申请的, 在encrypted_key_alloc函数中.

static struct encrypted_key_payload *encrypted_key_alloc(struct key *key,

const char *format,

const char *master_desc,

const char *datalen)

{

...

ret = kstrtol(datalen, 10, &dlen);

if (ret < 0 || dlen < MIN_DATA_SIZE || dlen > MAX_DATA_SIZE)

return ERR_PTR(-EINVAL);

format_len = (!format) ? strlen(key_format_default) : strlen(format);

decrypted_datalen = dlen;

payload_datalen = decrypted_datalen;

if (format && !strcmp(format, key_format_ecryptfs)) {

...

}

encrypted_datalen = roundup(decrypted_datalen, blksize);

datablob_len = format_len + 1 + strlen(master_desc) + 1

+ strlen(datalen) + 1 + ivsize + 1 + encrypted_datalen;

/* 这个函数也比较重要 */

ret = key_payload_reserve(key, payload_datalen + datablob_len

+ HASH_SIZE + 1);

if (ret < 0)

return ERR_PTR(ret);

/* 申请指定大小的对象 */

epayload = kzalloc(sizeof(*epayload) + payload_datalen +

datablob_len + HASH_SIZE + 1, GFP_KERNEL);

if (!epayload)

return ERR_PTR(-ENOMEM);

epayload->payload_datalen = payload_datalen;

epayload->decrypted_datalen = decrypted_datalen;

epayload->datablob_len = datablob_len;

return epayload;

}

对于encrypted key的用法, 可以参考Documentations/security/keys-trusted-encrypted.txt

这里简单说一下用到的payload的格式.

"new default user:user_key_desc payload_len".

函数参数中的datalen指向payload_len, master_desc指向user:user_key_desc, format指向default, payload最大为4096, 也即encrypted_key_payload对象最大的时候会取kmalloc-8192. 最小的时候由于加上了HASH_SIZE+1, 最小0x48+32+1=0x69

因此这个对象可以落在 kmalloc-96 kmalloc-128 - kmalloc-8192区域

使用encrypted key的系统限制 以及 对应的策略

在/proc/sys/kernel/keys/中, 保存着当前系统普通用户能申请的key数以及总大小,限制了这个对象的喷的总数.

在encrypted_update函数中, 也调用了encrypted_key_alloc函数, 然后会释放之前申请的空间, 可以利用这个函数来进行交替性的堆喷.

static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)

{

struct encrypted_key_payload *epayload = key->payload.data[0];

struct encrypted_key_payload *new_epayload;

char *buf;

char *new_master_desc = NULL;

const char *format = NULL;

size_t datalen = prep->datalen;

int ret = 0;

if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))

return -ENOKEY;

if (datalen <= 0 || datalen > 32767 || !prep->data)

return -EINVAL;

buf = kmalloc(datalen + 1, GFP_KERNEL);

if (!buf)

return -ENOMEM;

buf[datalen] = 0;

memcpy(buf, prep->data, datalen);

ret = datablob_parse(buf, &format, &new_master_desc, NULL, NULL);

if (ret < 0)

goto out;

/* update的时候, 如果master_desc不匹配, 返回EINVAL */

ret = valid_master_desc(new_master_desc, epayload->master_desc);

if (ret < 0)

goto out;

/* 校验完成, 申请新的payload */

new_epayload = encrypted_key_alloc(key, epayload->format,

new_master_desc, epayload->datalen);

if (IS_ERR(new_epayload)) {

ret = PTR_ERR(new_epayload);

goto out;

}

__ekey_init(new_epayload, epayload->format, new_master_desc,

epayload->datalen);

memcpy(new_epayload->iv, epayload->iv, ivsize);

memcpy(new_epayload->payload_data, epayload->payload_data,

epayload->payload_datalen);

rcu_assign_keypointer(key, new_epayload);

/* 释放之前的payload */

call_rcu(&epayload->rcu, encrypted_rcu_free);

out:

kfree(buf);

return ret;

}

用encrypted_key_payload 来任意地址读

在double-free环境中, 另外一个对象覆盖了encrypted_key_payload的数据.

在encrypted_read函数中, 会读取payload->format payload->master_desc payload->datalen payload->iv指向的数据.

static long encrypted_read(const struct key *key, char __user *buffer,

size_t buflen)

{

struct encrypted_key_payload *epayload;

struct key *mkey;

const u8 *master_key;

size_t master_keylen;

char derived_key[HASH_SIZE];

char *ascii_buf;

size_t asciiblob_len;

int ret;

epayload = rcu_dereference_key(key);

/* returns the hex encoded iv, encrypted-data, and hmac as ascii */

asciiblob_len = epayload->datablob_len + ivsize + 1

+ roundup(epayload->decrypted_datalen, blksize)

+ (HASH_SIZE * 2);

if (!buffer || buflen < asciiblob_len)

return asciiblob_len;

mkey = request_master_key(epayload, &master_key, &master_keylen);

if (IS_ERR(mkey))

return PTR_ERR(mkey);

ret = get_derived_key(derived_key, ENC_KEY, master_key, master_keylen);

if (ret < 0)

goto out;

ret = derived_key_encrypt(epayload, derived_key, sizeof derived_key);

if (ret < 0)

goto out;

ret = datablob_hmac_append(epayload, master_key, master_keylen);

if (ret < 0)

goto out;

/* 读取所需数据到buf中 */

ascii_buf = datablob_format(epayload, asciiblob_len);

if (!ascii_buf) {

ret = -ENOMEM;

goto out;

}

up_read(&mkey->sem);

key_put(mkey);

if (copy_to_user(buffer, ascii_buf, asciiblob_len) != 0)

ret = -EFAULT;

kfree(ascii_buf);

return asciiblob_len;

out:

up_read(&mkey->sem);

key_put(mkey);

return ret;

}

static char *datablob_format(struct encrypted_key_payload *epayload,

size_t asciiblob_len)

{

char *ascii_buf, *bufp;

u8 *iv = epayload->iv;

int len;

int i;

ascii_buf = kmalloc(asciiblob_len + 1, GFP_KERNEL);

if (!ascii_buf)

goto out;

ascii_buf[asciiblob_len] = '\0';

/* copy datablob master_desc and datalen strings */

len = sprintf(ascii_buf, "%s %s %s ", epayload->format,

epayload->master_desc, epayload->datalen);

/* convert the hex encoded iv, encrypted-data and HMAC to ascii */

bufp = &ascii_buf[len];

for (i = 0; i < (asciiblob_len - len) / 2; i++)

bufp = hex_byte_pack(bufp, iv[i]);

out:

return ascii_buf;

}

用encrypted key 提权

在encrypted_destroy函数中, 会将区域清0, 用此可完成提权.

static void encrypted_destroy(struct key *key)

{

struct encrypted_key_payload *epayload = key->payload.data[0];

if (!epayload)

return;

memset(epayload->decrypted_data, 0, epayload->decrypted_datalen);

kfree(key->payload.data[0]);

}

对象1: tty_struct.write_buf

struct tty_struct {

...

#define N_TTY_BUF_SIZE 4096

...

unsigned char *write_buf;

int write_cnt;

...

};

write_buf成员在do_tty_write函数中申请, 默认长度为2048.

static inline ssize_t do_tty_write(

ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),

struct tty_struct *tty,

struct file *file,

const char __user *buf,

size_t count)

{

ssize_t ret, written = 0;

unsigned int chunk;

ret = tty_write_lock(tty, file->f_flags & O_NDELAY);

if (ret < 0)

return ret;

chunk = 2048;/* 默认大小为2048 */

if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))

chunk = 65536;/* 如果标志置位, 则扩充大小到65536 */

if (count < chunk)

chunk = count;

/* write_buf/write_cnt is protected by the atomic_write_lock mutex */

if (tty->write_cnt < chunk) {

unsigned char *buf_chunk;

if (chunk < 1024)

chunk = 1024;

buf_chunk = kmalloc(chunk, GFP_KERNEL);

if (!buf_chunk) {

ret = -ENOMEM;

goto out;

}

kfree(tty->write_buf);

tty->write_cnt = chunk;

tty->write_buf = buf_chunk;

}

/* Do the write .. */

for (;;) {

...

}

...

out:

tty_write_unlock(tty);

return ret;

}

从代码里面可以看出, write_buf的大小也是可控的, 大小[2048, 65536].

搜索代码, 得到TTY_NO_WRITE_SPLIT标志在n_hdlc.c中有路径会将其置位. 而write_buf指向的空间数据可以通过write系统调用来实现.

NOTE: 需要注意的是, 用open打开tty时需要加上O_NONBLOCK标志.

利用步骤

结合encrypted_key_payload和tty_struct.write_buf, 完成利用.

准备工作.

堆喷, 准备大量的所需大小的对象, 放入内核空间, 便于后续的检测反馈.

一个user-type的key, encrypted key需要这个.

一个或多个encrypted的key, 消耗key的总大小, 便于后续的检测反馈.

触发double-free.

(交替)申请write_buf, encrypted_key_payload对象(使用encrypted_update函数).

这个可能需要根据漏洞具体的环境来看申请的对象的顺序.

检测encrypted_update的返回值, 如果为EINVAL, 则判断此时的内核空间中两个对象重叠.

不停的调用encrypted_update检测合适的master_desc的位置.

由于在read函数中需要master_desc的值, 所以我们首先需要遍历内核空间, 找到所需要的字串.

所以我们设置好encrypted_update的参数(通过write_buf), 使调用过程如下.

encrypted_update -> encrypted_key_alloc -> key_payload_reserve.

当其返回EDQUOT时, 即找到对应的master_desc.

在准备工作中的堆喷和消耗key的总大小, 即是为了找到这个master_desc.

此时已经具备任意地址读的能力. 检测init_task (此过程未测试), 或者检测相应的task_struct结构中的comm字段, 找到目标进程的task_struct地址, 然后获取cred地址.

调用encrypted_destroy, 完成提权.

这个函数的调用需要先keyctl_revoke, 它只是将key进行一下标记, 然后调用gc.

在测试过程中发现, 在keyctl_revoke之后, 立即调用add_key来申请一个与需要destroy的key相同的payload, 会立即触发encrypted_destory函数.

总结

利用的主要对象为encrypted_key_payload, 适用大小为 [96-8192] [128-8192]的对象, POC中只进行了kmalloc-8192的测试.

write_buf可以应用在目标对象为[2048, 4096, 8192]大小时.

依赖于slab的优先申请最近释放的块的特性.

KSPP中的CONFIG_SLAB_FREELIST_HARDENED应该已经加固了这个特性

linux内核漏洞分类,blog/linux kernel double-free类型漏洞的利用.md at master · snorez/blog · GitHub...相关推荐

  1. swagger php 生成api,blog/Swagger生成php restful API接口文档.md at master · lfq618/blog · GitHub...

    Swagger生成php restful API接口文档 背景 我们的restful api项目采用yaf框架, 整体结构简单, 我们只需要用swagger扫描 application目录即可. 下面 ...

  2. linux内核 lts长期演进,Linux Kernel 4.19 将成为下一个LTS(长期支持)系列

    最近Linux内核开发人员和维护人员Greg Kroah-Hartman透露,Linux Kernel 4.19将下一个长期支持的Linux内核系列. 现在Linux Kernel 4.17已经达到使 ...

  3. linux 内核flush,armv8(aarch64)linux内核中flush_dcache_all函数详细分析

    /* *  __flush_dcache_all() *  Flush the wholeD-cache. * Corrupted registers: x0-x7, x9-x11 */ ENTRY( ...

  4. 一文了解linux内核,一文了解Linux的系统结构

    什么是 Linux ? 如果你以前从未接触过Linux,可能就不清楚为什么会有这么多不同的Linux发行版.在查看Linux软件包时,你肯定被发行版.LiveCD和GNU之类的术语搞晕过.初次进入Li ...

  5. 搭建《深入Linux内核架构》的Linux环境

    搭建<深入Linux内核架构>的Linux环境 阅读目录(Content) 作者 软件 概述 正文 一.安装GCC 二.编译Linux内核 三.制作跟文件系统 四.运行qemu 五.启动l ...

  6. linux内核启动分析 三,Linux内核分析 实验三:跟踪分析Linux内核的启动过程

    贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...

  7. Linux内核入门-如何获取Linux内核源代码、生成配置内核

    如何获取Linux内核源代码 如何获取Linux内核源代码 下载Linux内核当然要去官方网站了,网站提供了两种文件下载,一种是完整的Linux内核,另一种是内核增量补丁,它们都是tar归档压缩包.除 ...

  8. 查看linux内核的编译时间,linux内核编译步骤

    linux内核编译步骤 对于linux新手来说,编译内核相对有一些难度,甚至不知道如何入手,我通过在网上收集这方面的资料,最终编译成功.现在我归纳了一下,写出这一篇还算比较详细的步骤,希望能对各位新手 ...

  9. Linux内核开发_1_编译LInux内核

    目录 1. 准备工作 1.1 学习环境 1.2 下载Linux内核源码 1.3 解压Linux内核 1.4 目录结构介绍 2. Linux内核配置 2.1 配置选项 1. make config 2. ...

最新文章

  1. linux断电关机后,进度条满后卡在那里
  2. 02-导航实例-storyboard实现
  3. Leetcode5634. 删除子字符串的最大得分[C++题解]:贪心
  4. 下载python步骤_下载及安装Python详细步骤
  5. carsim学习笔记4——构建路面
  6. Java Okio-更加高效易用的IO库
  7. 互联网时代下,看租赁电商如何玩转消费金融
  8. 在职场中,如何能够让下属认真完成工作,又不顶撞你?
  9. 计算机怎么用字节表示300,网速300兆是什么意思(图文)
  10. 科技公司钟爱的50款开源工具
  11. XHTML学习资料(三)—— 表格
  12. Mac下Eclipse反编译插件安装
  13. Maven的打包命令
  14. Unity Container 应用示例
  15. android 播放assets下视频,安卓播放assets文件里视频文件相关问题分析
  16. HNOI2014 世界树 基于虚树的树形动态规划
  17. 谷歌浏览器打包扩展程序和导入扩展程序
  18. Spark 第三讲 Scala数组与函数基础
  19. 十二.vue-resourse实现get,post,jsonp请求
  20. 教你如何搜索pois(兴趣点),制作可视化作品

热门文章

  1. 找到数组里消失的数字(鸽笼原理)
  2. python打卡以及Linux自动运行python文件
  3. stable-baselines3学习之Logger
  4. 【Other】比秒小的时间单位及换算
  5. *4-2 CCF 2014-12-2 Z字形扫描
  6. DDD 聚合根 限界上下文
  7. [源码和文档分享]基于Python实现的论坛帖子情感分析
  8. matlab 矩阵大小变换,matlab 矩阵变换
  9. Linux终端连接遇到的两个问题
  10. 小程序-demo:知乎日报