基本知识
TODO
基本数据结构
nft_table:https://elixir.bootlin.com/linux/v5.10/source/include/net/netfilter/nf_tables.h#L1074
可以看到这里的chains、sets、objectst都只是双链表,甚至没有指明类型,因此笔者猜测这个table上应该是可以挂上不同类型的set的,🤔。
nft_set:https://elixir.bootlin.com/linux/v5.10/source/include/net/netfilter/nf_tables.h#L430
nft_set_elem:https://elixir.bootlin.com/linux/v5.10/source/include/net/netfilter/nf_tables.h#L234
实操
这里先根据这个exp来学习:https://github.com/randorisec/CVE-2022-34918-LPE-PoC/blob/main/src/nf_tables.c ,这里主要提供了三个函数接口:
void create_table(int sock, const char *name);
void create_set(int sock, const char *set_name, uint32_t set_keylen, uint32_t data_len, const char *table_name, uint32_t id);
void add_elem_to_set(int sock, const char *set_name, uint32_t set_keylen, const char *table_name, uint32_t id, uint32_t data_len, uint8_t *data) ;
让我们逐一来调用并调试,😊!
准备工作
将https://github.com/randorisec/CVE-2022-34918-LPE-PoC的`src`目录下的`nf_tables.c`、`netlink.c`两个c文件,以及其`inc`目录下的`log.h 、
nf_tables.h、
netlink.h三个头文件和我们的
exp.c`文件放到一起,然后通过如下命令进行编译:
gcc ./exp.c ./nf_tables.c ./netlink.c -o test --static -masm=intel |
就可以调用这三个接口函数了!😊
首先我们记得在自己的exp.c
中创建新的namespace,然后创建一个socket返回文件描述符:
|
创建table
之后调用create_table创建一个table:
|
table的分配点在这里,函数为nf_tables_newtable
:https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nf_tables_api.c#L1028
其cache是kmalloc-512:(笔者这个版本是5.10,没有cg隔离,但是GFP_KERNEL无所谓)
table_name有一个任意大小的分配:
创建set
然后是调用create_set
函数给我们的table添加一个set:
int main(){ |
第一个参数是sock;
第二个参数是新创建的set的名字;
第三个参数是keylen;
第四个参数是datalen;
第五个参数是table的名字,我们的set要挂到table上;
第六个参数是id,可能是以后用于在table上查找set用的;
set的添加位于这个函数:https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nf_tables_api.c#L4103
首先是检查一堆flag,以及desc,这里就涉及我们传入的klen、dlen:
set的分配在这里:https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nf_tables_api.c#L4265
可以看到这个分配来自kmalloc,然后size和udlen是两个变量,经过调试发现这个sizeof(*set)是0x100;size是0xe0;(这里如何改变长度以后再看)
之后会有如下几个操作:
首先拷贝udata数据,然后初始化set的各成员,调用ops->init(这个在CVE-2022-34918中可能会用到,这里先按下不表),之后nft_trans_set_add干了啥咱也不知道,反正最后会将这个新创建的set挂到table的sets链表上,table的引用计数use+1。
expr的分配
源码就在创建set的末尾:https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nf_tables_api.c#L4281
gc_init调试结果如下:
创建elem
相应代码如下:
int main(){ |
这里有个巨大的坑:
在add_elem_to_set中,使用的是set_str8_attr来设置set_name,这样子就会在netlink的消息中生成一个固定的8字节的字符串,netlink的消息中会有4字节的元数据,后边跟上具体内容的,然后这里固定是8,总长度就是12,最后解包出来得到8,然后和6字节的“my_set”作比较就会出错。
虽然create_set中也是用的同样的函数:
但是问题是在内核中创建set的时候会以字符串的角度copy这个name,然后不会管你netlink里的长度,遇到00就不复制了。🤔
调用链如下:
nf_tables_newsetelem
-> nft_add_set_elem
->``
tmpl
首先关注tmpl,其结构定义如下:
其相关的函数有nft_set_ext_add_length
和nft_set_ext_add
:
len代表具体数据的长度,nft_set_ext_types[id].len
则代表
add_length要加元数据+content,而add则只加一个固定长度;
然后在nft_add_set_elem
函数中就会添加tmpl:
NFT_SET_EXT_FLAGS (可选,根据flags) |
总之就是每添加一个扩展元素,tmpl就要在对应的offset数组中记录当前长度(也就是这个新增的元素在ext中的偏移),然后更新tmpl的总长度(增加元素的len,和types数组中的len)
elem
elem的创建位置在这里:https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nf_tables_api.c#L5069
ext结构体定义如下:
参考
https://github.com/randorisec/CVE-2022-34918-LPE-PoC/blob/main/src/nf_tables.c