流程
用户态 —-> 内核态
I.切换GS段寄存器
通过 `swapgs` 切换 GS 段寄存器,将 GS 寄存器值和一个特定位置的值进行交换,目的是保存 GS 值,同时将该位置的值作为内核执行时的 GS 值使用
II.保存用户态栈帧信息
将当前栈顶(用户空间栈顶)记录在 CPU 独占变量区域里,将 CPU 独占区域里记录的内核栈顶放入 rsp/esp
III.保存用户态寄存器信息
通过 push 保存各寄存器值到栈上,以便后续“着陆”回用户态
IV.通过汇编指令判断是否为32位
V.控制权转交内核,执行系统调用
在这里用到一个全局函数表`sys_call_table`,其中保存着系统调用的函数指针
内核态 —-> 用户态
由内核态重新“着陆”回用户态只需要恢复用户空间信息即可:
- `swapgs`指令恢复用户态GS寄存器
- `sysretq`或者`iretq`恢复到用户空间
方式一
进程权限凭证结构体cred
,它用于 保存每个进程的权限定义如下:
struct cred {
atomic_t usage;
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
kuid_t uid; /* 实际用户id */
kgid_t gid; /* 实际用户组id */
kuid_t suid; /* 保存的用户uid */
kgid_t sgid; /* 保存的用户组gid */
kuid_t euid; /* 真正有效的用户id */
kgid_t egid; /* 真正有效的用户组id */
kuid_t fsuid;
kgid_t fsgid;
unsigned securebits; /* 安全管理标识;用来控制凭证的操作与继承 */
kernel_cap_t cap_inheritable; /* execve时可以继承的权限 */
kernel_cap_t cap_permitted; /* 可以(通过capset)赋予cap_effective的权限 */
kernel_cap_t cap_effective; /* 进程实际使用的权限 */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
//。。。。。。
};
} __randomize_layout;
真实用户ID(real UID):标识一个进程启动时的用户ID
保存用户ID(saved UID):标识一个进程最初的有效用户ID
有效用户ID(effective UID):标识一个进程正在运行时所属的用户ID,一个进程在运行途中是可以改变自己所属用户的,因而权限机制也是通过有效用户ID进行认证的,内核通过 euid 来进行特权判断;为了防止用户一直使用高权限,当任务完成之后,euid 会与 suid 进行交换,恢复进程的有效权限
文件系统用户ID(UID for VFS ops):标识一个进程创建文件时进行标识的用户ID
我们知道root
身份的id
和组id
都是0表示最高权限,一般我们使用的管理员用户就是1000
那么我们在内核创建一个新的进程时,改变进程的cred结构体
的uid和gid
都为0也就完成了提权到root
方法二
struct cred* prepare_kernel_cred(struct task_struct* daemon)
:该函数用以拷贝一个进程的cred结构体,并返回一个新的cred结构体,需要注意的是daemon
参数应为有效的进程描述符地址或NULL
int commit_creds(struct cred *new)
:该函数用以将一个新的cred
结构体应用到进程
struct cred *prepare_kernel_cred(struct task_struct *daemon)
{
const struct cred *old;
struct cred *new;
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;
kdebug("prepare_kernel_cred() alloc %p", new);
if (daemon)
old = get_task_cred(daemon);
else //为NULL则从init_crea初创建一个默认的crea结构体
old = get_cred(&init_cred);
validate_creds(old);
//。。。。
return NULL;
}
int commit_creds(struct cred *new)
{
struct task_struct *task = current;
const struct cred *old = task->real_cred;
//。。。
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;
smp_wmb();
}
/* alter the thread keyring */
if (!uid_eq(new->fsuid, old->fsuid))
key_fsuid_changed(task);
if (!gid_eq(new->fsgid, old->fsgid))
key_fsgid_changed(task);
/* 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;
}
那么在内核空间执行commit_creds(prepare_kernel_cred(NULL))
则可以将该进程提权root