1、Linux内核exploit介绍
在linux下,整个内存空间中,只有一部分低地址是进程可访问的,而高地址处则是属于内核。例如,在x86的机器上,0x00000000
到0xbfffffff
是属于进程的,而0xc0000000
到0xffffffff
这1 GB是属于内核的。
出于安全考虑,进程无法访问属于内核的内存,否则恶意进程就有可能对系统内核的内存进行读取或者篡改。反过来,属于进程的内存,是可以被内核访问的。特别地,在内核太可以跳转执行用户空间中的代码。(不过,在某些硬件上,比如较新的Core CPU,Intel加入了SMEP等保护功能,限制了内核执行用户空间代码的操作)
内核空间的exploit,本质上与用户空间的exploit是相同的:都是修改执行流程,达到我们的目的。一般来说,用户空间的exploit,其目的是获取shell;而内核空间的exploit,其目的则是提升权限,获取对系统的完全控制。
Linux系统下,每个进程拥有其对应的struct cred
,用于记录该进程的uid。内核exploit的目的,便是修改当前进程的cred,从而提升权限。当然,进程本身是无法篡改自己的cred的,我们需要在内核空间中,通过以下方式来达到这一目的:
|
|
其中,prepare_kernel_cred()
创建一个新的cred,参数为0则将cred中的uid, gid设置为0,对应于root用户。随后,commit_creds()
将这个cred应用于当前进程。此时,进程便提升到了root权限。
这些方法的地址,可以通过/proc/kallsyms
获取。不过,有时为了安全,管理员会隐藏内核符号的地址,此时便无法通过这一方式获取地址。
提升权限后,我们还需要返回到用户空间。在这里,我们可以运行shell,从而以root身份执行任意命令了。
2、范例——pwnable.kr: syscall
在pwnable.kr上有一道题,syscall,就可以作为内核exploit的入门
其中将syscall.c编译进了内核
|
|
2.1 问题分析
你会发现这道题目提供了一个编写的内核模块源码,其中添加了一个syscall,其执行的逻辑基本与strcpy()
相同,只是会将小写字母变为大写字母。
那么,这里的漏洞就很明显了,基本上就是一个向任意地址写任意内容的漏洞,只要写入的内容不包含小写字母。接下来,就是如何利用这个syscall,获取root权限,从而读取flag文件。
系统调用的地址存在0x8000e348+223 = 0x8000e6c4
, flag在/root/flag
2.2 解题思路
首先修改 223 号系统调用的内容,然后调用这个修改过的 223 号系统调用,在 kernel space 把 uid 改掉,之后在 user space execve()
就好了。
2.3 解题步骤
在现在版本的 Linux 内核修改 uid,需要通过prepare_creds()
和commit_creds()
两步参考2。这两个函数的地址存在/proc/kallsyms
:
|
|
参考 @acama 的版本 参考3写了一个( @acama 的版本prepare_creds()
之后直接就commit_creds()
, 这估计只在老版本可以).prepare_creds()
返回的结构体定义可以看参考4.
|
|
这个生成的指令是不能用原先的 223 号系统调用直接写进内存的,所以我准备了一个真正的write-anything-anywhere
的跳板:
|
|
先把 waa 写进内存,然后把 cred 写进内存。至于写到哪里,我随手写了两个地址: 0x83f5cafe, 0x83f6beee.
2.4 exp集锦
参考: https://cubarco.org/blog/2015/12/writeup-pwnable-syscall/
|
|
参考: http://w0lfzhang.me/2017/04/27/pwnable-syscall/
|
|