linux内核漏洞分类,blog/linux kernel double-free类型漏洞的利用.md at master · snorez/blog · GitHub...
对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...相关推荐
- swagger php 生成api,blog/Swagger生成php restful API接口文档.md at master · lfq618/blog · GitHub...
Swagger生成php restful API接口文档 背景 我们的restful api项目采用yaf框架, 整体结构简单, 我们只需要用swagger扫描 application目录即可. 下面 ...
- linux内核 lts长期演进,Linux Kernel 4.19 将成为下一个LTS(长期支持)系列
最近Linux内核开发人员和维护人员Greg Kroah-Hartman透露,Linux Kernel 4.19将下一个长期支持的Linux内核系列. 现在Linux Kernel 4.17已经达到使 ...
- linux 内核flush,armv8(aarch64)linux内核中flush_dcache_all函数详细分析
/* * __flush_dcache_all() * Flush the wholeD-cache. * Corrupted registers: x0-x7, x9-x11 */ ENTRY( ...
- 一文了解linux内核,一文了解Linux的系统结构
什么是 Linux ? 如果你以前从未接触过Linux,可能就不清楚为什么会有这么多不同的Linux发行版.在查看Linux软件包时,你肯定被发行版.LiveCD和GNU之类的术语搞晕过.初次进入Li ...
- 搭建《深入Linux内核架构》的Linux环境
搭建<深入Linux内核架构>的Linux环境 阅读目录(Content) 作者 软件 概述 正文 一.安装GCC 二.编译Linux内核 三.制作跟文件系统 四.运行qemu 五.启动l ...
- linux内核启动分析 三,Linux内核分析 实验三:跟踪分析Linux内核的启动过程
贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...
- Linux内核入门-如何获取Linux内核源代码、生成配置内核
如何获取Linux内核源代码 如何获取Linux内核源代码 下载Linux内核当然要去官方网站了,网站提供了两种文件下载,一种是完整的Linux内核,另一种是内核增量补丁,它们都是tar归档压缩包.除 ...
- 查看linux内核的编译时间,linux内核编译步骤
linux内核编译步骤 对于linux新手来说,编译内核相对有一些难度,甚至不知道如何入手,我通过在网上收集这方面的资料,最终编译成功.现在我归纳了一下,写出这一篇还算比较详细的步骤,希望能对各位新手 ...
- Linux内核开发_1_编译LInux内核
目录 1. 准备工作 1.1 学习环境 1.2 下载Linux内核源码 1.3 解压Linux内核 1.4 目录结构介绍 2. Linux内核配置 2.1 配置选项 1. make config 2. ...
最新文章
- linux断电关机后,进度条满后卡在那里
- 02-导航实例-storyboard实现
- Leetcode5634. 删除子字符串的最大得分[C++题解]:贪心
- 下载python步骤_下载及安装Python详细步骤
- carsim学习笔记4——构建路面
- Java Okio-更加高效易用的IO库
- 互联网时代下,看租赁电商如何玩转消费金融
- 在职场中,如何能够让下属认真完成工作,又不顶撞你?
- 计算机怎么用字节表示300,网速300兆是什么意思(图文)
- 科技公司钟爱的50款开源工具
- XHTML学习资料(三)—— 表格
- Mac下Eclipse反编译插件安装
- Maven的打包命令
- Unity Container 应用示例
- android 播放assets下视频,安卓播放assets文件里视频文件相关问题分析
- HNOI2014 世界树 基于虚树的树形动态规划
- 谷歌浏览器打包扩展程序和导入扩展程序
- Spark 第三讲 Scala数组与函数基础
- 十二.vue-resourse实现get,post,jsonp请求
- 教你如何搜索pois(兴趣点),制作可视化作品