├── .gitignore ├── Makefile ├── README.md ├── consts.h ├── device_handlers.c ├── device_handlers.h ├── elf.c ├── elf.h ├── file.c ├── file.h ├── ioctl.c ├── ioctl.h ├── ioctl_sender ├── main.cpp └── send_ioctl ├── main.c ├── so_injector.c ├── so_injector.h ├── so_shellcode_loader.S ├── so_shellcode_loader.h ├── so_to_inject └── main.c ├── test_program └── sleeper.cpp ├── utils.c └── utils.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # Kernel Development settings 55 | /.vscode 56 | *.sh -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | obj-m := kernel_injector.o 2 | kernel_injector-y := main.o device_handlers.o ioctl.o so_injector.o file.o utils.o elf.o so_shellcode_loader.o 3 | 4 | KERNELDIR ?= ~/workspace/buildroot-2020.02.4/output/build/linux-4.19.91 5 | PWD := $(shell pwd) 6 | 7 | all: debug 8 | 9 | release: 10 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 11 | 12 | ccflags-y := -g -Og -O0 13 | debug: 14 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 15 | 16 | clean: 17 | rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions debug 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linux-kernel-so-injector 2 | ## TL;DR 3 | Linux kernel mode to user mode so injection 4 | 5 | Tested on linux kernel version: 4.19.91. Current version depends on libc, but it's easy to change it. 6 | 7 | Inject shared library to target process from the kernel 8 | 9 | ## How it works 10 | The injection process is divided into several stages: 11 | 12 | 1. Send SIGSTOP to target process 13 | 2. Find free space for our shellcode (any R^X pages) using /proc/PID/maps 14 | 3. Find libc address using /proc/PID/maps 15 | 4. Parse libc elf at runtime to find __libc_dlopen_mode 16 | 5. Get the target process rip register 17 | 6. Build our shellcode (get_shellcode function) with the correct address of: 18 | * So file path 19 | * Previous rip register address (in order to reconsturct the running of the process after loading the so from our shellcode) 20 | * __libc_dlopen_mode address (using this function in libc, we will load our so) 21 | 7. Write to target process memory the so file path 22 | 8. Write the shellcode to target process memory 23 | 9. Set target process rip register to the shellcode address 24 | 10. Send SIGCONT to target process 25 | 26 | The whole process described above happens at the kernel module. 27 | The only things that the kernel module needs are: target pid, so file path. 28 | 29 | ## Limitations 30 | * Currently there is no support for syscalls from the type that need EINTR or restart_syscall when stopped by signal. 31 | * Currently implemented only for x86_64. 32 | * This code isn't robust or fully tested. Therefore, you should expect bugs to occur under certain conditions when process getting non-stop signals or other edge cases. 33 | * The shellcode and so path won't cleaned because I didn't have enough time to implement it from different kernel thread, but there is a commit which all the cleanup code available. However, the kernel thread isn't and without the creation of new kernel thread it will stick the injection. 34 | * The reconstruction isn't fully (r15 isn't restored). 35 | 36 | All of the limitations which mentioned above could be solved. 37 | -------------------------------------------------------------------------------- /consts.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define MAYJOR_NUMBER 510 4 | 5 | #define DEVICE_NAME "Injector" 6 | 7 | #define MAX_SYMBOL_NAME 256 8 | 9 | #define SYSCALL_OPCODE_SIZE 2 10 | 11 | enum RETURN_CODE{ 12 | SUCCESS, 13 | INVALID_PARAMETER, 14 | INVALID_PID, 15 | SIGSTOP_FAILED, 16 | GET_USER_PAGE_REMOTE_FAILED, 17 | REMOTE_MEMORY_ALLOC_FAILED, 18 | GET_DYNAMIC_SECTION_FAILED, 19 | }; -------------------------------------------------------------------------------- /device_handlers.c: -------------------------------------------------------------------------------- 1 | #include "device_handlers.h" 2 | 3 | #include 4 | 5 | #include "ioctl.h" 6 | #include "consts.h" 7 | 8 | int device_open(struct inode *inode, struct file *file) { 9 | return SUCCESS; 10 | } 11 | 12 | int device_close(struct inode *inode, struct file *file) { 13 | return SUCCESS; 14 | } 15 | 16 | long device_ioctl(struct file *f, unsigned int cmd, unsigned long arg) { 17 | long status = SUCCESS; 18 | printk(KERN_ALERT "ioctl handler\n"); 19 | switch (cmd) 20 | { 21 | case IOCTL_INJECT_SHARED_OBJECT: 22 | printk(KERN_ALERT "Call so ioctl handler\n"); 23 | status = inject_so_ioctl_handler(arg); 24 | break; 25 | 26 | default: 27 | status = SUCCESS; 28 | break; 29 | } 30 | return INVALID_PARAMETER; 31 | } 32 | -------------------------------------------------------------------------------- /device_handlers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | int device_open(struct inode *inode, struct file *file); 6 | 7 | int device_close(struct inode *inode, struct file *file); 8 | 9 | long device_ioctl(struct file *f, unsigned int cmd, unsigned long arg); 10 | -------------------------------------------------------------------------------- /elf.c: -------------------------------------------------------------------------------- 1 | #include "elf.h" 2 | 3 | #include 4 | 5 | #include "utils.h" 6 | #include "consts.h" 7 | 8 | int get_dynamic_section(struct task_struct* task, unsigned long number_of_entries, unsigned long module_base_address, 9 | unsigned long offset, unsigned long* dynamic_tables) { 10 | ssize_t result; 11 | Elf64_Phdr elf_section; 12 | size_t i; 13 | for (i = 0; i < number_of_entries; i++) { 14 | result = mem_read(task, (char*)&elf_section, sizeof(Elf64_Phdr), offset + (sizeof(Elf64_Phdr) * i)); 15 | if (sizeof(Elf64_Phdr) != result) { 16 | return GET_DYNAMIC_SECTION_FAILED; 17 | } 18 | if (PT_DYNAMIC == elf_section.p_type) { 19 | *dynamic_tables = module_base_address + elf_section.p_vaddr; 20 | return SUCCESS; 21 | } 22 | } 23 | return GET_DYNAMIC_SECTION_FAILED; 24 | } 25 | 26 | void* get_dynamic_table(struct task_struct* task, size_t table_index, unsigned long dynamic_tables){ 27 | Elf64_Dyn dynamic_table; 28 | ssize_t result; 29 | size_t i = 0; 30 | while (true) { 31 | result = mem_read(task, (char*)&dynamic_table, sizeof(Elf64_Dyn), dynamic_tables + (sizeof(Elf64_Dyn) * i)); 32 | if (sizeof(Elf64_Dyn) != result) { 33 | return NULL; 34 | } 35 | if (table_index == dynamic_table.d_tag) { 36 | return (void*)dynamic_table.d_un.d_ptr; 37 | } 38 | ++i; 39 | } 40 | return NULL; 41 | } 42 | 43 | void* find_symbol(struct task_struct* task, unsigned long symbol_string_table, unsigned long symbol_table, const char* symbol_name) { 44 | Elf64_Sym symbol; 45 | ssize_t result; 46 | size_t i = 0; 47 | char found_symbol_name[MAX_SYMBOL_NAME] = { 0 }; 48 | size_t symbol_len = strlen(symbol_name); 49 | while (true) { 50 | result = mem_read(task, (char*)&symbol, sizeof(Elf64_Sym), symbol_table + (sizeof(Elf64_Sym) * i)); 51 | if (sizeof(Elf64_Sym) != result) { 52 | return NULL; 53 | } 54 | result = mem_read(task, found_symbol_name, symbol_len, symbol_string_table + symbol.st_name); 55 | if (symbol_len != result) { 56 | return NULL; 57 | } 58 | if (0 == strncmp(symbol_name, found_symbol_name, symbol_len)) { 59 | return (void*)symbol.st_value; 60 | } 61 | ++i; 62 | memset(found_symbol_name, 0, MAX_SYMBOL_NAME); 63 | } 64 | return NULL; 65 | } 66 | 67 | void* get_symbol_address(struct task_struct* task, void* module_base_address, const char* symbol_name) { 68 | Elf64_Ehdr elf_header; 69 | unsigned long dynamic_tables; 70 | unsigned long symbol_string_table; 71 | unsigned long symbol_table; 72 | void* symbol_address; 73 | ssize_t result; 74 | unsigned long offset = (unsigned long)module_base_address; 75 | 76 | result = mem_read(task, (char*)&elf_header, sizeof(Elf64_Ehdr), offset); 77 | if (sizeof(Elf64_Ehdr) != result) { 78 | return NULL; 79 | } 80 | offset += elf_header.e_phoff; 81 | result = get_dynamic_section(task, elf_header.e_phnum, (unsigned long)module_base_address, offset, &dynamic_tables); 82 | if (SUCCESS != result) { 83 | return NULL; 84 | } 85 | 86 | symbol_string_table = (unsigned long)get_dynamic_table(task, DT_STRTAB, dynamic_tables); 87 | symbol_table = (unsigned long)get_dynamic_table(task, DT_SYMTAB, dynamic_tables); 88 | 89 | if (NULL == (void*)symbol_table || NULL == (void*)symbol_string_table) { 90 | return NULL; 91 | } 92 | symbol_address = find_symbol(task, symbol_string_table, symbol_table, symbol_name); 93 | if (NULL == symbol_address) { 94 | return NULL; 95 | } 96 | return symbol_address + (unsigned long)module_base_address; 97 | } -------------------------------------------------------------------------------- /elf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | void* get_symbol_address(struct task_struct* task, void* module_base_address, const char* symbol_name); -------------------------------------------------------------------------------- /file.c: -------------------------------------------------------------------------------- 1 | #include "file.h" 2 | 3 | struct file *file_open(const char *path, int flags, int rights) { 4 | struct file *filp = NULL; 5 | int err = 0; 6 | 7 | filp = filp_open(path, flags, rights); 8 | if (IS_ERR(filp)) { 9 | err = PTR_ERR(filp); 10 | return NULL; 11 | } 12 | return filp; 13 | } 14 | 15 | void file_close(struct file *file) { 16 | filp_close(file, NULL); 17 | } 18 | 19 | int file_read(struct file *file, unsigned long long offset, unsigned char *data, unsigned int size) { 20 | int ret; 21 | ret = kernel_read(file, data, size, &offset); 22 | return ret; 23 | } 24 | 25 | int file_write(struct file *file, unsigned long long offset, unsigned char *data, unsigned int size) { 26 | int ret; 27 | ret = kernel_write(file, data, size, &offset); 28 | return ret; 29 | } 30 | 31 | int file_sync(struct file *file) { 32 | vfs_fsync(file, 0); 33 | return 0; 34 | } -------------------------------------------------------------------------------- /file.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct file* file_open(const char *path, int flags, int rights); 7 | 8 | void file_close(struct file *file); 9 | 10 | int file_read(struct file *file, unsigned long long offset, unsigned char *data, unsigned int size); 11 | 12 | int file_write(struct file *file, unsigned long long offset, unsigned char *data, unsigned int size); 13 | 14 | int file_sync(struct file *file); -------------------------------------------------------------------------------- /ioctl.c: -------------------------------------------------------------------------------- 1 | #include "ioctl.h" 2 | 3 | #include 4 | 5 | #include "consts.h" 6 | #include "so_injector.h" 7 | 8 | int inject_so_ioctl_handler(unsigned long arg) { 9 | int status; 10 | SoInjectionParameters parameters; 11 | status = inject_so_ioctl_parser(arg, ¶meters); 12 | if (SUCCESS != status) { 13 | return status; 14 | } 15 | status = inject_so(¶meters); 16 | if (SUCCESS != status) { 17 | kfree(parameters.so_path); 18 | return status; 19 | } 20 | kfree(parameters.so_path); 21 | return SUCCESS; 22 | } -------------------------------------------------------------------------------- /ioctl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define IOCTL_INJECT_SHARED_OBJECT 1337 4 | 5 | int inject_so_ioctl_handler(unsigned long arg); -------------------------------------------------------------------------------- /ioctl_sender/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include /* open */ 6 | #include /* exit */ 7 | #include /* ioctl */ 8 | 9 | #define DEVICE_NAME "/dev/Injector" 10 | 11 | typedef struct { 12 | int pid; 13 | const char* so_path; 14 | size_t so_path_size; 15 | } SoInjectionParameters; 16 | 17 | int main() { 18 | const char* so_path = "/root/libhello.so"; 19 | int fd = open(DEVICE_NAME, 0); 20 | if (fd < 0) { 21 | printf("Can't open device file: %s\n", DEVICE_NAME); 22 | return 1; 23 | } 24 | SoInjectionParameters parameters; 25 | std::cout << "Enter the pid: " << std::endl; 26 | std::cin >> parameters.pid; 27 | parameters.so_path = so_path; 28 | parameters.so_path_size = strlen(so_path); 29 | ioctl(fd, 1337, (void*)¶meters); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /ioctl_sender/send_ioctl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rhydon1337/linux-kernel-so-injector/f9e382f5a8e7535906da04704e86a32b53f9429f/ioctl_sender/send_ioctl -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "device_handlers.h" 15 | #include "consts.h" 16 | 17 | MODULE_LICENSE("GPL"); 18 | MODULE_AUTHOR("Rhydon"); 19 | 20 | struct file_operations fops = { 21 | .unlocked_ioctl = device_ioctl, 22 | .open = device_open, 23 | .release = device_close, 24 | }; 25 | 26 | dev_t g_first_device; 27 | struct cdev g_cdev; 28 | struct class* g_cl; 29 | 30 | static int driver_initialization(void) 31 | { 32 | int ret_val; 33 | printk(KERN_INFO "hello...\n"); 34 | ret_val = register_chrdev(MAYJOR_NUMBER, DEVICE_NAME, &fops); 35 | if (ret_val < 0) { 36 | printk (KERN_ERR "Sorry, registering the character device failed with %d\n", ret_val); 37 | return ret_val; 38 | } 39 | ret_val = alloc_chrdev_region( &g_first_device, 0, 1, DEVICE_NAME); 40 | if( 0 > ret_val) 41 | { 42 | printk( KERN_ALERT "Device Registration failed\n" ); 43 | return -1; 44 | } 45 | if ( (g_cl = class_create( THIS_MODULE, "chardev" ) ) == NULL ) 46 | { 47 | printk( KERN_ALERT "Class creation failed\n" ); 48 | unregister_chrdev_region( g_first_device, 1 ); 49 | return -1; 50 | } 51 | 52 | if( device_create(g_cl, NULL, g_first_device, NULL, DEVICE_NAME) == NULL ) 53 | { 54 | printk( KERN_ALERT "Device creation failed\n" ); 55 | class_destroy(g_cl); 56 | unregister_chrdev_region(g_first_device, 1 ); 57 | return -1; 58 | } 59 | 60 | cdev_init(&g_cdev, &fops); 61 | 62 | if(cdev_add( &g_cdev, g_first_device, 1 ) == -1) 63 | { 64 | printk( KERN_ALERT "Device addition failed\n" ); 65 | device_destroy(g_cl, g_first_device); 66 | class_destroy(g_cl); 67 | unregister_chrdev_region(g_first_device, 1); 68 | return -1; 69 | } 70 | return SUCCESS; 71 | } 72 | 73 | static void driver_exit(void) 74 | { 75 | cdev_del(&g_cdev); 76 | device_destroy(g_cl, g_first_device); 77 | class_destroy(g_cl); 78 | unregister_chrdev_region(g_first_device, 1); 79 | printk(KERN_ALERT "Device unregistered\n"); 80 | } 81 | 82 | module_init(driver_initialization); 83 | module_exit(driver_exit); 84 | -------------------------------------------------------------------------------- /so_injector.c: -------------------------------------------------------------------------------- 1 | #include "so_injector.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "consts.h" 17 | #include "utils.h" 18 | #include "elf.h" 19 | #include "so_shellcode_loader.h" 20 | 21 | #define KERNEL_PRIV 1 22 | 23 | int inject_so_ioctl_parser(unsigned long arg, SoInjectionParameters* parameters) { 24 | unsigned long status; 25 | void* so_path; 26 | status = copy_from_user((void*)parameters, (void*)arg, sizeof(SoInjectionParameters)); 27 | so_path = parameters->so_path; 28 | // include the null-terminator 29 | parameters->so_path = kmalloc(parameters->so_path_size + 1, GFP_KERNEL); 30 | memset(parameters->so_path, 0, parameters->so_path_size + 1); 31 | status = copy_from_user(parameters->so_path, so_path, parameters->so_path_size); 32 | parameters->so_path_size++; 33 | if (SUCCESS != status) { 34 | return -EFAULT; 35 | } 36 | return SUCCESS; 37 | } 38 | 39 | 40 | int inject_so(SoInjectionParameters* parameters) { 41 | int status; 42 | bool is_stopped; 43 | struct task_struct* target_task; 44 | struct pid* pid_struct; 45 | void* free_addr; 46 | void* libc_address; 47 | void* symbol_address; 48 | void* shellcode; 49 | size_t shellcode_size; 50 | bool came_from_syscall; 51 | printk(KERN_INFO "Start injecting the so to pid %d\n", parameters->pid); 52 | 53 | // find the target process 54 | pid_struct = find_get_pid(parameters->pid); 55 | target_task = pid_task(pid_struct, PIDTYPE_TGID); 56 | if (NULL == target_task) { 57 | return INVALID_PID; 58 | } 59 | 60 | // stop the target process 61 | status = send_sig(SIGSTOP, target_task, KERNEL_PRIV); 62 | if (0 > status) { 63 | printk(KERN_INFO "Unable to stop the process, pid %d\n", parameters->pid); 64 | return SIGSTOP_FAILED; 65 | } 66 | is_stopped = true; 67 | printk(KERN_INFO "The process stopped successfully, pid %d\n", parameters->pid); 68 | 69 | // find free space for writing the so name 70 | free_addr = find_executable_space(parameters->pid); 71 | if (NULL == free_addr) { 72 | printk(KERN_INFO "Unable to find free space in the process, pid %d\n", parameters->pid); 73 | goto release_process; 74 | } 75 | printk(KERN_INFO "The free address is: %lx\n", (unsigned long)free_addr); 76 | 77 | // find libc for the injection 78 | libc_address = find_lib_address(parameters->pid, "libc-"); 79 | if (NULL == libc_address) { 80 | printk(KERN_INFO "Unable to find libc in the process, pid %d\n", parameters->pid); 81 | goto release_process; 82 | } 83 | printk(KERN_INFO "The address of the found libc is: %lx\n", (unsigned long)libc_address); 84 | 85 | // find __libc_dlopen_mode for loading the injected so 86 | symbol_address = get_symbol_address(target_task, libc_address, "__libc_dlopen_mode"); 87 | if (NULL == symbol_address) 88 | { 89 | printk(KERN_INFO "Unable to find the symbol address, pid %d\n", parameters->pid); 90 | goto release_process; 91 | } 92 | printk(KERN_INFO "The address of the symbol is: %lx\n", (unsigned long)symbol_address); 93 | 94 | // did we stopped from a system call? 95 | if (syscall_get_nr(target_task, task_pt_regs(target_task)) >= 0){ 96 | came_from_syscall = true; 97 | } 98 | else { 99 | came_from_syscall = false; 100 | } 101 | 102 | // get the shellcode parsed and patched to correct addresses 103 | shellcode = get_shellcode(&shellcode_size, task_pt_regs(target_task), (unsigned long)free_addr, (unsigned long)symbol_address, came_from_syscall); 104 | if (NULL == shellcode) { 105 | printk(KERN_INFO "Unable to get the shellcode\n"); 106 | goto release_process; 107 | } 108 | 109 | // write to so path to process memory include the null-terminator 110 | if(parameters->so_path_size != mem_write(target_task, parameters->so_path, parameters->so_path_size, (unsigned long)free_addr)) { 111 | printk(KERN_INFO "Unable to write the so path to process memory, pid %d\n", parameters->pid); 112 | goto release_shellcode; 113 | } 114 | 115 | // write to so path to process memory include the null-terminator 116 | if(shellcode_size != mem_write(target_task, shellcode, shellcode_size, (unsigned long)free_addr + parameters->so_path_size)) { 117 | printk(KERN_INFO "Unable to write the shellcode to process memory, pid %d\n", parameters->pid); 118 | goto release_shellcode; 119 | } 120 | 121 | // did we stopped from a system call? 122 | if (came_from_syscall){ 123 | task_pt_regs(target_task)->ip = (unsigned long)free_addr + parameters->so_path_size + 2; 124 | } 125 | else { 126 | task_pt_regs(target_task)->ip = (unsigned long)free_addr + parameters->so_path_size; 127 | } 128 | 129 | // continue the target process in order to execute our so shellcode loader 130 | send_sig(SIGCONT, target_task, KERNEL_PRIV); 131 | is_stopped = false; 132 | 133 | release_shellcode: 134 | kfree(shellcode); 135 | release_process: 136 | if (is_stopped) { 137 | send_sig(SIGCONT, target_task, KERNEL_PRIV); 138 | } 139 | return status; 140 | } -------------------------------------------------------------------------------- /so_injector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | typedef struct { 5 | int pid; 6 | char* so_path; 7 | size_t so_path_size; 8 | } SoInjectionParameters; 9 | 10 | int inject_so_ioctl_parser(unsigned long arg, SoInjectionParameters* parameters); 11 | 12 | int inject_so(SoInjectionParameters* parameters); 13 | 14 | int write_process_memory_page(struct task_struct* task, void* user_address, void* kernel_address, int len); 15 | 16 | int write_process_memory(struct task_struct* task, void* user_address, void* kernel_address, int len); 17 | -------------------------------------------------------------------------------- /so_shellcode_loader.S: -------------------------------------------------------------------------------- 1 | #pragma GCC push_options 2 | #pragma GCC optimize ("O0") 3 | 4 | #define MAGIC_SO_PATH 0x1234567890abcdea 5 | #define MAGIC_PREV_IP 0x1234567890abcdeb 6 | #define MAGIC_SO_LOAD_FUNCTION 0x1234567890abcded 7 | 8 | #define RTLD_NOW 0x00002 9 | #define __RTLD_DLOPEN 0x80000000 10 | 11 | .text 12 | .align 16 13 | .global shellcode 14 | .global end_of_shellcode 15 | shellcode: 16 | # call __libc_dlopen_mode and pssing parameters 17 | nop 18 | nop 19 | nop 20 | nop 21 | nop 22 | pushq %rax 23 | pushq %rbx 24 | pushq %rcx 25 | pushq %rdx 26 | pushq %rdi 27 | pushq %rsi 28 | pushq %rsp 29 | pushq %rbp 30 | pushq %r8 31 | pushq %r9 32 | pushq %r10 33 | pushq %r11 34 | pushq %r12 35 | pushq %r13 36 | pushq %r14 37 | pushq %r15 38 | pushfq 39 | 40 | movq %rsp, %rbx 41 | andq $0xFFFFFFFFFFFFFFF0, %rsp 42 | 43 | movq $MAGIC_SO_LOAD_FUNCTION, %rax 44 | movq $MAGIC_SO_PATH, %rdi 45 | mov $(RTLD_NOW | __RTLD_DLOPEN), %rsi 46 | callq *%rax 47 | 48 | movq %rbx, %rsp 49 | 50 | popfq 51 | popq %r15 52 | popq %r14 53 | popq %r13 54 | popq %r12 55 | popq %r11 56 | popq %r10 57 | popq %r9 58 | popq %r8 59 | popq %rbp 60 | popq %rsp 61 | popq %rsi 62 | popq %rdi 63 | popq %rdx 64 | popq %rcx 65 | popq %rbx 66 | popq %rax 67 | 68 | movq $MAGIC_PREV_IP, %r15 69 | jmpq *%r15 70 | 71 | end_of_shellcode: 72 | .end 73 | 74 | 75 | #pragma GCC pop_options -------------------------------------------------------------------------------- /so_shellcode_loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern int shellcode(void); 4 | extern int end_of_shellcode(void); -------------------------------------------------------------------------------- /so_to_inject/main.c: -------------------------------------------------------------------------------- 1 | // C program to demonstrate working of 2 | // __attribute__((constructor)) and 3 | // __attribute__((destructor)) 4 | #include 5 | #include 6 | 7 | // Assigning functions to be executed before and 8 | // after main() 9 | void __attribute__((constructor)) calledFirst(); 10 | void __attribute__((destructor)) calledLast(); 11 | 12 | void main() { 13 | printf("\nI am in main\n"); 14 | } 15 | 16 | // This function is assigned to execute before 17 | // main using __attribute__((constructor)) 18 | void calledFirst() 19 | { 20 | printf("\nI am called first\n"); 21 | system("echo a>/root/a.txt"); 22 | } 23 | 24 | // This function is assigned to execute after 25 | // main using __attribute__((destructor)) 26 | void calledLast() 27 | { 28 | printf("\nI am called last\n"); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /test_program/sleeper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include /* open */ 6 | #include /* exit */ 7 | #include /* ioctl */ 8 | 9 | int main() { 10 | while (true){ 11 | std::cout << "here" << std::endl; 12 | sleep(1); 13 | } 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "file.h" 9 | #include "so_shellcode_loader.h" 10 | 11 | void* find_lib_address(pid_t pid, char* library) { 12 | struct file* fp; 13 | char filename[30]; 14 | char data[850]; 15 | char* line; 16 | unsigned long addr; 17 | int offset = 0; 18 | char* substring; 19 | int size = 0; 20 | sprintf(filename, "/proc/%d/maps", pid); 21 | fp = file_open(filename, O_RDONLY, 0); 22 | if(NULL == fp) { 23 | return NULL; 24 | } 25 | while(true) { 26 | size = file_read(fp, offset, data, 850); 27 | if (0 == size) { 28 | file_close(fp); 29 | return NULL; 30 | } 31 | substring = strstr(data, "\n"); 32 | if (NULL == substring) { 33 | substring = data + size; 34 | } 35 | 36 | size = substring - data; 37 | line = kmalloc(size + 1, GFP_KERNEL); 38 | strncpy(line, data, size); 39 | sscanf(line, "%lx-%*x %*s %*s %*s %*d", &addr); 40 | if(strstr(line, library) != NULL) { 41 | kfree(line); 42 | break; 43 | } 44 | kfree(line); 45 | offset += size + 1; 46 | } 47 | file_close(fp); 48 | return (void*)addr; 49 | } 50 | 51 | void* find_executable_space(pid_t pid) { 52 | struct file* fp; 53 | char filename[30]; 54 | char data[850]; 55 | char* line; 56 | unsigned long addr; 57 | char str[20]; 58 | char perms[5]; 59 | int offset = 0; 60 | char* substring; 61 | int size = 0; 62 | sprintf(filename, "/proc/%d/maps", pid); 63 | fp = file_open(filename, O_RDONLY, 0); 64 | if(NULL == fp) { 65 | return NULL; 66 | } 67 | while(true) { 68 | size = file_read(fp, offset, data, 850); 69 | if (0 == size) { 70 | file_close(fp); 71 | return NULL; 72 | } 73 | substring = strstr(data, "\n"); 74 | if (NULL == substring) { 75 | substring = data + size; 76 | } 77 | 78 | size = substring - data; 79 | line = kmalloc(size + 1, GFP_KERNEL); 80 | strncpy(line, data, size); 81 | sscanf(line, "%lx-%*x %s %*s %s %*d", &addr, perms, str); 82 | kfree(line); 83 | if(strstr(perms, "x") != NULL) { 84 | break; 85 | } 86 | offset += size + 1; 87 | } 88 | file_close(fp); 89 | return (void*)addr; 90 | } 91 | 92 | ssize_t mem_rw(struct task_struct *task, char *buf, size_t count, loff_t *ppos, int write) { 93 | struct mm_struct *mm = task->mm; 94 | unsigned long addr = *ppos; 95 | ssize_t copied; 96 | char *page; 97 | unsigned int flags; 98 | 99 | if (!mm) 100 | return 0; 101 | 102 | page = (char *)get_zeroed_page(GFP_KERNEL); 103 | if (!page) 104 | return -ENOMEM; 105 | 106 | copied = 0; 107 | if (!atomic_inc_not_zero(&mm->mm_users)) 108 | goto free; 109 | 110 | /* Maybe we should limit FOLL_FORCE to actual ptrace users? */ 111 | flags = FOLL_FORCE; 112 | if (write) 113 | flags |= FOLL_WRITE; 114 | 115 | while (count > 0) { 116 | int this_len = min_t(int, count, PAGE_SIZE); 117 | 118 | if (write && NULL == memcpy(page, buf, this_len)) { 119 | copied = -EFAULT; 120 | break; 121 | } 122 | 123 | this_len = access_process_vm(task, addr, page, this_len, flags); 124 | if (!this_len) { 125 | if (!copied) 126 | copied = -EIO; 127 | break; 128 | } 129 | 130 | if (!write && NULL == memcpy(buf, page, this_len)) { 131 | copied = -EFAULT; 132 | break; 133 | } 134 | 135 | buf += this_len; 136 | addr += this_len; 137 | copied += this_len; 138 | count -= this_len; 139 | } 140 | *ppos = addr; 141 | 142 | mmput(mm); 143 | free: 144 | free_page((unsigned long) page); 145 | return copied; 146 | } 147 | 148 | ssize_t mem_read(struct task_struct* task, char *buf, size_t count, unsigned long pos) { 149 | loff_t ppos = pos; 150 | return mem_rw(task, buf, count, &ppos, 0); 151 | } 152 | 153 | ssize_t mem_write(struct task_struct* task, char *buf, size_t count, unsigned long pos) { 154 | loff_t ppos = pos; 155 | return mem_rw(task, buf, count, &ppos, 1); 156 | } 157 | 158 | void* get_shellcode(size_t* shellcode_size, struct pt_regs* registers, unsigned long so_library_name, unsigned long load_so_function, bool came_from_syscall) { 159 | void* shellcode_patched; 160 | unsigned long ip; 161 | if (came_from_syscall) { 162 | ip = registers->ip - 2; 163 | } 164 | else { 165 | ip = registers->ip; 166 | } 167 | *shellcode_size = (unsigned long)end_of_shellcode - (unsigned long)shellcode; 168 | if (0 >= *shellcode_size){ 169 | return NULL; 170 | } 171 | shellcode_patched = kmalloc(*shellcode_size, GFP_KERNEL); 172 | memcpy(shellcode_patched, shellcode, *shellcode_size); 173 | memcpy(shellcode_patched + 39, (void*)&load_so_function, sizeof(unsigned long)); 174 | memcpy(shellcode_patched + 49, (void*)&so_library_name, sizeof(unsigned long)); 175 | memcpy(shellcode_patched + 99, (void*)&ip , sizeof(unsigned long)); 176 | return shellcode_patched; 177 | } -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | void* find_lib_address(pid_t pid, char* library); 8 | 9 | void* find_executable_space(pid_t pid); 10 | 11 | ssize_t mem_read(struct task_struct* task, char *buf, size_t count, unsigned long pos); 12 | 13 | ssize_t mem_write(struct task_struct* task, char *buf, size_t count, unsigned long pos); 14 | 15 | void* get_shellcode(size_t* shellcode_size, struct pt_regs* registers, unsigned long so_library_name, unsigned long load_so_function, bool came_from_syscall); --------------------------------------------------------------------------------