记一个linux内核内存提权问题

简介:

前些天,linux内核曝出了一个内存提权漏洞。通过骇客的精心构造,suid程序将print的输出信息写到了自己的/proc/$pid/mem文件里面,从而修改了自己的可执行代码,为普通用户开启了一个带root权限的shell。这个过程还是挺有意思的,不得不佩服骇客们的聪明才智,故在此分享一下,以表崇敬之情。

首先,破解过程使用到了suid程序。suid并不是一个程序,而是可执行文件的一种属性。当你执行一个带有suid属性的程序时,在执行suid程序期间,你启动的进程的user将被临时改为suid程序的owner,进程将拥有程序owner所拥有的权限。这一特性经常用于让普通用户临时获得root权限。
在linux系统中,很多功能是需要root权限才能使用的。用户如果想要用到这些功能,可以有两个办法:一是使用root用户登录。但是很可能你没有root密码。就算有,这样做也不安全(误操作是致命的);二是执行一个owner是root的suid程序。这样就可以在不使用root用户登录的情况下,被允许使用一些需要root权限的功能,既方便又安全。
比如我们经常用到的ping命令,它就是这样的一个suid程序。
$ ll /bin/ping
-rwsr-xr-x 1 root root 37312 Aug 6 2008 /bin/ping
(注意s属性,这就是suid标志。)
ping使用了ICMP协议,通过发送ICMP报文来探测网络的连通性。但是因为ICMP是IP层协议,其行为代表的是整个机器(而不是像传输层协议那样,代表机器上的某个应用),故只有root用户才能建立ICMP报文。而我们之所以不使用root用户登录也可以执行ping命令,其原因正是suid。

那么,怎么保证suid程序提供的root权限不被滥用呢?换句话说,通过suid得到root权限跟使用root用户登录有什么不同呢?一般来说,suid程序都是封闭的,只干某件事情(并且干的事情都确定不会有危害),干完就退出(不会节外生枝)。再以ping为例,普通用户可以执行ping命令来发送ICMP报文。但是ping命令只会发送网络探测相关的ICMP报文,普通用户无法利用它来建立任意的ICMP报文,更不可能利用它来完成其他需要root权限才能做的事情,比如删除用户。

能不能通过修改suid程序,使其去做一些越权的事情呢?比如修改ping程序,使其能够删除用户?这也是不可以的。suid程序跟其他文件一样,受访问权限的保护,一般只有其owner才能有权限修改它,其他用户只能读或者执行。
不过,在这次的破解中,骇客却真的修改了suid程序。怎么办到的呢?利用/proc/$pid/mem。

proc文件/proc/pid/mempid进程的一份内存镜像,能够通过它来读写到进程的所有内存,包括可执行代码(它们已经映射到内存中)。在2.6.39版本以前,这份内存镜像是不可写的,不过后来这个限制被取消了。当然,对/proc/pid/mempid进程自己写自己的/proc/pid/memsuidsuidstderrstdout/proc/pid/mem,它在输出信息的时候不就会将你输入的信息改写到自己的内存里去了么!

比如骇客利用的su命令:
ll/bin/surwsrxrx1rootroot28336Oct312008/bin/su su hahahaha
su: user hahahaha does not exist

输入参数"hahahaha"是一个不存在的用户,su命令会通过stderr输出错误信息,并且信息里面就包含我们的输入参数"hahahaha"。如果输入参数是一段二进制代码,那么它同样也会出现在输出信息中!
然后,跟其他可执行程序一样,su的输出是可以重定向的,比如:
suhahahaha2>ttt cat ttt
su: user hahahaha does not exist

那么,如果将输出重定向到执行su的进程自己的/proc/pid/mem su hahahaha 2> /proc/self/mem


不过现在还有两个问题要解决……
第一个还是权限问题。现在已经让执行su的进程自己修改自己的/proc/$pid/mem,不过还不够。再来看看具体还有哪些权限检查。

1、open操作:
static int mem_open(struct inode* inode, struct file* file)
{
file->private_data = (void*)((long)current->self_exec_id);
......
}
没有权限检查,但是会将current->self_exec_id记录下来,后面会对其做校验。

2、write操作:
static ssize_t mem_write(struct file * file, const char __user *buf,size_t count, loff_t *ppos)
{
......
struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
......
mm = check_mem_permission(task);
copied = PTR_ERR(mm);
if (IS_ERR(mm))
goto out_free;
......
if (file->private_data != (void *)((long)current->self_exec_id))
goto out_mm;
......
}
有两处检查,一是通过check_mem_permission()检查当前进程是否可以操作该文件,这就是前面所提到的,只会允许本进程或者调试进程的操作。现在这一关已经过了。
另一处检查是对self_exec_id的检查,要求进程在对/proc/$pid/mem进行open()和write()的时候拥有相同的self_exec_id(注意,前面在open的时候已经把当时的self_exec_id记录在了file->private_data中)。另一方面,每当一个进程调用exec()来执行程序时,进程的self_exec_id会自增:
void setup_new_exec(struct linux_binprm * bprm)
{
......
current->self_exec_id++;
......
}

而我们之前的那句shell命令(su hahahaha 2> /proc/self/mem)大致是这样实现的:
int fd = open("/proc/self/mem", O_WRONLY);
dup2(fd, 2);
close(fd);
execve("/bin/su", {"su", "hahahaha"}, {...});

注意,虽然suid程序的错误输出被重定向到了/proc/self/mem,但是由于open()和write()分别发生于execve()的之前和之后,两次的self_exec_id是不同的,所以write()操作无法通过权限检查……内核正是利用self_exec_id来确保/proc/pid/memexec()selfexecid1fork()selfexecid2exec()open()/proc/pid/mem(注意open的时候没有权限检查,所以能够open成功);
3、通过诸如unix socket的方法,将子进程中打开的fd传回给父进程(没想到unix socket还有这么一招吧~ man一下cmsg,看看关于SCM_RIGHTS的内容);
4、由于子进程是exec()之后再open()的,记录在file中的self_exec_id会自增一次。所以父进程exec()执行suid程序之后,write()时的self_exec_id刚好就跟open()时一样了;

OK!之前提到的两个问题,第一个权限问题已经解决了,现在我们已经能让suid程序在自己的内存空间中写一些我们想要的可执行代码。第二个问题,这些可执行代码该写到什么地方去?随便乱写显然是没有意义的。

首先,我们能控制suid程序写文件的位置吗?
可以!比如这样:
int fd = open("/proc/self/mem", O_WRONLY);
dup2(fd, 2);
lseek64(fd, pos_what_we_want, SEEK_SET);
close(fd);
execve("/bin/su", {"su", "hahahaha"}, {...});
然后su就会顺着我们lseek64()设置的位置开始写。

其次,应该选择哪个位置呢?有两个条件:
1、在我们期望的write()之后的必经之路上;
2、程序流程是跳转到这个位置来的,而不是顺序执行下来的。因为像su的输出那样("su: user hahahaha does not exist"),在我们输入的内容前面会有一些其他的信息("su: user "),这些信息肯定会把可执行代码写坏的,唯一的办法就是让程序流程不要执行到它们,而是直接跳转到我们的输入上;
比如骇客选择了exit()函数的入口,就能满足以上两个条件:程序最后都会调用libc库函数exit()来退出、而作为函数的入口点,程序流程是通过call指令跳转过来的。而lseek64()所需要指定的位置,就是exit()入口点减去strlen("su: user ")的位置。

再次,怎么找到exit()的入口点呢?
最简单的办法就是objdump,如:
objdumpd/bin/su|awk2 == "<exit@plt>:"{print}'
0000000000001c90 <exit@plt>:
还有一点就是要求suid程序被载入内存的时候位置是不能随机的,否则objdump看到的exit()入口点就不是运行时真正的入口点(并且每次运行的入口点都还可能不一样)。
通过"readelf -h bin""Type"bin是否是按位置无关来编译的(DYN表示位置无关、EXEC则相反)。如果不是位置无关,那么objdump看到的地址就是运行时的地址。骇客使用了su程序来进行破解,正是因为在他的系统上,/bin/su的Type是EXEC。

在别的系统上这个未必成立,比如我的系统:
$ readelf -h /bin/su | grep Type
Type: DYN (Shared object file)

在我的系统中,可以选用umount来进行破解,也是同样的道理。
ll/bin/umountrwsrxrx1rootroot40208Nov262008/bin/umount umount hahahaha
umount: hahahaha is not mounted (according to mtab)
$ readelf -h /bin/umount | grep Type
Type: EXEC (Executable file)

最后,就是要在exit()的入口点写什么内容的问题了。很简单,写一段代码,使用execve()系统调用运行一个shell就行了。suid程序已经带来了root权限,以后想干什么都交给这个shell吧~
不过注意,这里要写的不是C代码、不是汇编代码、而是二进制的机器代码。

骇客的原文见:http://blog.zx2c4.com/749,里面包含了破解代码的链接。还有,linus的补丁也已经出来了,在骇客的那篇文章中也能找到链接。如果本文所讨论的问题你都已经理解了,骇客的blog原文对于你来说也就不会有难度。


七伤
+关注
目录
打赏
0
0
0
0
52
分享
相关文章
|
1月前
|
linux 手动释放内存
在 Linux 系统中,内存管理通常自动处理,但业务繁忙时缓存占用过多可能导致内存不足,影响性能。此时可在业务闲时手动释放内存。
114 17
Linux中的System V通信标准--共享内存、消息队列以及信号量
希望本文能帮助您更好地理解和应用System V IPC机制,构建高效的Linux应用程序。
106 48
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
131 20
Intel Linux 内核测试套件-LKVS介绍 | 龙蜥大讲堂104期
《Intel Linux内核测试套件-LKVS介绍》(龙蜥大讲堂104期)主要介绍了LKVS的定义、使用方法、测试范围、典型案例及其优势。LKVS是轻量级、低耦合且高代码覆盖率的测试工具,涵盖20多个硬件和内核属性,已开源并集成到多个社区CICD系统中。课程详细讲解了如何使用LKVS进行CPU、电源管理和安全特性(如TDX、CET)的测试,并展示了其在实际应用中的价值。
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
101 15
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
linux 内存监控
linux 内存监控
79 1
linux性能监控:内存监控命令之free命令
linux性能监控:内存监控命令之free命令
255 1
linux性能监控:内存监控命令之free命令

热门文章

最新文章